Lottie庫(kù)Airbnb出的是一個(gè)能夠幫助Android,iOS解析AE導(dǎo)出的包含動(dòng)畫信息的json文件。AE實(shí)現(xiàn)這個(gè)是通過(guò)Bodymovin這個(gè)插件,但是這事應(yīng)該是設(shè)計(jì)師去關(guān)心的就不是開發(fā)人員去關(guān)注的了。
[TOC]
引入庫(kù)
dependencies {
...
compile 'com.airbnb.android:lottie:2.2.0'
...
}
使用LottieAnimationView
可以直接在xml中聲明
<com.airbnb.lottie.LottieAnimationView
android:id="@+id/animation_view"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
app:lottie_fileName="hello-world.json"
app:lottie_loop="true"
app:lottie_autoPlay="true"
/>
-
app:lottie_fileName="hello-world.json"指定了顯示的動(dòng)畫文件,該文件放在assert文件夾下,如圖:
若要在assets的子文件夾下,例如Walthrough下的例如Walthrough.json則為:Walkthrough/Walkthrough.json -
app:lottie_loop="true"設(shè)置是否循環(huán)播放 -
app:lottie_autoPlay="true"設(shè)置是否自動(dòng)啟動(dòng)播放
讀取Json
獲得加載json動(dòng)畫文件有很多種方式:
(1)最簡(jiǎn)單的就是之前放在asserts文件夾下,直接讀取就好
(2)還能通過(guò)網(wǎng)絡(luò)獲取Json后加載,因?yàn)檎也坏骄W(wǎng)絡(luò)json動(dòng)畫資源沒有測(cè)試,貼一下官方文檔的代碼:
//獲取json數(shù)據(jù)并實(shí)例化JsonObject
JSONObject json = new JSONObject(response.body().string());
Cancellable compositionCancellable = LottieComposition.Factory.fromJson(getResources(), json, (composition) -> {
animationView.setComposition(composition);
animationView.playAnimation();
});
// 通過(guò)cancel來(lái)停止對(duì)于composition的異步加載
//compositionCancellable.cancel();
動(dòng)態(tài)添加
LottieAnimationView animationView = (LottieAnimationView) findViewById(R.id.animation_view);
animationView.setAnimation("hello-world.json");
animationView.loop(true);
animationView.playAnimation();
具體的方法含義參考xml中聲明的解釋就好。
常用方法
-
animationView.isAnimating();動(dòng)畫是否在播放 -
animationView.playAnimation();播放動(dòng)畫 -
animationView.pauseAnimation();暫停動(dòng)畫 -
animationView.cancelAnimation();取消動(dòng)畫 -
animationView.setProgress(progress);設(shè)置進(jìn)度,progress范圍0~1 -
animationView.setMinAndMaxProgress(min,max);設(shè)置播放范圍,0~1,測(cè)試目前有bug,設(shè)置播放時(shí)間后再恢復(fù)播放,進(jìn)度會(huì)跳過(guò)一段時(shí)間
管理動(dòng)畫
繪制監(jiān)聽器
//每次動(dòng)畫繪制會(huì)調(diào)用
animationView.addAnimatorUpdateListener((animation) -> {
// 可以用來(lái)獲取播放進(jìn)度并同步進(jìn)度條
float progress = (Float) animation.getAnimatedValue()*100f;
seekbar.setProgress(((int) progress));
});
自定義播放速度與區(qū)間
//一秒內(nèi)值由0變化為1,可以通過(guò)float值管理動(dòng)畫播放區(qū)間,設(shè)置duration控制播放速度
ValueAnimator animator =ValueAnimator.ofFloat(0f, 1f)
.setDuration(1000);//1s
animator.addUpdateListener(animation -> {
//通過(guò)由0->1的變化模擬動(dòng)畫播放
animationView.setProgress(animation.getAnimatedValue());
});
//最終使用ValueAnimator管理動(dòng)畫播放
animator.start();
實(shí)踐與應(yīng)用
基本動(dòng)畫播放與進(jìn)度管理
這個(gè)實(shí)現(xiàn)沒什么難點(diǎn),就是通過(guò)seekbar監(jiān)聽管理進(jìn)度:
sbBaseAnimation.setOnSeekBarChangeListener(new SeekBar.OnSeekBarChangeListener() {
@Override
public void onProgressChanged(SeekBar seekBar, int progress, boolean fromUser) {
animationView.setProgress(progress/100f);
}
@Override
public void onStartTrackingTouch(SeekBar seekBar) {
animationView.pauseAnimation();
btnPlayAnim.setText("播放動(dòng)畫");
}
@Override
public void onStopTrackingTouch(SeekBar seekBar) {
}
});
實(shí)現(xiàn)圖標(biāo)切換動(dòng)畫
原理也很簡(jiǎn)單,本質(zhì)上是這個(gè)圖標(biāo)是一個(gè)動(dòng)畫,list->back是從前往后播放,而back->list則是從后往前倒著播放。因?yàn)檫@個(gè)json動(dòng)畫過(guò)長(zhǎng),所以只截取其中一段就達(dá)到了效果,這個(gè)應(yīng)用感覺對(duì)業(yè)務(wù)還是比較有用的。
private static final int TOTAL_DURATION = 2000;
private static final float START_PROGRESS_PART = 0.1f;
private static final float END_PROGRESS_PART = 0.3f;
//用于記錄當(dāng)前按鈕狀態(tài)
boolean isBackState = false;
@OnClick(R.id.anim_button)
public void onViewClicked() {
if(animatorComplete!=null&&animatorComplete.isRunning()){
animatorComplete.cancel();
animatorComplete = null;
}
//以下的參數(shù)是為了實(shí)現(xiàn)在動(dòng)畫播放到一半又需要切換時(shí),能夠直接往回播放的效果
float progress = animButton.getProgress();
//這次動(dòng)畫播放需要的時(shí)長(zhǎng),因?yàn)閺闹虚g挽回播時(shí)間也會(huì)變短所以要實(shí)時(shí)計(jì)算
int nowDuration = 0;
//這次播放起點(diǎn)與終點(diǎn)的差值
float nowGap;
//動(dòng)畫從頭到尾播放的起終點(diǎn)差值
float totalGap = END_PROGRESS_PART - START_PROGRESS_PART;
if (isBackState) {
//back->list
nowGap = progress - START_PROGRESS_PART;
nowDuration = (int) ((nowGap* TOTAL_DURATION)/totalGap);
animatorComplete = ValueAnimator.ofFloat(progress, START_PROGRESS_PART)
.setDuration(nowDuration);
} else {
//list->back
nowGap = END_PROGRESS_PART - progress;
nowDuration = (int) ((nowGap* TOTAL_DURATION)/totalGap);
animatorComplete = ValueAnimator.ofFloat(progress, END_PROGRESS_PART)
.setDuration(nowDuration);
}
animatorComplete.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
@Override
public void onAnimationUpdate(ValueAnimator animation) {
animButton.setProgress((Float) animation.getAnimatedValue());
tvProgress.setText("animatorComplete"+animation.getAnimatedValue()+"");
}
});
isBackState = !isBackState;
animatorComplete.start();
}
動(dòng)畫中自定義字體與動(dòng)態(tài)文字
代碼實(shí)現(xiàn)很簡(jiǎn)單,就是原理有點(diǎn)弄不清楚:
TextDelegate textDelegate = new TextDelegate(animFont);
animationView.setTextDelegate(textDelegate);
//這里input寫為NAME(原文本)才能更改成其他文字
textDelegate.setText("NAME","動(dòng)態(tài)更改的文本");
其中“NAME”這個(gè)文本是在json動(dòng)畫文本中指定的,動(dòng)態(tài)更改需要在textDelegate.setText()中將原文本設(shè)置為在Json文件中指定的文本才能實(shí)現(xiàn)動(dòng)態(tài)更改。
而文本的字體是在也是在json中指定的,使用時(shí)也需要在asserts/font/文件夾下放入相應(yīng)的.tts字體文件才能保證動(dòng)畫的正常運(yùn)行。
這一類可以實(shí)現(xiàn)更改動(dòng)畫中的字體和文本,代碼參考了官方demo,但是官方文檔目前沒有對(duì)使用到的類進(jìn)行具體說(shuō)明,所以具體原理不好弄清楚,之前關(guān)于“NAME”與字體文件的相關(guān)說(shuō)明也是我查看json文件后猜測(cè)得出的結(jié)論,不一定正確,僅供參考。
引導(dǎo)動(dòng)畫
展示效果是通過(guò)結(jié)合了sliding-intro-screen簡(jiǎn)單的實(shí)現(xiàn)了引導(dǎo)頁(yè)面,然后通過(guò)監(jiān)聽引導(dǎo)頁(yè)面的操作,同步管理動(dòng)畫的進(jìn)度就實(shí)現(xiàn)了。具體Lottie官方的Demo中邏輯也是自己寫的,沒有封裝。也就是說(shuō)如果我們要實(shí)現(xiàn)這種效果的話,還是需要自己進(jìn)行邏輯處理的Lottie并沒有現(xiàn)成的包。
打字動(dòng)畫
打字動(dòng)畫官方是自定義了
FrameLayout,代碼太多所以沒細(xì)看,猜測(cè)本質(zhì)上也是一個(gè)個(gè)的動(dòng)畫view,只不過(guò)使用了有利于重用的LottieComposition。