從json文件到炫酷動畫-Lottie實現思路和源碼分析
Lottie是最近Airbnb開源的動畫項目,支持Android、iOS、ReactNaitve三個平臺,本文分析主要Lottie把json文件轉為動畫的思路和源碼實現。
文章首先介紹Lottie的基本使用,然后分析把json文件映射到動畫的實現思路,最后分析Lottie的源碼實現,這里分析的是Lottie-Android。
基本用法
與使用相關的只有三個類文件: LottieAnimationView、LottieComposition、LottieDrawable ,所以Lottie使用起來特別簡單(需要注意Lottie支持API16及以上)。
最簡單的使用方式是在xml中增加LottieAnimationView:
"Logo/LogoSmall.json"是需要加載的動畫數據路徑,根目錄是assets目錄。
也可以通過代碼設置動畫數據json路徑:
然后在代碼中控制動畫播放或者添加監聽事件:
Lottie提供了LottieDrawable可以使用:
可以看到Lottie使用起來非常簡單,我們之后就從以上用到的 LottieAnimationView、LottieComposition、LottieDrawable 入手來分析下Lottie動畫的實現原理。
思路分析
我們先從底層思考下如何在屏幕上繪制動畫,最簡單的方式是把動畫分為多張圖片,然后通過周期替換屏幕上繪制的圖片來形成動畫,這種暴力的方式非常簡單,但缺點明顯,很耗內存,動畫播放中前后兩張替換的圖片在很多元素并沒有變化,重復的內容浪費了空間。
為了提高空間利用率,可以把圖片中的元素進行拆分,使用過photoshop的同學知道,其實在處理一張圖片時,可以把一張復雜的圖片使用多個圖層來表示,每個圖層上展示一部分內容,圖層中的內容也可以拆分為多個元素。拆分元素之后,根據動畫需求,可以單獨對圖層,甚至圖層中的元素設置平移、旋轉、收縮等動畫。
Lottie使用json文件來作為動畫數據源,json文件是通過 Bodymovin 插件導出的,查看sample中給出的json文件,其實就是把圖片中的元素進行來拆分,并且描述每個元素的動畫執行路徑和執行時間。Lottie的功能就是讀取這些數據,然后繪制到屏幕上。
現在思考如果我們拿到一份json格式動畫如何展示到屏幕上。首先要解析json,建立數據到對象的映射,然后根據數據對象創建合適的Drawable繪制到View上,動畫的實現可以通過操作讀取到的元素完成。
源碼分析
1. json文件到對象的映射
Lottie使用 LottieComposition 來作為After Effects的數據對象,即把json文件映射到 LottieComposition , LottieComposition 中提供了解析json的靜態方法:
我們看下 LottieComposition 都有哪些成員變量,這些成員變量描述了After Effects中的動畫。
可以看到startFrame、endFrame、duration、scale等都是動畫中常見的。我們看下 List<Layer> ,看名字就是映射拆分后的圖層數據:
Layer 中完成layer的json數據解析:
2. 數據對象到Drawable的映射
AnimatableLayer 繼承自 Drawable ,我們看下它的子類:
其中 LayerView 對應著 Layer 數據, Layer 中有
對應的 LayerView 中有
可以簡單地理解為ViewGroup中可以包含ViewGroup或者View,但其實整個Lottie實現的動畫都是繪制在一個View LottieAnimationView 上。
AnimatableLayer 的其它子類如 ShapeLayer,RectLayouer 等作為 LayerView 中 List<AnimatableLayer> 的元素。
3. 繪制
LottieAnimationView 繼承自 AppCompatImageView ,封裝了一些動畫的操作,如:
具體的繪制時委托為 LottieDrawable 完成的,我們看下 LottieDrawable 中的 draw() 方法:
LottieDrawable 繼承自 AnimatableLayer ,其 draw() 方法如下:
可以看到先繪制了本層的內容,然后開始繪制包含的 layers 的內容:
這個過程于界面中ViewGroup嵌套繪制類似。
實現分析
上面我們根據動畫繪制的思路分析了下Lottie實現機制,下面從正面來捋一下程序的執行過程:
- 創建 LottieAnimationView lottieAnimationView
- 創建 LottieDrawable lottieDrawable
- 使用 LottieComposition 中的靜態方法解析json文件創建 LottieComposition lottieComposition ,這個過程中已經創建來多個 Layer 對象。
- lottieDrawable.setComposition(lottieComposition)
先清理之前的數據,然后開始 buildLayersForComposition ,即根據 lottieComposition 建立多個 layerView ,此時已經創建好了多個Drawable,并通過List建立的為以 lottieDrawable 為根的一個drawable樹。
lottieAnimationView.setImageDrawable(lottieDrawable)
lottieAnimationView.playAnimation()
直接委托給了 lottieDrawable , lottieDrawable 中有 private final ValueAnimator animator = ValueAnimator.ofFloat(0f, 1f);
重點看下 setProgress 方法
調用了 private final List<KeyframeAnimation<?>> animations = new ArrayList<>() 的 setProgress :
在 onValueChanged 時,各個創建好的Drawable會根據需求進行重繪,達到動畫的效果。
Lottie把動畫從View的動效轉移到了Drawable上。
Lottie的性能
可以看到Lottie把json描述的動畫數據映射到Drawable之后,實現動畫時用到了 ValueAnimator ,在動畫更新時使用Drawable而非View,個人感覺在不需要交互時Drawable顯然比View更加輕量。以下是Lottie性能的官方的說明:
- 如果沒有mask和mattes,那么性能和內存非常好,沒有bitmap創建,大部分操作都是簡單的cavas繪制。
- 如果存在mattes,將會創建2~3個bitmap。bitmap在動畫加載到window時被創建,被window刪除時回收。所以不宜在RecyclerView中使用包涵mattes或者mask的動畫,否則會引起bitmap抖動。除了內存抖動,mattes和mask中必要的bitmap.eraseColor()和canvas.drawBitmap()也會降低動畫性能。對于簡單的動畫,在實際使用時性能不太明顯。
- 如果在列表中使用動畫,推薦使用緩存LottieAnimationView.setAnimation(String, CacheStrategy) 。
來自:http://www.jianshu.com/p/81be1bf9600c