Lottie 作為一個(gè)可以輕松實(shí)現(xiàn)復(fù)雜動(dòng)畫的跨平臺(tái)開源動(dòng)畫庫闰挡,從發(fā)布至今翰铡,受到了越來越多開發(fā)者的推崇承粤。筆者所在項(xiàng)目從今年 8 月開始接入 lottie 庫揪漩,使用的是 2.0.0 的版本答捕。接入初期斯议,在享受 lottie 帶來的便利和高效同時(shí)沉填,有時(shí)會(huì)遇到動(dòng)畫效果和預(yù)期不符的情況有鹿,在修正了使用方法后吊奢,問題大多都能解決茉继。最近又遇到了一個(gè)問題咧叭,解決起來花費(fèi)了些精力,記錄下來烁竭。
問題描述
項(xiàng)目中有個(gè)“點(diǎn)贊”的動(dòng)效是用 lottie 實(shí)現(xiàn)的菲茬,而展示效果出現(xiàn)了偶現(xiàn)的 bug,LottieAnimationView 顯示過大:
開啟布局邊界可以清楚的看到“點(diǎn)贊” icon 顯示大小確實(shí)過大了派撕。該 icon 的布局代碼:
布局指定了 LottieAnimationView 的動(dòng)畫資源路徑婉弹、loop 和 scale 屬性,寬终吼、高為 wrap_content镀赌。
這里 scale 設(shè)置為 0.33 需要解釋一下。我們知道 lottie 的 JSON 格式的動(dòng)畫資源文件中是有動(dòng)畫的寬际跪、高屬性的商佛,并且 lottie-android 庫將 JSON 中的寬喉钢、高的單位視為 dp,也就是動(dòng)畫的實(shí)際顯示大小是 JSON 文件中設(shè)定的大小乘以手機(jī)系統(tǒng)的 density 值威彰。那么在 wrap_content 下出牧,為了使動(dòng)畫顯示大小在數(shù)值上和 JSON 中設(shè)定的一致,就需要指定一個(gè) scale 值歇盼,scale 值大小為 density 的倒數(shù)舔痕,以 density = 3 為例,這里 scale 即為 0.33豹缀。
但是從這個(gè) bug 來看伯复,動(dòng)畫 LottieAnimationView 并沒有按照預(yù)期的 scale 縮放,顯示的大小是未縮放的大小邢笙。
分析問題
View 大小顯示不正常啸如,應(yīng)該是 measure 過程出了問題。LottieAnimationView 自己沒有 onMeasure() 方法氮惯,于是查看一下其父類 ImageView 的 onMeasure() 方法叮雳。出現(xiàn) bug 的手機(jī)的系統(tǒng)是 Android 5.1 系統(tǒng),查看 Android 5.1 的 ImageView 源碼可以看到妇汗,onMeasure() 處理時(shí)是基于 mDrawableWidth 和 mDrawableHeight 來確定最終的大小帘不,因此猜測這兩個(gè)變量的賦值出現(xiàn)了問題。 mDrawableWidth 和 mDrawableHeight 的賦值在 updateDrawable 方法中杨箭,如下所示寞焙。
LottieDrawable 重寫了 getIntrinsicWidth() 和 getIntrinsicHeight() 方法
可以看到返回的結(jié)果已經(jīng)考慮 scale 值了。如果返回值不符合預(yù)期互婿,那么一定是 scale 值不正確捣郊,這里先留著,后面繼續(xù)分析慈参。
從 updateDrawable() 方法開始向上層層追蹤調(diào)用呛牲,可以找到調(diào)用 updateDrawable() 方法的調(diào)用鏈:
由此可知,LottieAnimationView 在解析動(dòng)畫文件成 LottieComposition 后懂牧,setComposition() 時(shí)會(huì)調(diào)用到 updateDrawable() 來獲取 drawable 的大小侈净,進(jìn)而確定自身的大小。
由于我們把動(dòng)畫資源寫到了 xml 布局文件中僧凤,所以 LottieComposition 的解析時(shí)機(jī)是在 LottieAnimationView 的 init() 方法中畜侦。
setAnimation() 方法首先在緩存池中查找是否存在解析好的相同文件名的動(dòng)畫文件,如果存在直接調(diào)用 setComposition() 使用躯保;如果不存在旋膳,則啟動(dòng) loader 加載、解析動(dòng)畫文件途事,在回調(diào)函數(shù)中調(diào)用 setComposition()验懊,相關(guān)代碼如下:
到這里擅羞,代碼邏輯已經(jīng)梳理得比較清晰了:在 LottieAnimationView 中解析動(dòng)畫文件成 LottieComposition,然后調(diào)用 setComposition() 保存义图、處理 LottieComposition减俏,最終調(diào)用到 ImageView 的 updateDrawable() 方法,獲取 LottieDrawable 的尺寸碱工,反映到 LottieAnimationView 上娃承。
這個(gè)流程看起來沒什么問題。但是從 bug 上來看怕篷,最終獲取的 LottieDrawable 尺寸是錯(cuò)誤的历筝。根據(jù)前面的結(jié)論,此時(shí)應(yīng)該是 scale 值不正確了廊谓。繼續(xù)看一下 scale 設(shè)置的時(shí)機(jī)梳猪。
從上述 LottieAnimationView 的 init() 方法中看到,scale 值從 xml 布局文件中解析得來蒸痹,數(shù)據(jù)肯定不會(huì)有錯(cuò)誤春弥。但是設(shè)置的時(shí)機(jī)是在 setAnimation() 之后。這會(huì)有什么問題呢叠荠?
當(dāng)然有問題了惕稻,setAnimation() 時(shí)如果緩存中有解析好的動(dòng)畫資源,那么就會(huì)直接獲取使用蝙叛,繼續(xù)執(zhí)行到 ImageView.updateDrawable(),此時(shí) scale 值還未設(shè)置公给,初始為 1f借帘,所以獲取到的 LottieDrawable 大小就是未縮放的大小了,LottieAnimationView 的大小也就偏大了淌铐。
那本文開頭提到的 bug 原因是這樣的嗎肺然?答案是肯定的。這個(gè)“點(diǎn)贊”動(dòng)畫資源在其他的業(yè)務(wù)場景中也有使用腿准,并且际起,由于是在列表中使用,因此做了強(qiáng)緩存設(shè)置吐葱。所以只要這個(gè)“點(diǎn)贊”資源加載街望、解析過,那么就會(huì)緩存下來弟跑,進(jìn)入到其他頁面再次使用時(shí)灾前,此 bug 就會(huì)復(fù)現(xiàn)。
問題原因找到了孟辑,趕緊修復(fù)吧哎甲。等等蔫敲,筆者發(fā)現(xiàn)按照上面提到的復(fù)現(xiàn)路徑,在 Android 6.0 以上系統(tǒng)上并沒有出現(xiàn)這個(gè) bug炭玫,這是怎么回事呢奈嘿?
查看 ImageView 的源碼發(fā)現(xiàn),Android 6.0 以上系統(tǒng)里吞加,ImageView 中 mDrawableWidth 和 mDrawableHeight 多了一個(gè)賦值時(shí)機(jī)裙犹,而這個(gè)時(shí)機(jī)是在 Android 5.x 系統(tǒng)里沒有的。
對(duì)比 Android 6.0+ 和 Android 5.x 的 ImageView 的 invalidateDrawable() 可知榴鼎,Android 6.0+ 系統(tǒng)上會(huì)根據(jù)獲取到的 drawable 大小來更新mDrawableWidth伯诬、mDrawableHeight 的值。
這就不難解釋為什么 Android 6.0 以上系統(tǒng)不會(huì)出現(xiàn)這個(gè) bug 了巫财。LottieDrawable 繪制時(shí)調(diào)用自己的 invalidateSelf()盗似,invalidateSelf() 方法會(huì)調(diào)用到 ImageView 的 invalidateDrawable(),此時(shí) scale 值已經(jīng)設(shè)置完畢平项,就可以保證獲取到的 LottieDrawable 的大小是正確的了赫舒。
根據(jù)以上分析,LottieAnimationView 縮放無效的 bug 出現(xiàn)在 Android 5.x 系統(tǒng)上闽瓢,由于縮放參數(shù) scale 設(shè)置時(shí)機(jī)在動(dòng)畫解析之后接癌,所以緩存中有動(dòng)畫資源時(shí),還沒等到 scale 設(shè)置好扣讼,就直接獲取 drawable 的大小作為 LottieAnimationView onMeasure() 參考的大小了缺猛。
解決問題
問題原因找到了,就好辦了椭符。
首先考慮是否可以修正 lottie 的使用姿勢來避免這個(gè)問題呢荔燎?比如在 java 代碼中通過 setAnimation() 來設(shè)置動(dòng)畫文件,而不是在 xml 布局文件中設(shè)置销钝。這樣就能夠確保 setAnimation() 的調(diào)用在設(shè)置 scale 之后了有咨。這種方式理論上是可以解決問題的,但是實(shí)際操作上是不可行的蒸健。因?yàn)檫@樣做相當(dāng)于設(shè)定了一個(gè)使用 lottie 開發(fā)的規(guī)則座享,削減了 lottie 開發(fā)的便利性的同時(shí),讓團(tuán)隊(duì)每個(gè)人都遵守起來成本很高似忧,而且難以保證不會(huì)出錯(cuò)渣叛。
那么就只能通過修改 lottie 源碼來解決了。這樣做也是合理的盯捌,因?yàn)楂@取 drawable 尺寸時(shí)依賴于 scale 的值诗箍,邏輯上,此時(shí) scale 值必須設(shè)置完畢才行。因此滤祖,我們將 LottieAnimationView 的 init() 方法修改了一下筷狼,將 scale 值的設(shè)置提前。