Android LottieAnimationView 使用中遇到的坑

Android LottieAnimationView 使用中遇到的坑

Lottie 是 Airbnb 開源的火熱動畫庫,它能夠解析 AE 動畫中用Bodymovin 導出的json文件,並在移動設備上利用原生庫進行渲染,讓程序員告別痛苦的動畫。Lottie 如今支持了諸多平臺 Android/iOS/RN/Web/Windows,在統一性上也有無可比擬的優點。在Android 使用Lottie 的方式也很是簡單,具體能夠參照 Lottie github,在使用中,皮皮想分享其中使用的兩個注意點。android

在3.0.0如下版本使用高版本json 會出現 MissingKeyFrame的報錯,致使App崩潰

崩潰報錯以下: git

Missing values for keyframe
這個問題在3.0.0以上版本已經修復完成,具體緣由皮皮後續文章在進行討論。 因爲Lottie 3.0.0以上版本必需要項目支持androidX,而皮皮項目遷移到androidX成本過高,暫不可行,只能讓UI提供版本較低的json,同時修復該崩潰問題。 注意到LottieAnimationView是繼承ImageView,報錯是因爲ImageView的draw方法拋出,故在此try...Catch既可。修改代碼以下:
修復MissingKeyFrame報錯
問題解決。

多個json切換時會發生動畫不切換,不播放問題

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處lottieDrawablesetComposition,代碼以下: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,咱們再看lottieViewplayAnimation()方法:異步

public void playAnimation() {
    lottieDrawable.playAnimation();
    enableOrDisableHardwareLayer();
}
複製代碼

此處執行了lottieDrawableplayAnimation()方法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,歡迎各位大神點評。

參考資料

airbnb.io/lottie/#/

github.com/airbnb/lott…

相關文章
相關標籤/搜索