從json文件到炫酷動(dòng)畫-Lottie實(shí)現(xiàn)思路和源碼分析

Lottie是最近Airbnb開源的動(dòng)畫項(xiàng)目,支持Android、iOS、ReactNaitve三個(gè)平臺(tái),相關(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ú)對(duì)圖層,甚至圖層中的元素設(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ù)到對(duì)象的映射,然后根據(jù)數(shù)據(jù)對(duì)象創(chuàng)建合適的Drawable繪制到View上,動(dòng)畫的實(shí)現(xiàn)可以通過操作讀取到的元素完成。

源碼分析

1. json文件到對(duì)象的映射

Lottie使用LottieComposition來作為After Effects的數(shù)據(jù)對(duì)象,即把json文件映射到LottieCompositionLottieComposition中提供了解析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ù)對(duì)象到Drawable的映射

AnimatableLayer 繼承自 Drawable,我們看下它的子類:

其中LayerView對(duì)應(yīng)著Layer數(shù)據(jù),Layer中有


對(duì)應(yīng)的LayerView中有

可以簡單地理解為ViewGroup中可以包含ViewGroup或者View,但其實(shí)整個(gè)Lottie實(shí)現(xiàn)的動(dòng)畫都是繪制在一個(gè)View LottieAnimationView上。

AnimatableLayer 的其它子類如 ShapeLayer,RectLayouer等作為 LayerViewList<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í)行過程:

  1. 創(chuàng)建LottieAnimationView lottieAnimationView
  2. 創(chuàng)建LottieDrawable lottieDrawable
  3. 使用LottieComposition中的靜態(tài)方法解析json文件創(chuàng)建LottieComposition lottieComposition,這個(gè)過程中已經(jīng)創(chuàng)建來多個(gè)Layer對(duì)象。
  4. lottieDrawable.setComposition(lottieComposition)

先清理之前的數(shù)據(jù),然后開始buildLayersForComposition,即根據(jù)lottieComposition建立多個(gè)layerView,此時(shí)已經(jīng)創(chuàng)建好了多個(gè)Drawable,并通過List建立的為以lottieDrawable為根的一個(gè)drawable樹。

  1. lottieAnimationView.setImageDrawable(lottieDrawable)

  2. lottieAnimationView.playAnimation()

直接委托給了lottieDrawablelottieDrawable中有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性能的官方的說明:

  1. 如果沒有mask和mattes,那么性能和內(nèi)存非常好,沒有bitmap創(chuàng)建,大部分操作都是簡單的cavas繪制。
  2. 如果存在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)畫性能。對(duì)于簡單的動(dòng)畫,在實(shí)際使用時(shí)性能不太明顯。
  3. 如果在列表中使用動(dòng)畫,推薦使用緩存LottieAnimationView.setAnimation(String, CacheStrategy) 。

歡迎關(guān)注公眾號(hào)wutongke,每天推送移動(dòng)開發(fā)前沿技術(shù)文章:

wutongke

推薦閱讀:

Airbnb開源炫酷動(dòng)畫庫Lottie(譯)-看看Airbnb的工程師怎么說

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 229,460評(píng)論 6 538
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 99,067評(píng)論 3 423
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 177,467評(píng)論 0 382
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經(jīng)常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 63,468評(píng)論 1 316
  • 正文 為了忘掉前任,我火速辦了婚禮,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 72,184評(píng)論 6 410
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 55,582評(píng)論 1 325
  • 那天,我揣著相機(jī)與錄音,去河邊找鬼。 笑死,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,616評(píng)論 3 444
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 42,794評(píng)論 0 289
  • 序言:老撾萬榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個(gè)月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 49,343評(píng)論 1 335
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 41,096評(píng)論 3 356
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 43,291評(píng)論 1 371
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,863評(píng)論 5 362
  • 正文 年R本政府宣布,位于F島的核電站,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 44,513評(píng)論 3 348
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 34,941評(píng)論 0 28
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 36,190評(píng)論 1 291
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 52,026評(píng)論 3 396
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 48,253評(píng)論 2 375

推薦閱讀更多精彩內(nèi)容