Lottie是最近Airbnb開源的動(dòng)畫項(xiàng)目瞧预,支持Android即硼、iOS鹿响、ReactNaitve三個(gè)平臺,相關(guān)背景介紹可以參考之前的文章Airbnb開源炫酷動(dòng)畫庫Lottie(譯)-看看Airbnb的工程師怎么說夕春。本文分析主要Lottie把json文件轉(zhuǎn)為動(dòng)畫的思路和源碼實(shí)現(xiàn)。
文章首先介紹Lottie的基本使用专挪,然后分析把json文件映射到動(dòng)畫的實(shí)現(xiàn)思路及志,最后分析Lottie的源碼實(shí)現(xiàn),這里分析的是Lottie-Android寨腔。
基本用法
與使用相關(guān)的只有三個(gè)類文件:LottieAnimationView速侈、LottieComposition、LottieDrawable
迫卢,所以Lottie使用起來特別簡單(需要注意Lottie支持API16及以上)倚搬。
最簡單的使用方式是在xml中增加LottieAnimationView:
"Logo/LogoSmall.json"是需要加載的動(dòng)畫數(shù)據(jù)路徑,根目錄是assets目錄乾蛤。
也可以通過代碼設(shè)置動(dòng)畫數(shù)據(jù)json路徑:
然后在代碼中控制動(dòng)畫播放或者添加監(jiān)聽事件:
Lottie提供了LottieDrawable可以使用:
可以看到Lottie使用起來非常簡單每界,我們之后就從以上用到的LottieAnimationView、LottieComposition家卖、LottieDrawable
入手來分析下Lottie動(dòng)畫的實(shí)現(xiàn)原理盆犁。
思路分析
我們先從底層思考下如何在屏幕上繪制動(dòng)畫,最簡單的方式是把動(dòng)畫分為多張圖片篡九,然后通過周期替換屏幕上繪制的圖片來形成動(dòng)畫谐岁,這種暴力的方式非常簡單,但缺點(diǎn)明顯,很耗內(nèi)存伊佃,動(dòng)畫播放中前后兩張?zhí)鎿Q的圖片在很多元素并沒有變化窜司,重復(fù)的內(nèi)容浪費(fèi)了空間。
為了提高空間利用率航揉,可以把圖片中的元素進(jìn)行拆分塞祈,使用過photoshop的同學(xué)知道,其實(shí)在處理一張圖片時(shí)帅涂,可以把一張復(fù)雜的圖片使用多個(gè)圖層來表示议薪,每個(gè)圖層上展示一部分內(nèi)容,圖層中的內(nèi)容也可以拆分為多個(gè)元素媳友。拆分元素之后斯议,根據(jù)動(dòng)畫需求,可以單獨(dú)對圖層醇锚,甚至圖層中的元素設(shè)置平移哼御、旋轉(zhuǎn)、收縮等動(dòng)畫焊唬。
Lottie使用json文件來作為動(dòng)畫數(shù)據(jù)源恋昼,json文件是通過Bodymovin插件導(dǎo)出的,查看sample中給出的json文件赶促,其實(shí)就是把圖片中的元素進(jìn)行來拆分液肌,并且描述每個(gè)元素的動(dòng)畫執(zhí)行路徑和執(zhí)行時(shí)間。Lottie的功能就是讀取這些數(shù)據(jù)鸥滨,然后繪制到屏幕上矩屁。
現(xiàn)在思考如果我們拿到一份json格式動(dòng)畫如何展示到屏幕上。首先要解析json爵赵,建立數(shù)據(jù)到對象的映射吝秕,然后根據(jù)數(shù)據(jù)對象創(chuàng)建合適的Drawable繪制到View上,動(dòng)畫的實(shí)現(xiàn)可以通過操作讀取到的元素完成空幻。
源碼分析
1. json文件到對象的映射
Lottie使用LottieComposition
來作為After Effects的數(shù)據(jù)對象烁峭,即把json文件映射到LottieComposition
,LottieComposition
中提供了解析json的靜態(tài)方法:
我們看下LottieComposition
都有哪些成員變量秕铛,這些成員變量描述了After Effects中的動(dòng)畫约郁。
可以看到startFrame、endFrame但两、duration鬓梅、scale等都是動(dòng)畫中常見的。我們看下List<Layer>
谨湘,看名字就是映射拆分后的圖層數(shù)據(jù):
Layer
中完成layer的json數(shù)據(jù)解析:
2. 數(shù)據(jù)對象到Drawable的映射
AnimatableLayer
繼承自 Drawable
绽快,我們看下它的子類:
其中LayerView
對應(yīng)著Layer
數(shù)據(jù)芥丧,Layer
中有
對應(yīng)的
LayerView
中有可以簡單地理解為ViewGroup中可以包含ViewGroup或者View,但其實(shí)整個(gè)Lottie實(shí)現(xiàn)的動(dòng)畫都是繪制在一個(gè)View LottieAnimationView
上坊罢。
AnimatableLayer
的其它子類如 ShapeLayer续担,RectLayouer
等作為 LayerView
中List<AnimatableLayer>
的元素。
3. 繪制
LottieAnimationView
繼承自 AppCompatImageView
活孩,封裝了一些動(dòng)畫的操作物遇,如:
具體的繪制時(shí)委托為 LottieDrawable
完成的,我們看下 LottieDrawable
中的 draw()
方法:
LottieDrawable
繼承自AnimatableLayer
憾儒,其draw()
方法如下:
可以看到先繪制了本層的內(nèi)容询兴,然后開始繪制包含的layers
的內(nèi)容:
這個(gè)過程于界面中ViewGroup嵌套繪制類似。
實(shí)現(xiàn)分析
上面我們根據(jù)動(dòng)畫繪制的思路分析了下Lottie實(shí)現(xiàn)機(jī)制起趾,下面從正面來捋一下程序的執(zhí)行過程:
- 創(chuàng)建
LottieAnimationView lottieAnimationView
- 創(chuàng)建
LottieDrawable lottieDrawable
- 使用
LottieComposition
中的靜態(tài)方法解析json文件創(chuàng)建LottieComposition lottieComposition
诗舰,這個(gè)過程中已經(jīng)創(chuàng)建來多個(gè)Layer
對象。 lottieDrawable.setComposition(lottieComposition)
先清理之前的數(shù)據(jù)阳掐,然后開始buildLayersForComposition
始衅,即根據(jù)lottieComposition
建立多個(gè)layerView
冷蚂,此時(shí)已經(jīng)創(chuàng)建好了多個(gè)Drawable缭保,并通過List建立的為以lottieDrawable
為根的一個(gè)drawable樹。
lottieAnimationView.setImageDrawable(lottieDrawable)
lottieAnimationView.playAnimation()
直接委托給了lottieDrawable
蝙茶,lottieDrawable
中有private final ValueAnimator animator = ValueAnimator.ofFloat(0f, 1f);
重點(diǎn)看下setProgress
方法
調(diào)用了private final List<KeyframeAnimation<?>> animations = new ArrayList<>()
的setProgress
:
在onValueChanged
時(shí)艺骂,各個(gè)創(chuàng)建好的Drawable會(huì)根據(jù)需求進(jìn)行重繪,達(dá)到動(dòng)畫的效果隆夯。
Lottie把動(dòng)畫從View的動(dòng)效轉(zhuǎn)移到了Drawable上钳恕。
Lottie的性能
可以看到Lottie把json描述的動(dòng)畫數(shù)據(jù)映射到Drawable之后,實(shí)現(xiàn)動(dòng)畫時(shí)用到了ValueAnimator
蹄衷,在動(dòng)畫更新時(shí)使用Drawable而非View忧额,個(gè)人感覺在不需要交互時(shí)Drawable顯然比View更加輕量。以下是Lottie性能的官方的說明:
- 如果沒有mask和mattes愧口,那么性能和內(nèi)存非常好睦番,沒有bitmap創(chuàng)建,大部分操作都是簡單的cavas繪制耍属。
- 如果存在mattes托嚣,將會(huì)創(chuàng)建2~3個(gè)bitmap。bitmap在動(dòng)畫加載到window時(shí)被創(chuàng)建厚骗,被window刪除時(shí)回收示启。所以不宜在RecyclerView中使用包涵mattes或者mask的動(dòng)畫,否則會(huì)引起bitmap抖動(dòng)领舰。除了內(nèi)存抖動(dòng)夫嗓,mattes和mask中必要的bitmap.eraseColor()和canvas.drawBitmap()也會(huì)降低動(dòng)畫性能迟螺。對于簡單的動(dòng)畫,在實(shí)際使用時(shí)性能不太明顯啤月。
- 如果在列表中使用動(dòng)畫煮仇,推薦使用緩存LottieAnimationView.setAnimation(String, CacheStrategy) 。
歡迎關(guān)注公眾號wutongke谎仲,每天推送移動(dòng)開發(fā)前沿技術(shù)文章:
推薦閱讀: