Android Lottie 踩坑之 LottieAnimationView 缩放无效

Lottie 作为一个可以轻松实现复杂动画的跨平台开源动画库,从发布至今,受到了越来越多开发者的推崇。笔者所在项目从今年 8 月开始接入 lottie 库,使用的是 2.0.0 的版本。接入初期,在享受 lottie 带来的便利和高效同时,有时会遇到动画效果和预期不符的情况,在修正了使用方法后,问题大多都能解决。最近又遇到了一个问题,解决起来花费了些精力,记录下来。

问题描述

项目中有个“点赞”的动效是用 lottie 实现的,而展示效果出现了偶现的 bug,LottieAnimationView 显示过大:

"点赞" LottieAnimationView 显示过大

开启布局边界可以清楚的看到“点赞” icon 显示大小确实过大了。该 icon 的布局代码:

布局指定了 LottieAnimationView 的动画资源路径、loop 和 scale 属性,宽、高为 wrap_content。

这里 scale 设置为 0.33 需要解释一下。我们知道 lottie 的 JSON 格式的动画资源文件中是有动画的宽、高属性的,并且 lottie-android 库将 JSON 中的宽、高的单位视为 dp,也就是动画的实际显示大小是 JSON 文件中设定的大小乘以手机系统的 density 值。那么在 wrap_content 下,为了使动画显示大小在数值上和 JSON 中设定的一致,就需要指定一个 scale 值,scale 值大小为 density 的倒数,以 density = 3 为例,这里 scale 即为 0.33。

但是从这个 bug 来看,动画 LottieAnimationView 并没有按照预期的 scale 缩放,显示的大小是未缩放的大小。

分析问题

View 大小显示不正常,应该是 measure 过程出了问题。LottieAnimationView 自己没有 onMeasure() 方法,于是查看一下其父类 ImageView 的 onMeasure() 方法。出现 bug 的手机的系统是 Android 5.1 系统,查看 Android 5.1 的 ImageView 源码可以看到,onMeasure() 处理时是基于 mDrawableWidth 和 mDrawableHeight 来确定最终的大小,因此猜测这两个变量的赋值出现了问题。 mDrawableWidth 和 mDrawableHeight 的赋值在 updateDrawable 方法中,如下所示。

LottieDrawable 重写了 getIntrinsicWidth() 和 getIntrinsicHeight() 方法

可以看到返回的结果已经考虑 scale 值了。如果返回值不符合预期,那么一定是 scale 值不正确,这里先留着,后面继续分析。

从 updateDrawable() 方法开始向上层层追踪调用,可以找到调用 updateDrawable() 方法的调用链:


updateDrawable() 方法的调用关系

由此可知,LottieAnimationView 在解析动画文件成 LottieComposition 后,setComposition() 时会调用到 updateDrawable() 来获取 drawable 的大小,进而确定自身的大小。

由于我们把动画资源写到了 xml 布局文件中,所以 LottieComposition 的解析时机是在 LottieAnimationView 的 init() 方法中。

setAnimation() 方法首先在缓存池中查找是否存在解析好的相同文件名的动画文件,如果存在直接调用 setComposition() 使用;如果不存在,则启动 loader 加载、解析动画文件,在回调函数中调用 setComposition(),相关代码如下:

到这里,代码逻辑已经梳理得比较清晰了:在 LottieAnimationView 中解析动画文件成 LottieComposition,然后调用 setComposition() 保存、处理 LottieComposition,最终调用到 ImageView 的 updateDrawable() 方法,获取 LottieDrawable 的尺寸,反映到 LottieAnimationView 上。

这个流程看起来没什么问题。但是从 bug 上来看,最终获取的 LottieDrawable 尺寸是错误的。根据前面的结论,此时应该是 scale 值不正确了。继续看一下 scale 设置的时机。

从上述 LottieAnimationView 的 init() 方法中看到,scale 值从 xml 布局文件中解析得来,数据肯定不会有错误。但是设置的时机是在 setAnimation() 之后。这会有什么问题呢?

当然有问题了,setAnimation() 时如果缓存中有解析好的动画资源,那么就会直接获取使用,继续执行到 ImageView.updateDrawable(),此时 scale 值还未设置,初始为 1f,所以获取到的 LottieDrawable 大小就是未缩放的大小了,LottieAnimationView 的大小也就偏大了。

那本文开头提到的 bug 原因是这样的吗?答案是肯定的。这个“点赞”动画资源在其他的业务场景中也有使用,并且,由于是在列表中使用,因此做了强缓存设置。所以只要这个“点赞”资源加载、解析过,那么就会缓存下来,进入到其他页面再次使用时,此 bug 就会复现。

问题原因找到了,赶紧修复吧。等等,笔者发现按照上面提到的复现路径,在 Android 6.0 以上系统上并没有出现这个 bug,这是怎么回事呢?

查看 ImageView 的源码发现,Android 6.0 以上系统里,ImageView 中 mDrawableWidth 和 mDrawableHeight 多了一个赋值时机,而这个时机是在 Android 5.x 系统里没有的。

对比 Android 6.0+ 和 Android 5.x 的 ImageView 的 invalidateDrawable() 可知,Android 6.0+ 系统上会根据获取到的 drawable 大小来更新mDrawableWidth、mDrawableHeight 的值。

这就不难解释为什么 Android 6.0 以上系统不会出现这个 bug 了。LottieDrawable 绘制时调用自己的 invalidateSelf(),invalidateSelf() 方法会调用到 ImageView 的 invalidateDrawable(),此时 scale 值已经设置完毕,就可以保证获取到的 LottieDrawable 的大小是正确的了。

根据以上分析,LottieAnimationView 缩放无效的 bug 出现在 Android 5.x 系统上,由于缩放参数 scale 设置时机在动画解析之后,所以缓存中有动画资源时,还没等到 scale 设置好,就直接获取 drawable 的大小作为 LottieAnimationView onMeasure() 参考的大小了。

解决问题

问题原因找到了,就好办了。

首先考虑是否可以修正 lottie 的使用姿势来避免这个问题呢?比如在 java 代码中通过 setAnimation() 来设置动画文件,而不是在 xml 布局文件中设置。这样就能够确保 setAnimation() 的调用在设置 scale 之后了。这种方式理论上是可以解决问题的,但是实际操作上是不可行的。因为这样做相当于设定了一个使用 lottie 开发的规则,削减了 lottie 开发的便利性的同时,让团队每个人都遵守起来成本很高,而且难以保证不会出错。

那么就只能通过修改 lottie 源码来解决了。这样做也是合理的,因为获取 drawable 尺寸时依赖于 scale 的值,逻辑上,此时 scale 值必须设置完毕才行。因此,我们将 LottieAnimationView 的 init() 方法修改了一下,将 scale 值的设置提前。

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 204,732评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 87,496评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 151,264评论 0 338
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,807评论 1 277
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,806评论 5 368
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,675评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,029评论 3 399
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,683评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 41,704评论 1 299
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,666评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,773评论 1 332
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,413评论 4 321
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,016评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,978评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,204评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,083评论 2 350
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,503评论 2 343

推荐阅读更多精彩内容