Android Lottie動畫庫研究

JavaDog發表於2019-01-17

隨著移動網際網路的發展,越來越多的移動 APP 都會從互動視覺方面來提升使用者體驗,其中提供炫酷的動畫效果是一個經常使用的方法。然而,眾所周知,Android平臺中的動畫效果的實現一直以來都存在一些痛點,而這些痛點也給 Android平臺應用上實現豐富動畫效果帶來了非常大的困難。本文將會提及一種更先進的動畫框架,通過這個框架可以方便實現各種炫酷的動畫效果,而且相比傳統動畫效果實現方法,有非常大的優勢,各位看官,不要著急,請接著慢慢往下看!

一. 傳統動效實現

傳統動畫效果的實現方法,歸納總結起來,大概有這麼幾種方案:

1. 程式碼實現

通過 Android 系統提供的動畫介面,通過程式碼來實現動畫效果,一般情況下,這種動畫效果都是比較簡單的,例如實現一個簡單的平移、綻放,旋轉等,如果這三種效果組合起來,將會變得非常複雜,因此,程式碼實現一般也僅適用於簡單動效的開發,如果太複雜的動效的話,那麼投入產出比將會嚴重不平衡。

通過程式碼實現,也又分兩種:

  • 純程式碼實現:使用 Animation 類或 Property Animation 來實現。
  • 序列幀實現:設計師提供一系列的序列幀圖片,開發者將這些圖片封裝成 xml animation,然後在程式碼中載入該 xml,從而實現動畫效果。

這種方式的實現的缺點:

  • 不靈活:程式碼實現複雜,動畫效果的更改,將更改程式碼。
  • 開發效率低下:需要多次除錯和修改,才能達到最終的實現效果。
  • 記憶體開銷大:序列幀是多張圖片實現,記憶體開銷大。
2. Gif 動態圖

通過 Gif 圖片可以實現動效,但是最大的問題是:

  • 記憶體大
  • 不能控制動畫,例如暫停,開始
3. 小視訊

播放一個輕量的小視訊,這種也能達到效果,但最大的問題是:

  • 相容性,視訊播放特殊性可能引發相容的問題
  • View 不好控制,可能會影響到音訊等

總結起來,傳統動效實現的流程如下:

視覺出設計稿 ——> 輸出 n 張圖 ——> 研發實現序列幀 ——> 執行看效果/適配 ——> 調整程式碼再看效果 ——> ...

所下圖所示:

2017-06-30-lottie-01.png

由此可以看出,開發者實現一個動畫非常繁瑣複雜,而且每增加一種動畫,需要重新開發。

二. 傳統動效實現的缺點

綜合看來,以上幾種方案都有非常明顯的缺點,再次概括一下,傳統動畫效果的缺點有

1. 效率低

設計師要出多終序列幀圖片,工作量大;研發需要針對這些序列幀來實現,反覆除錯,開發工作量大,從而導致一個動畫效果的實現的整體效率非常低。

2. 不靈活

要更新一個動畫,需要研發再次修改程式碼,再反覆除錯,非常不靈活。

那針對這些問題,有沒有一種能全新的解決方案來解決這些問題,使動畫效果的實現更加優雅更高效呢?接下來就是本文的重點 —— Airbnb Lottie 動畫庫登場!

三. Airbnb Lottie動畫框架的實

關於 lottiegithub.com/airbnb/lott… ), 它提供了優雅的解決方案,通過這個框架,可以做出非常豐富的炫酷的動畫效果。先來看一下官方的效果圖:

2017-06-30-lottie-02.gif

2017-06-30-lottie-03.gif

2017-06-30-lottie-04.gif

2017-06-30-lottie-05.gif

1. 基於 lottie 動效實現流程

  • 第一步: 設計師設計動畫效果,再通過外掛(Adobe After Effects)將動畫效果匯出成一個動畫描述檔案
  • 第二步: 將這個動畫描述檔案,預置在應用的 assets 目錄下,通過lottie框架載入這些動畫檔案。
  • 第三步: 執行,檢視效果

概括起來,如下圖所示:

2017-06-30-lottie-06.png

2. Lottie的優勢

相比傳統的實現方案,具備非常大的優勢。

a) 高效

開發者一次開發,可以多次複用,不需要再去寫各種具體的動畫相關的程式碼。設計師設計好動畫效果之後,匯出檔案即可。

b) 靈活

由於動畫通過檔案來描述,替換不同的檔案,將會得到不同的動畫效果,動效的更新或升級,將非常靈活。

c) 資料來源多樣性

既然是載入動畫描述檔案,那麼這個檔案就可以從任意地方來,assets、sdcard、network都是可以的。從網路載入動畫描述檔案,將能做到不發版的情況下,動態更新動效。可以從網路下載動畫檔案,從而可以快速做A/B test。

d) 跨平臺

動畫檔案可以應用於 Android、iOS、React Native,這樣設計師只需出一份動效設計稿就行,不用區分平臺。

不同的動畫效果,只需要做一次開發即可

四. Lottie實現原理

1. 開發流程圖

2017-06-30-lottie-08.png

2. 實現原理

Lottie使用json檔案來作為動畫資料來源,json檔案是通過Bodymovin外掛匯出的,檢視sample中給出的json檔案,其實就是把圖片中的元素進行來拆分,並且描述每個元素的動畫執行路徑和執行時間。Lottie的功能就是讀取這些資料,然後繪製到螢幕上。

Lottie 提供了一個 LottieAnimationView 給使用者使用,而實際 Lottie 的核心是 LottieDrawable,它承載了所有的繪製工作,LottieAnimationView則是對LottieDrawable 的封裝,再附加了一些例如 解析 的功能。

LottieComposition 是 對應的 Model,承載 的所有資訊。
CompositionLayer 是 layer 的集合。
ImageAssetBitmapManager 負責管理動畫所需的圖片資源。

它們的關係:

2017-06-30-lottie-09.jpg

3. 資料解析

首先要解析json,建立資料到物件的對映,然後根據資料物件建立合適的Drawable繪製到view上,動畫的實現可以通過操作讀取到的元素完成。

具體過程如下所示:

json檔案 ——> Componsition ——> Drawable ——> View

通過如下3個核心類來來完成整個工作流程,因而使用起來比較簡單。以下是三個比較核心的類的說明:

  • LottieComposition (json->資料物件)

Lottie使用LottieComposition來作為After Effects的資料物件,即把Json檔案對映為到LottieComposition,該類中提供瞭解析json的靜態方法。

  • LottieDrawable (資料物件->Drawable)

這個類是最上層使用的重要的一個類,動畫的繪製就是由這個類來實現。

  • LottieAnimationView(繪製)

操作集合,LottieAnimationView 繼承自 AppCompatImageView,封裝了一些動畫的操作,具體的繪製時委託為 LottieDrawable 完成的。

資料的解析,主要參考 LottieComposition.fromJsonSync 方法:

    static LottieComposition fromJsonSync(Resources res, JSONObject json) {
      Rect bounds = null;
      float scale = res.getDisplayMetrics().density;
      int width = json.optInt("w", -1);
      int height = json.optInt("h", -1);

      if (width != -1 && height != -1) {
        int scaledWidth = (int) (width * scale);
        int scaledHeight = (int) (height * scale);
        bounds = new Rect(0, 0, scaledWidth, scaledHeight);
      }

      long startFrame = json.optLong("ip", 0);
      long endFrame = json.optLong("op", 0);
      int frameRate = json.optInt("fr", 0);
      LottieComposition composition =
          new LottieComposition(bounds, startFrame, endFrame, frameRate, scale);
      JSONArray assetsJson = json.optJSONArray("assets");
      parseImages(assetsJson, composition);
      parsePrecomps(assetsJson, composition);
      parseLayers(json, composition);
      return composition;
    }
複製程式碼

還有 parseImagesparsePrecompsparseLayers 這幾個方法。

4. JSON檔案的屬性含義

動畫描述檔案是通過 bodymovin 外掛匯出來的,裡面包含了動畫的一切資訊,包括了幀率,動畫形態,如何做動畫等。接下來將簡單說明一下動畫描述檔案中的主要屬性。

最外部結構:

{
  "v": "4.6.2",
  "fr": 25,
  "ip": 0,
  "op": 1000,
  "w": 720,
  "h": 800,
  "nm": "合成 1",
  "ddd": 0,
  "assets":[],
  "layers":[]
}
複製程式碼

屬性的含義:

屬性含義
vbodymovin的版本
fr幀率
ip起始關鍵幀
op結束關鍵幀
w動畫寬度
h動畫高度
assets動畫圖片資源資訊
layers動畫圖層資訊

從這裡可以獲取 設計的動畫的寬高,幀相關的資訊,動畫所需要的圖片資源的資訊以及圖層資訊。

assets

圖片資源資訊, 相關類 LottieImageAsset、 ImageAssetBitmapManager。

"assets": [
    {
      "id": "image_0",
      "w": 58,
      "h": 31,
      "u": "images/",
      "p": "img_0.png"
    }
}
複製程式碼

屬性的含義:

屬性含義
id圖片id
w圖片寬度
h圖片高度
p圖片名稱

layers

圖層資訊,相關類:Layer、BaseLayer以及 BaseLayer 的實現類。

{
    "ddd": 0,
    "ind": 0,
    "ty": 2,
    "nm": "btnSlice.png",
    "cl": "png",
    "refId": "image_0",
    "ks": {....},
    "ao": 0,
    "ip": 0,
    "op": 90.0000036657751,
    "st": 0,
    "bm": 0,
    "sr": 1
}
複製程式碼
屬性含義
nmlayerName 圖層資訊
refId引用的資源 id,如果是 ImageLayer 那麼就是圖片的id
tylayertype 圖層型別
ipinFrame 該圖層起始關鍵幀
opoutFrame 該圖層結束關鍵幀
ststartFrame 開始
indlayer id 圖層 id

Layer 可以理解為圖層,跟 PS 等工具的概念相同,每個 Layer 負責繪製自己的內容。

在 Lottie 裡擁有不同的 Layer,目前有 PreComp,Solid,Image,Null,Shape,Text ,各個 Layer 擁有的屬性各不相同,這裡只指出共有的屬性。

下圖為 Layer 相關類圖:

2017-06-30-lottie-10.jpg

5. 動畫如何動起來的?

還有一個問題,動畫是如何動起來的呢?這裡用到了屬性動畫來產生一個0~1的插值,根據不同的插值來播放不同幀的動畫,說白了,就是呼叫 setProgress(float) 來做動畫。程式碼如下:

private final ValueAnimator animator = ValueAnimator.ofFloat(0f, 1f);

public LottieDrawable() {
    animator.setRepeatCount(0);
    animator.setInterpolator(new LinearInterpolator());
    animator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
      @Override public void onAnimationUpdate(ValueAnimator animation) {
        if (systemAnimationsAreDisabled) {
          animator.cancel();
          setProgress(1f);
        } else {
          setProgress((float) animation.getAnimatedValue());
        }
      }
    });
  }
複製程式碼
6. lottie適配原理

內部適配邏輯如下:

static LottieComposition fromJsonSync(Resources res, JSONObject json) {
      Rect bounds = null;
      float scale = res.getDisplayMetrics().density;
      int width = json.optInt("w", -1);
      int height = json.optInt("h", -1);

      if (width != -1 && height != -1) {
        int scaledWidth = (int) (width * scale);
        int scaledHeight = (int) (height * scale);
        bounds = new Rect(0, 0, scaledWidth, scaledHeight);
      }
      ...
}
複製程式碼

LottieAnimationView#setComposition:

public void setComposition(@NonNull LottieComposition composition) {
    if (L.DBG) {
      Log.v(TAG, "Set Composition \n" + composition);
    }
    lottieDrawable.setCallback(this);

    boolean isNewComposition = lottieDrawable.setComposition(composition);
    if (!isNewComposition) {
      // We can avoid re-setting the drawable, and invalidating the view, since the composition
      // hasn't changed.
      return;
    }

    int screenWidth = Utils.getScreenWidth(getContext());
    int screenHeight = Utils.getScreenHeight(getContext());
    int compWidth = composition.getBounds().width();
    int compHeight = composition.getBounds().height();
    if (compWidth > screenWidth ||
        compHeight > screenHeight) {
      float xScale = screenWidth / (float) compWidth;
      float yScale = screenHeight / (float) compHeight;

      float maxScaleForScreen = Math.min(xScale, yScale);
      setScale(Math.min(maxScaleForScreen, lottieDrawable.getScale()));

      Log.w(L.TAG, String.format(
          "Composition larger than the screen %dx%d vs %dx%d. Scaling down.",
          compWidth, compHeight, screenWidth, screenHeight));
    }

    ...
  }
複製程式碼
7. 播放動畫時序圖

播放動畫的時序圖如下所示:

2017-06-30-lottie-11.jpg

其中 BaseKeyframeAnimation 類是比較核心的,它根據當前的 progress,來計算所需要的值。 關於 BaseKeyframeAnimation 的繼承關係圖如下:

2017-06-30-lottie-12.jpg

五. lottie vs keyframes

lottie由 Airbnb 出品,而keyframes由 facebook 出品,這兩個庫實現效果都差不多。據 lottie 官網說功能比 keyframes 強大一些。感興趣的看官可以去深入研究一下。

關於 keyframes 的介紹,請參考:
facebookincubator.github.io/Keyframes/

六. 後續思考

1. 更酷的使用者引導

App的使用者引導完成可以用 lottie 來實現,介面的圖形可以跟隨手勢滑動而變化,這樣的體驗會更好,更新穎。

2. 動效工具

完全可以開發一個預覽動效的工具,提供給視覺設計師,比如,帶一個二維碼掃描功能,設計師設計好動效後,用這個app掃描二維碼,把動畫描述檔案下載到本地,就可以立即看到動畫在App中的效果是什麼樣。

3. 更多動效成為可能

基於這樣的動畫框架,App中可以實現更多更豐富的動畫效果,這將會大大提升使用者體驗。

七. 參考資料

Android Lottie動畫庫研究


相關文章