1. 程式人生 > >Android•Lottie 動畫庫填坑記

Android•Lottie 動畫庫填坑記

?wxfrom=5&wx_lazy=1

作者 | 負了時光不負卿

地址 | http://www.jianshu.com/p/89c64253fd77

宣告 | 本文是 負了時光不負卿 原創,已獲授權釋出,未經原作者允許請勿轉載

入坑背景

由於從事直播軟體開發的緣故,本猿在版本迭代過程中一期不落的接觸到各式各樣動畫效果。最早的時候,苦逼的用 Android 原生動畫做直播間全屏禮物,反覆的看著美工給的 Flash 效果圖,不斷的拼湊素材圖片,調整控制動畫播放的屬性值,各個動畫程式碼都很類似,但卻無法套用,一連兩三天下來,基本上腦海中除了動畫就一片空白

踩坑準備

熟悉一個新的框架最快的方式就是檢視官方文件,因為官方文件中一般都會給出一個 Demo,果不其然,Lottie 也是!文件的閱讀量不是很大,通篇下來介紹了:

播放本地 Assets 目錄下的 Json 動畫檔案
通過 Json 資料播放動畫
如何對動畫進行監聽以及動畫進度調節
Lottie 動畫資料的預載入和快取
為 Assets 目錄下的 Json 動畫檔案配置動畫所需要的素材

開始入坑

然而,他介紹了這麼多,並沒有一款適合我的。因為伺服器下發不是簡單的 Json 資料,是一個動畫壓縮包,裡面包括了動畫檔案和播放動畫需要的素材檔案,而且解壓後的檔案也不在 Asset 目錄下。於是,只好跟蹤 animationView.setAnimation("hello-world.json")原始碼,看看最終到底做了什麼事!

public void setAnimation
(String animationName)
{
   setAnimation(animationName, defaultCacheStrategy);
 }

一個引數呼叫兩個引數同名方法,只好接著往下看!

public void setAnimation(final String animationName, final CacheStrategy cacheStrategy) {
   this.animationName = animationName;
   if (weakRefCache.containsKey(animationName)) {
     WeakReference<LottieComposition> compRef = weakRefCache.get
(animationName);
     if (compRef.get() != null) {
       setComposition(compRef.get());
       return;
     }
   } else if (strongRefCache.containsKey(animationName)) {
     setComposition(strongRefCache.get(animationName));
     return;
   }
   this.animationName = animationName;
   lottieDrawable.cancelAnimation();
   cancelLoaderTask();
   compositionLoader = LottieComposition.Factory.fromAssetFileName(getContext(), animationName,
       new OnCompositionLoadedListener() {
         @Override
         public void onCompositionLoaded(LottieComposition composition)
{
           if (cacheStrategy == CacheStrategy.Strong) {
             strongRefCache.put(animationName, composition);
           } else if (cacheStrategy == CacheStrategy.Weak) {
             weakRefCache.put(animationName, new WeakReference<>(composition));
           }
           setComposition(composition);
         }
       });
 }

從這裡可以看到官方文件中說的快取,包括強引用快取,弱引用快取,和無快取模式,而且知道 Json 動畫檔案最終會轉化為 Composition 物件,而 Compostion 物件是通過 LottieComposition

public static Cancellable fromAssetFileName(Context context, String fileName,
       OnCompositionLoadedListener loadedListener)
{
     InputStream stream;
     try {
       stream = context.getAssets().open(fileName);
     } catch (IOException e) {
       throw new IllegalStateException("Unable to find file " + fileName, e);
     }
     return fromInputStream(context, stream, loadedListener);
   }

看到這裡我們這就明白,當初傳入的檔名,最終還是通過getAssets().open(fileName) 的方法,以流的方式進行處理了,於是我們可以這樣載入放在其他目錄下的 Json 動畫檔案。

public static void loadAnimationByFile(File file, final OnLoadAnimationListener listener) {
       if (file == null || !file.exists()) {
           if (listener != null) {
               listener.onFinished(null);
           }
           return;
       }
       FileInputStream fins = null;
       try {
           fins = new FileInputStream(file);
           LottieComposition.Factory.fromInputStream(GlobalContext.getAppContext(), fins, new OnCompositionLoadedListener() {
               @Override
               public void onCompositionLoaded(LottieComposition composition) {
                   if (listener != null) {
                       listener.onFinished(composition);
                   }
               }
           });
       } catch (IOException e) {
           e.printStackTrace();
           if (listener != null) {
               listener.onFinished(null);
           }
           if (fins != null) {
               try {
                   fins.close();
               } catch (IOException e1) {
                   e1.printStackTrace();
               }
           }
       }
   }

非同步的方式獲取 Composition 物件,因為不使用 setAnimation(final String animationName, final CacheStrategy cacheStrategy) 方法,所以我們沒法使用框架提供的快取,為了下次播放時不需要重新解析動畫檔案,使動畫的載入速度更快,我們也需要重新做一套緩衝處理,如下

LocalLottieAnimUtil.loadAnimationByFile(animFile, new LocalLottieAnimUtil.OnLoadAnimationListener() {
    @Override
   public void onFinished(LottieComposition lottieComposition) {
          if (lottieComposition != null) {
                mCenter.putLottieComposition(id, lottieComposition);  // 使用
           } else {
               GiftFileUtils.deleteFile(getAnimFolder(link));  //刪除動畫檔案目錄,省的下次載入依然失敗,而是重新去下載資源壓縮包
        }
public class EnterRoomResCenter {
   private SparseArray<LottieComposition> lottieCompositions = new SparseArray<>();  //快取Composition
   public void putLottieComposition(int id, LottieComposition composition) {
       lottieCompositions.put(id, composition);
   }
   public LottieComposition getAnimComposition(int id) {
       return mCenter.getLottieComposition(id);
   }
}

完成了 Json 動畫檔案的載入,接下來就是播放動畫。正如原始碼方法中 setAnimation(final String animationName, final CacheStrategy cacheStrategy)  一樣,我們也需要對 LottieAnimationView 進行setComposition(composition) 處理,然後呼叫LottieAnimationView.playAnimation() 就可以進行動畫播放了,於是我這樣做了:

public static void playAnimation(LottieAnimationView animationView,LottieComposition composition) {
       animationView.setComposition(composition);
       animationView.playAnimation();
   }

想想這個需求馬上就要搞定,於是我抿抿嘴偷偷笑了,這也太輕鬆了吧!於是端起茶杯去接了杯水,並運行了專案,準備回來看到那絢麗的動畫。然而,事與願違,等待我的是一片血紅的“大姨媽”。

java.lang.IllegalStateException: 
You must set an images folder before loading an image. Set it with LottieComposition#setImagesFolder or LottieDrawable#setImagesFolder

看到這個錯誤,想起官方文件上面有說,如何為動畫配置播放動畫所需要的素材,而且錯誤提示也特別的明顯,看了看給的資源包的目錄,似乎發現了什麼!於是我按照官方《為 Assets 目錄下的 Json動畫檔案設定播放動畫所需要的資源》一樣,改了一下程式碼:

?

public static void playAnimation(LottieAnimationView animationView,String imageFolder, LottieComposition composition) {
       animationView.setComposition(composition);
      animationView.setImageAssetsFolder(imageFolder);   // 新新增的
       animationView.playAnimation();
   }


想著異常資訊都提示這麼明顯了,而且官方文件給的模板也是這樣寫的,我更加確定這次動畫播放絕對的沒有問題。然而,動畫最終還是沒有播放出來!沒辦法,只好繼續翻原始碼,既然 Assets 目錄下setImageAssetsFolder

@SuppressWarnings("WeakerAccess") public void setImageAssetsFolder(String imageAssetsFolder) {
   lottieDrawable.setImagesAssetsFolder(imageAssetsFolder);
 }

沒有什麼頭緒只好繼續往下看:

@SuppressWarnings("WeakerAccess") public void setImagesAssetsFolder(@Nullable String imageAssetsFolder) {
   this.imageAssetsFolder = imageAssetsFolder;
 }

這個變數被設定成類屬性了,那麼我們只需要在這個類下搜尋怎麼樣被使用就可以馬上定位出原因,發現有這麼一行:

imageAssetBitmapManager = new ImageAssetBitmapManager(getCallback(),
         imageAssetsFolder, imageAssetDelegate, composition.getImages());
   }

我擦,變數被傳遞到一個 ImageAssetBitmapManager 物件裡面去了,只好進這個類繼續跟蹤,最終定位到這樣一個方法:

Bitmap bitmapForId(String id) {
   Bitmap bitmap = bitmaps.get(id);
   if (bitmap == null) {
     LottieImageAsset imageAsset = imageAssets.get(id);
     if (imageAsset == null) {
       return null;
     }
     if (assetDelegate != null) {
       bitmap = assetDelegate.fetchBitmap(imageAsset);
       bitmaps.put(id, bitmap);
       return bitmap;
     }
     InputStream is;
     try {
       if (TextUtils.isEmpty(imagesFolder)) {
         throw new IllegalStateException("You must set an images folder before loading an image." +
             " Set it with LottieComposition#setImagesFolder or LottieDrawable#setImagesFolder");
       }
       is = context.getAssets().open(imagesFolder + imageAsset.getFileName());
     } catch (IOException e) {
       Log.w(L.TAG, "Unable to open asset.", e);
       return null;
     }
     BitmapFactory.Options opts = new BitmapFactory.Options();
     opts.inScaled = true;
     opts.inDensity = 160;
     bitmap = BitmapFactory.decodeStream(is, null, opts);
     bitmaps.put(id, bitmap);
   }
   return bitmap;
 }

播放動畫所需要的圖片資源都通過這個方法獲取,傳入一個圖片檔名稱,然後通過流獲取 Bitmap 物件並返回。這裡需要介紹一下:

如果 Json 動畫檔案使用了圖片素材,裡面的 Json 資料必然會宣告該圖片檔名。在 Composition.Factory 進行解析為 Composition 時,裡面使用的圖片都以鍵值對的方式存放到 Composition 的

private final Map<String, LottieImageAsset> images = new HashMap<>() 中,LottieAnimationView.setCompostion(Compostion) 最終落實到LottieDrawable.setCompostion(Compostion),LottieDrawable 為了獲取動畫裡面的 bitmap 物件,Lottie 框架封裝了ImageAssetBitmapManager 物件,在 LottieDrawable 中建立,將圖片的獲取轉移到 imageAssetBitmapManager 中,並暴露 public Bitmap bitmapForId(String id) 的方法。

LottieImageAsset imageAsset = imageAssets.get(id);

上面的 bitmapForId(String id) 方法體中有這麼一行程式碼,如上,之前Json 動畫檔案解析的圖片都存放到 imageAssets中,id 是當前需要載入的圖片素材名,通過 get 獲取到對應的 LottieImageAsset 物件,其實裡面也就包裝了該 id 值,做這層包裝可能為了以後方便擴充套件吧!

if (assetDelegate != null) {
       bitmap = assetDelegate.fetchBitmap(imageAsset);
       bitmaps.put(id, bitmap);
       return bitmap;
     }
    ...
     is = context.getAssets().open(imagesFolder + imageAsset.getFileName());
    bitmap = BitmapFactory.decodeStream(is, null, opts);
    return bitmap;
   ...

同樣從 bitmapForId(String id) 方法體中提取出如上程式碼,從上面可以看出如果 assetDelegate == null,它就會從 Asset的imagesFolder 目錄下找素材檔案。因為之前我們並沒有設定過 assetDelegate,而且我們的素材並不是在 Asset 的 imagesFolder 目錄下,所以獲取不到 bitmap 物件,動畫無法播放也是情有可原的,不斷的反向追溯assetDelegate 來源,找到LottieAnimationView.setImageAssetDelegate(ImageAssetDelegate assetDelegate) 方法,所以調整之前的程式碼,如下:

public static ImageAssetDelegate imageAssetDelegate = new ImageAssetDelegate() {
       @Override
       public Bitmap fetchBitmap(LottieImageAsset asset) {
           String filePath = currentImgFolder + File.separator + asset.getFileName();
           return BitmapFactory.decodeFile(filePath, opts);
       }
   }
   public static void playAnimation(LottieAnimationView animationView, String imageFolder, ImageAssetDelegate imageAssetDelegate, LottieComposition composition) {
       if (animationView == null || composition == null) {
           return;
       }
       animationView.setComposition(composition);
       animationView.setImageAssetsFolder(imageFolder);
       animationView.setImageAssetDelegate(imageAssetDelegate);
       animationView.playAnimation();
   }

到現在為此,這個動畫才能播放出來,這個地方有一點比較坑的就是ImageAssetDelegate 的建立:

public static ImageAssetDelegate imageAssetDelegate = new ImageAssetDelegate() {
       @Override
       public Bitmap fetchBitmap(LottieImageAsset asset) {
           String filePath = currentImgFolder + File.separator + asset.getFileName();
           return BitmapFactory.decodeFile(filePath, opts);
       }
   }

每次使用的時候,我們都需要有這樣一個 currentImgFolder 變數,維護這個檔案所在的父目錄的位置,其實框架大可以在ImageAssetBitmapManager 中這樣呼叫,將之前我們用setImageFolder(String folder) 又重新的回調回來。

if (assetDelegate != null) {
       bitmap = assetDelegate.fetchBitmap(imagesFolder, imageAsset);    // imagesFolder是新加
       bitmaps.put(id, bitmap);
       return bitmap;
     }

動畫展示效果不正常

  • 在動畫 json 檔案中,有如下類似的資料,其中 W 和 H 欄位聲明瞭整個動畫的輸出大小,你需要確保你使用的LottieAnimationVIew 的寬高比和這個一致。

{"v":"4.9.0","fr":25,"ip":0,"op":50,"w":1242,"h":128,"nm":"WWW","ddd":0,"assets": ....
  • 播放本地動畫檔案展示的動畫偏小或偏大

注意 ImageAssetDelegate 的 fetBitmap() 程式碼中 indensity 屬性的設定

@Override
   public Bitmap fetchBitmap(LottieImageAsset asset) {
       String filePath = currentImgFolder + File.separator + asset.getFileName();
       BitmapFactory.Options opts = new BitmapFactory.Options();
       opts.inDensity = 110;                                                                           //請留意這個值的設定
       return BitmapFactory.decodeFile(filePath, opts);
   }
  • 同一個 LottieAnimationView 播放兩個帶素材的 Json 動畫檔案,會共用同一張素材圖片。

Bitmap bitmapForId(String id) {
   Bitmap bitmap = bitmaps.get(id);
   ...
   return bitmap;
 }

在 ImageAssetBitmapManager 的 bitmapForId() 的方法會對bitmap 進行快取,建立<檔名 : Bitmap>的對映表,如果兩個Json 動畫檔案裡面使用的素材圖同名的話,就會出現如下問題。解決辦法就是保證每個 Json 動畫檔案使用的素材圖片不同名。

實用總結

  • 播放放置在 Asset 目錄下的動畫檔案

設定播放檔案: setAnimation("檔名")
如果動畫檔案帶素材: setImageAssetsFolder("資料夾名")

  • 播放系統目錄下的動畫檔案

非同步獲取 Compostion 物件: LottieComposition.Factory.fromInputStream()
設定播放的素材: setComposition(composition)
如果動畫檔案帶素材: setImageAssetsFolder("資料夾名") + setImageAssetDelegate(imageAssetDelegate)

與之相關

0

相關推薦

AndroidLottie 畫庫

作者 | 負了時光不負卿 地址 | http://www.jianshu.com/p/89c64253fd77 宣告 | 本文是 負了時光不負卿 原創,已獲授權釋出,未經原作者允許請勿轉載 入坑背景 由於從事直播軟體開發的緣故,本猿在版本迭代過程中一期不落的接觸到各式各樣動畫效果。最早的時候,苦逼

Android Lottie畫庫介紹

        目前支援android上gif動畫主要存在兩種方式:第一種為將動畫做成一張張圖片然後進行快速切換,從而形成動畫效果,第二種將動畫變成json字串,利用開源庫進行解析,然後進行顯示從而達到動畫效果。其中兩種方式的典型代表庫為:android-gif-drawa

Android-動畫- Lottie 畫庫使用介紹

Lottie 是 Airbnb 公司開源的動畫庫。官方文件是這麼介紹的Lottie for Android, iOS, React Native, and Web。Lottie is a mobi

Android專案開發-Fragment的onAttach

背景 現在 Android 開發多使用一個 Activity 管理多個 Fragment 進行開發,不免需要兩者相互傳遞資料,一般是給 Fragment 添加回調介面,讓 Activity 繼承並實現。 回撥介面一般都寫在 Fragment 的onAttac

Android 原生應用嵌入React-Native模組開發-環境配置及

1.Can't find variable: __fbBatchedBridge 還是在專案的根資料夾下,命令列執行如下命令,啟動測試伺服器。$ npm start 但是部分Android 6.0的機

Android 專案開發

如果移動端訪問不佳,請訪問–> Github版 關鍵詞:Android7.0 、系統語言 、順序不一致 獲取系統當前語言是一個比較常用的功能,在 Android 7.0 系統上舊函式獲取到的當前系統語言並不正確,或者說從 Android 7.0 起,A

支付開發之微信支付

wiki index 傳輸 系統 外網 ttr throw div union 微信支付,支持的支付方式比較多:有掃碼支付,刷卡支付,APP支付和公眾號支付。其中,APP和網站上最常用的就是APP支付和公眾號支付。前者集成在APP中,後者主要是為微信用戶提供了另一種支付方式

UiAutomator2.0升級

新建 意思 原來 也有 sha ogl selector 輸入文字 apk UiAutomator2.0升級填坑記 SkySeraph May. 28th 2017 Email:[email protected]/* */ 更多精彩請直接訪問SkyS

Ueditor在上傳圖片時,服務器返回502 bad gateway的

ueditor 502 iconv故事發生在2017.10.26下午,環宇同學跟我反應說在測試青羊雙創項目的後臺管理,發現在編輯園區信息時,想要上傳一堆圖片,用編輯器嘗試上傳圖片時,卻報了“上傳失敗,請重試”的錯,類似下圖:納尼?我...... 多麽正常的編輯器,我本地跑的好好的,我趕緊去試了一下,果然報錯,

環境配置(近期實測)——Ubuntu16.04+CUDA9.0+tensorflow-gpu

u盤 earch 實測 win 1.3 ted 等待 gef kernel 近幾年深度學習在物體檢測方面出現了許多基於不同框架的網絡模型,不同模型需要不同的版本的Python、TensorFlow、Keras、CUDA、cuDNN以及操作系統。不得不說,要把經典物體檢測網絡

GitLab 容器化 CI 流程(一)

本文以SpringBoot專案的部署構建為例,對基於GItLab的CI流程進行簡要介紹。   環境準備: 1. 系統環境: 作業系統:CentOS 7.2 1511 GitLab:v11.1.4 GitLab-runner:v11.2.0 Docker:17.0

dcloud HTML5+的Android離線打包過程記錄

寫在前面 Android離線打包的原因: 1.即時通訊 2.即時通訊所需要的訊息推送與離線推送 軟體所需 hbuilder+android studio hbuilder注意事項 1.工程檔案npm run build打包的dist檔案單獨提出來為一個獨立的檔

ActiveMQ

Mysql 持久化現在大家使用MQ,基本都是會把資料進行持久化,MQ預設儲存持久化資料使用kahaDB,但是鑑於大家對mysql比較熟悉,很多人會選擇mysql進行資料的持久化,因為mysql檢視資料還是比較方便的。如果需要把持久化方式改為mysql,則需要修改如下配置: <persistenc

php curl返回false-curl呼叫微信建立自定義選單返回false

首先宣告一點,這個錯誤可能在你的開發生涯中不會遇到,除非你直接複製了微信公眾平臺的api地址。不過這應該算是php中curl擴充套件的一個bug,為什麼是bug呢?讓我們仔細來看。 下邊是示例程式碼,程式碼的功能是呼叫微信公眾平臺的建立自定義選單介面來建立自定義選單。 建

淺談html5 video 移動端

本文介紹了html5 video 移動端填坑記,分享給大家,具體如下:?12345678910111213141516171819202122232425262728<video id="video"style="object-fit:fill"autoplaywebk

Lottie 畫庫使用

Lottie 基本使用 xmls常用屬性 app:lottie_autoPlay="false" //自動播放 相當於 playAnimation() app:lottie_fileName="lottiefiles.com - ATM.json"//設定

spark:聚合函式之first

我們有一張表: val df = spark.createDataset(Seq( (1, "a", 66), (2, "a", 22), (3, "a", 11),

Android Studio 3.0 之依賴報錯。

Error:java.lang.RuntimeException: Annotation processors must be explicitly declared now.  The follow

vue + Echarts (Echarts資料量大,導致瀏覽器卡頓)

最近使用vue + Echarts 實現vue專案的資料視覺化功能的時候,發現隨著元件的增多,元件裡Echarts繪圖的增多,頁面操作越來越卡頓,點選資料比較大的元件時,Echarts繪圖渲染頁面的速度倒是挺快,但是當我點選切換其他元件統計圖時,出現了讓人難以忍受的卡頓,有

小程式

2018/2/9 阿里雲的SLB轉發的https,在低版本的安卓手機上(如OS版本4x以下),不支援TLS1.0 1.1 導致wx.request無效,進而小程式無法使用,目前做法,只能採用nginx轉發,暫不採用SLB了。小程式官方Q&A: