Lottie動畫庫 Android 端源碼淺析

驚艷的Lottie

前段時間airbnb開源的動畫庫Lottie得到了不錯的反響,旨在解決Android、IOS、RN 上面開發動畫成本高、表現不一致的問題,可以說降低了三端動畫的開發成本。

項目地址:https://github.com/airbnb/lottie-android

先上幾個git上的效果:

demo1
demo2

如果需要在3端都分別完成這些動畫,可能就需要折磨設計&開發同學了。人肉寫出這些效果簡直是處女座也無法完成的一件事。

Lottie在這件事上就是來拯救移動開發程序員的。

思路

Lottie借助AE生成動畫,再利用AE插件bodymovin來導出可描述的json文件,而Lottie負責在不同端上解析json文件完成動畫的繪制。

從設計思路上可以看出,這確實是一個很好的解法,Lottie抹平了各端的差異性,通過統一描述(JSON)來表述動畫。這看上去和近年來很火的Weex思路一致。

作為一個Android搬磚程序員,接下來的篇幅主要以Lottie Android SDK(ios也不會呀~~~)來分析一下Lottie的解法。

用法

先來感受一下用法,感覺還是很清爽的:

Dependency:

compile'com.airbnb.android:lottie:1.5.3'

使用lottie_fileName="hello-world.json"來指定動畫路徑,也可以使用:setAnimation("hello-world.json")來指定。


xml usage


java usage

然后你就可以像使用普通動畫對待Lottie了。

原理淺析

繪制

我們先從能接觸到的類com.airbnb.lottie.LottieAnimationView看起,所有的邏輯處理、json解析、繪制工作都在這里完成。

LottieAnimationView

可以看到這個是AppCompatImageView,里面有個很重要的變量:

LottieDrawable lottieDrawable;

LottieAnimationView在初始化時會調用setImageDrawable(lottieDrawable);方法,使用LottieDrawable作為ImageDrawable。并且同時重寫invalidateDrawable方法,當需要invalidate時通知lottieDrawable。

invalidateDrawable

LottieDrawable

繼承關系

從類結構看,繼承自AnimatableLayer,里面實現了Drawable需要實現的方法,主要是public void draw(@NonNull Canvas canvas)用來實現Drawable的繪制工作。

我們先來看看父類AnimatableLayer定義了什么行為。

首先當然是來看最重要的:draw


draw

可以看到,在繪制前,調用applyTransformForLayer對畫布canvas進行了transform操作。接著繪制background、layers。

而layers的定義是:final List layers = new ArrayList<>();每一層,都是一個AnimatableLayer對象。

我們來看看AnimatableLayer的子類。

我們目前大概可以分析出,Lottie 根據JSON 構造了一個樹形結構,root節點是LottieDrawable,將繪制的元素添加到了LottieDrawable.layers中。遞歸的描述每一個元素。

我們接著看“root”:LottieDrawable都做了些什么。

讓Drawable“動起來”

由于Drawable本身只是負責繪制工作,并不會像動畫一樣,有start、end、duration等屬性。因此在LottieDrawable引入了一個簡單的animator = ValueAnimator.ofFloat(0f, 1f)來解決這個問題。

所有對動畫的操作(repeat、start),都是對這個ValueAnimator元素的操作。

setProgerss

從上面可以看到,調用父類AnimatableLayer的setProgerss會通知animations、layers。之前我們已經了解過layers,那么animations是什么呢?來看定義:

private final List> animations = new ArrayList<>();

BaseKeyframeAnimation

BaseKeyframeAnimation從字面意思大概是用來描述關鍵幀的動畫。

做過動畫的朋友大概清楚“關鍵幀”的概念。BaseKeyframeAnimation的意義就是存放了一個動畫中所有的關鍵幀,以及每一幀的過度進度。

listeners可以注冊對動畫的監聽,當外界調用setProgerss時,會通知給監聽者。

keyframes存放了當前動畫所有關鍵幀。

最終對外的方法是:

abstract A getValue(Keyframekeyframe, float keyframeProgress);

從入參看很清晰,傳入了當前所在的關鍵幀,以及幀進度。

KeyFrame

KeyFrame是對每一幀的描述,字段如下:

LottieComposition是當前幀的所有數據,我們后面會講到。

并且提供了靜態工廠方法來生成一個KeyFrame。將JSON文件解析成一個KeyFrame對象。

setTransform

前面提到的animations是在何時注冊的呢?

我們在AnimatableLayer.setTransform方法中看到了注冊方法。這個方法在root節點不會被調用,但是在具體的Layer(如EllipseLayer、ShapeLayerView)中會使用。具體如下:


可以看到,在addAnimation后以及 注冊的updatelistener中調用了invalidateSelf()方法。而invalidateSelf()方法會觸發draw流程。draw會遞歸的調用layers進行繪制。由于每一個layer都擁有當前的 progress,因此就可以正確的繪制出來啦。

JSON 解析

解析工作在調用animationView.setAnimation后,就開始了。

JSON 解析 會用InputStream異步讀取json內容,經過處理后封裝成LottieComposition對象。

當加載完成后,會回調animationView.setComposition方法。animationView會接著調用LottieDrawable.setComposition。


buildLayersForComposition

這里完成了前面提到的layers的構建:

里面做了一次轉換,構造了一個LayerView對象,這個是關鍵所在。

LayerView

LayerView也是繼承自AnimatableLayer。 完成了所有Layer類型的轉換。

在初始化方式中會調用:setupForModel();方法。


摘取了setupShapeLayer的代碼片段,里面會根據不同的數據類型,生成不同的LayerView,這些細節就不再做細究了。

總結

為了實現基本的動畫能力,Lottie做了這么些事情。

1. 繪制:Lottie 使用 ImageView.Drawable來處理動畫。使用統一Canvas來繪制。

2.動畫控制:借助一個ValueAnimator來控制動畫的進度,通過一系列監聽器來通知Drawable進行繪制(draw)。

3.資源轉換:將JSON文件經過處理得到LottieComposition對象,包括了每一層動畫對象、資源(images)等信息。

寫在最后

首先給Lottie的工程師們一個star。

本文只是對Lottie的主要邏輯進行了梳理,沒有梳理動畫格式之類,里面比較復雜的的部分是不同的Layer的繪制、KeyFrame的計算。這部分由于對AE以及bodymovin不太了解就沒有深究了。旨在掌握Lottie的大體設計。如有錯誤之處,請指出。

最后吐槽一波Lottie的代碼結構,雖然源碼不多,但是這個一層結構。。。。

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。

推薦閱讀更多精彩內容