Lottie是最近Airbnb開源的動畫專案,支援Android、iOS、ReactNaitve三個平臺,相關背景介紹可以參考之前的文章Airbnb開源炫酷動畫庫Lottie(譯)-看看Airbnb的工程師怎麼說。本文分析主要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) 。
歡迎關注公眾號wutongke,每天推送移動開發前沿技術文章:
推薦閱讀: