Lottie 是 Airbnb 開源的火熱動畫庫,它能夠解析 AE 動畫中用Bodymovin 導出的json文件,並在移動設備上利用原生庫進行渲染,讓程序員告別痛苦的動畫。Lottie 如今支持了諸多平臺 Android/iOS/RN/Web/Windows,在統一性上也有無可比擬的優點。在Android 使用Lottie 的方式也很是簡單,具體能夠參照 Lottie github,在使用中,皮皮想分享其中使用的兩個注意點。android
崩潰報錯以下: git
這個問題在3.0.0以上版本已經修復完成,具體緣由皮皮後續文章在進行討論。 因爲Lottie 3.0.0以上版本必需要項目支持androidX,而皮皮項目遷移到androidX成本過高,暫不可行,只能讓UI提供版本較低的json,同時修復該崩潰問題。 注意到LottieAnimationView是繼承ImageView,報錯是因爲ImageView的draw方法拋出,故在此try...Catch既可。修改代碼以下: 問題解決。lottieView.setAnimation("lottie/1.json"); // 1
lottieView.setProgress(0f);
lottieView.loop(true);
lottieView.playAnimation(); // 2
複製代碼
正常的lottie運行代碼以下,若是是一個json,這個代碼是不會有問題的,可是若是setAnimation
有不少次的觸發會有一些意想不到得狀況。(從這也說明Lottie對多個json運行時不太友好的,應該經過多個LottieAnimationView來實現該功能) 下面解釋下爲何這個代碼會出問題。程序員
在代碼1 setAnimation
最終會執行到下面代碼:github
public void setAnimation(final String animationName, final CacheStrategy cacheStrategy) {
this.animationName = animationName;
animationResId = 0;
if (ASSET_WEAK_REF_CACHE.containsKey(animationName)) {
WeakReference<LottieComposition> compRef = ASSET_WEAK_REF_CACHE.get(animationName);
LottieComposition ref = compRef.get();
if (ref != null) {
setComposition(ref);
return;
}
} else if (ASSET_STRONG_REF_CACHE.containsKey(animationName)) {
setComposition(ASSET_STRONG_REF_CACHE.get(animationName));
return;
}
lottieDrawable.cancelAnimation();
cancelLoaderTask();
compositionLoader = LottieComposition.Factory.fromAssetFileName(getContext(), animationName,
new OnCompositionLoadedListener() {
@Override public void onCompositionLoaded(LottieComposition composition) {
if (cacheStrategy == CacheStrategy.Strong) {
ASSET_STRONG_REF_CACHE.put(animationName, composition);
} else if (cacheStrategy == CacheStrategy.Weak) {
ASSET_WEAK_REF_CACHE.put(animationName, new WeakReference<>(composition));
}
setComposition(composition);
}
});
}
複製代碼
注意這個代碼在加載文件時是一個異步操做LottieComposition.Factory.fromAssetFileName
,加載完成後纔會執行 setComposition
操做,setComposition
代碼以下json
public void setComposition(@NonNull LottieComposition composition) {
if (L.DBG) {
Log.v(TAG, "Set Composition \n" + composition);
}
lottieDrawable.setCallback(this);
boolean isNewComposition = lottieDrawable.setComposition(composition); // 3
enableOrDisableHardwareLayer();
if (!isNewComposition) {
// We can avoid re-setting the drawable, and invalidating the view, since the composition
// hasn't changed. return; } // If you set a different composition on the view, the bounds will not update unless // the drawable is different than the original. setImageDrawable(null); setImageDrawable(lottieDrawable); this.composition = composition; requestLayout(); } 複製代碼
在註釋3處lottieDrawable
會setComposition
,代碼以下:bash
public boolean setComposition(LottieComposition composition) {
if (this.composition == composition) {
return false;
}
clearComposition(); // 4
this.composition = composition;
buildCompositionLayer(); // 5
animator.setCompositionDuration(composition.getDuration());
setProgress(animator.getValue());
setScale(scale);
updateBounds();
applyColorFilters();
// We copy the tasks to a new ArrayList so that if this method is called from multiple threads,
// then there won't be two iterators iterating and removing at the same time. Iterator<LazyCompositionTask> it = new ArrayList<>(lazyCompositionTasks).iterator(); while (it.hasNext()) { LazyCompositionTask t = it.next(); t.run(composition); it.remove(); } lazyCompositionTasks.clear(); composition.setPerformanceTrackingEnabled(performanceTrackingEnabled); return true; } 複製代碼
註釋4處 會clear以前設置的compositionapp
public void clearComposition() {
recycleBitmaps();
if (animator.isRunning()) {
animator.cancel();
}
composition = null;
compositionLayer = null;
imageAssetManager = null;
invalidateSelf();
}
複製代碼
這時若是lottieView還在運行的話就會中止上一個動畫。註釋5處 會設置compositionLayer 代碼以下less
private void buildCompositionLayer() {
compositionLayer = new CompositionLayer(
this, Layer.Factory.newInstance(composition), composition.getLayers(), composition);
}
複製代碼
這樣咱們就拿到了compositionLayer,咱們再看lottieView
的playAnimation()
方法:異步
public void playAnimation() {
lottieDrawable.playAnimation();
enableOrDisableHardwareLayer();
}
複製代碼
此處執行了lottieDrawable
的playAnimation()
方法ide
public void playAnimation() {
if (compositionLayer == null) {
lazyCompositionTasks.add(new LazyCompositionTask() {
@Override public void run(LottieComposition composition) {
playAnimation();
}
});
return;
}
animator.playAnimation();
}
複製代碼
這裏發現compositionLayer == null
的話 會將playAnimation放到一個異步操做中執行,這樣等上面的json文件加載完成後就會執行lazyCompositionTasks
裏的方法。
至此能夠發現,若是lottieView裏有運行的json動畫時,這時更新新的json文件後,compositionLayer != null, playAnimation可能會在onCompositionLoaded還沒加載好就執行了,這樣動畫就是播放的上一個動畫,而在新的動畫加載完成後會先執行clearComposition
致使老的動畫中止播放,而新的動畫只是設置了,並未開始播放。
分析了多個json動畫爲何會出現不切換,不播放的緣由後,解決方案就很好搞定了:
一、 設置多個LottieAnimationView 執行,須要對多個LottieView進行管理;
二、 使用postDelay延遲
playAnimation
的執行(皮皮設置了50ms後基本能夠解決這個問題,不過不能保證這個問題必定不會發生);
三、 讓文件流的加載和playAnimation順序執行,不使用LottieAnimationView,改用LottieDrawable;
皮皮採用的是第三種方法,具體的代碼以下:
if (mPetLottieDrawable.isAnimating()) {
mPetLottieDrawable.cancelAnimation();
}
mPetLottieDrawable.clearComposition();
LottieComposition.Factory.fromAssetFileName(mContext, json, new OnCompositionLoadedListener() {
@Override
public void onCompositionLoaded(@Nullable LottieComposition composition) {
mPetLottieDrawable.setComposition(composition);
mPetLottieDrawable.setImagesAssetsFolder(image);
mPetLottieDrawable.playAnimation();
mPetLottieIv.setVisibility(View.VISIBLE);
Logger.d(TAG, "playPetLottieAnimationView === playAnimation show");
}
});
複製代碼
這樣就能夠完美的解決上述的問題啦。。。
好了,就寫這麼多吧,我要去呼吸新鮮空氣了!
留下個人WX,歡迎各位大神點評。