背景
我是一名Android开发者,以Android视角讲解。Lottie这玩意,我不记得几年前玩过,当时还是1.x版本,自己还通过AE鼓捣了很多好玩的动画,为此沾沾自喜,这可能就是学习的快感吧!最近项目需使用Lottie配合设计师实现各种复杂动画而再次被捡起。值得一提是Lottie的idea真的很棒,使用简单,是一个非常优秀的动画库。接下来大家跟我一起揭开她神秘的面纱。
Lottie
简介
Lottie是Airbnb开源的一个适用于Android、iOS、Web和Windows的动画库,解析Adobe After Effects(简称AE)通过Bodymovin导出的json,并以原生态在移动设备和Web上渲染。
百度翻译是真的不靠谱,还要我这个英语渣渣二次翻译。AE是什么?Bodymovin是什么?这个你不需要了解,让你用Lottie动画的设计师肯定懂 。什么,你主动提出来的?这都不知道,你提个毛线啊。
OK,现在就带你走进Android世界里的Lottie。
优势
为什么要用Lottie?传统的动画不香吗?传统动画大致分为帧动画、补间动画、属性动画和组合动画,甚至是gif。
现在我们就来吐槽一下这些动画,都2020年,应该很少人再用补间动画了吧,直接略过;如果是简单的动画属性动画是一个非常不错的选择,但复杂起来,不得不通过属性动画组合实现,而这套动画代码维护起来是非常操蛋的;如果直接使用gif,除开省事外,体验很差不说,占用的大内存和资源使用和回收时机是件让人头疼的事情,稍不小心就会引起内存溢出和卡顿,还有让Android开发大军跪下的分辨率适配工作;最后的帧动画如gif一样糟糕,gif其实就是帧动画。
再来看看我们今天的主角Lottie,Lottie只要一个json文件,文件可以来自网络,也可以来自本地,一份文件可以同时运行在Android、iOS、Web和Windows,实打实的跨平台,极大降低开发成本;几乎不需要额外的适配工作;占用内存小;可以控制动画速度和动态修改动画属性,用过的都说好,真的是碉堡了。
配置
Lottie在Android上支持的最低版本是16,其中AndroidX版本2.8.0及以上,我现在的项目还在使用support,只能用2.8.0以下的,故以下的讲解内容都是基于2.7.0版本,无论是哪个版本,原理性的内容是不会变的。
1 | // build.gradle依赖对应的版本就可以玩起来了 |
核心类
结构真的简单,核心三个类。
- LottieAnimationView:展示动画的View
- LottieDrawable: 展示动画的Drawable
- LottieComposition:动画资源管理者
使用
使用真的简单。
加载
加载大致分为这么几个场景:
本地Assets目录存放json/zip
1 | #LottieAnimationView |
远程json/zip
1 | #LottieAnimationView |
这是最常用的两种场景,但是作为高手,肯定不会用这两个方法,而是组合。Lottie提供这样的方法:
1 | #LottieAnimationView |
也就是说只要拿到LottieComposition,动画就可以跑起来。LottieComposition产生的唯一渠道,虽然有好多分支,但最终都到一个方法:
1 | #LottieCompositionFactory |
由方法名可知,方法是同步的,放在主线程肯定不行,必须自己实现一套异步加载,优化的时候必须安排各种缓存策略,总不能每次都io操作吧?这谁顶得住啊。
资深老Android肯定早就猜到Lottie自身已经提供,不然常见加载岂不是都重新加载io,别人可不这么low,当然你也可以自己实现一套加载机制(手动狗头)。
1 | #LottieCompositionFactory |
由代码推导,LottieTask内部线程池启动线程通过前面介绍方法加载LottieComposition,通过LottieListener回调。
基础功能
1 | public class LottieDrawable extends Drawable implements Drawable.Callback, Animatable |
LottieDrawable实现了Animatable,同时提供了动画常见的系列方法:
- addAnimatorUpdateListener:进度监听
- addAnimatorListener:周期监听
- playAnimation:开始动画
- endAnimation:结束动画
- resumeAnimation:恢复动画
- pauseAnimation:暂停动画
- cancelAnimation:取消动画
- setProgress:设置进度
是不是没有看到最常见的setDuration?这个也很好解释,Lottie本身是执行json文件,json本身包括一段动画,起始到结束,当时不需要设置什么时长。那我偏要设置时长呢?虽是有固定时长,说到底还是动画,必然是按帧执行,那么我通过addAnimatorUpdateListener和setProgress,当然方法执行的对象并不是同一个,相信聪明的你已经懂了。
修改属性
Lottie虽支持属性修改,但修改属性有限,可能有些属性一脸懵逼,是AE中名词,设计师肯定懂,讨论动画时,把词抛出来就行了。
- Transform:变换
- TRANSFORM_ANCHOR_POINT:锚点
- TRANSFORM_POSITION:位置
- TRANSFORM_OPACITY:透明度
- TRANSFORM_SCALE:缩放
- TRANSFORM_ROTATION:旋转
- Fill:填充
- COLOR:颜色,不支持渐变
- STROKE_WIDTH:线宽
- OPACITY:透明度
- COLOR_FILTER:滤镜
- Ellipse:椭圆
- POSITION:位置
- ELLIPSE_SIZE:大小
- Polystar:多边形
- POLYSTAR_POINTS:点的集合
- POLYSTAR_ROTATION:旋转角度
- POSITION:位置
- POLYSTAR_OUTER_RADIUS:外圆角
- POLYSTAR_OUTER_ROUNDEDNESS:外圆角度
- POLYSTAR_INNER_RADIUS:内圆角,用于星形
- POLYSTAR_INNER_ROUNDEDNESS:内圆角度,用于星形
- Repeater:类似镜像,复制
- REPEATER_COPIES:复制
- REPEATER_OFFSET:偏移
- TRANSFORM_ROTATION:旋转
- TRANSFORM_START_OPACITY:开始透明度
- TRANSFORM_END_OPACITY:结束透明度
- Layers:图层
- 包含Transform所有属性
- TIME_REMAP:时间重置
列了这么多,那么代码怎么写呢?
1 | #LottieAnimationView |
修改资源
除了修改属性,还可以修改资源,或者说用自己项目的工具加载资源,Lottie提供了各种各样的代理。
图片
图片是最常用的代理,可以通过LottieImageAsset提供的属性映射创建对应bitmap,不管是本地还是远程,甚至还可以做一下压缩。
1 | mPetActionCore.setImageAssetDelegate(new ImageAssetDelegate() { |
字体
1 | mPetActionCore.setFontAssetDelegate(new FontAssetDelegate(){ |
文本
1 | mPetActionCore.setTextDelegate(new TextDelegate(mPetActionCore) { |
性能
无论玩什么,这都是无法逃避的话题。除了Android日常开发所使用的性能检测工具外,Lottie还给咱们提供建议和工具。
Lottie建议尽量不要在动画中加入遮罩/蒙版,这会大大降低性能,如果非要使用,最好开启硬件加速。说到这,顺便提一下,目前Lottie支持AE中的哪些特性呢?点击这里。
Lottie还给咱们提供了渲染性能检测工具。
1 | // 开启性能检测,最好判断环境,例如在测试环境才开启 |
注意点
Lottie本身已经做的非常优秀了,如果真出了什么问题,也是无能为力的事情,只能放弃动画或者换动画,甚至放弃通过Lottie加载,不过咱们还是可以对一些小细节做优化的。
- 最常见的是首次执行丢帧。这个别无它法,资源就这么多,大家都在抢占,必然会引起卡顿或者丢帧。要么你把引起Lottie丢帧的罪魁祸首找出来,要么你就作延迟 ,一定时间后再执行。一般会选择后者,中间空档期会通过首帧占位图掩盖过去,首帧图自己想办法。
- 还有一个比较常见的就是动画素材本身有问题,不是设计师导出的文件有问题就是动画采用了Lottie不支持的特性,直接找设计师就完事了,常见的有元素丢失,空档期,效果与预览差异,元素位置偏移等等。
- 在动画的高频使用中可能会出现闪一下消失的情况,这往往是Lottie在加载资源时候,做了动画操作,所以维护好周期和状态是非常费脑的事情。
- 动画元素可能在执行的时候出现乱码情况,一般是多套json之间切换切位图存放目录不一样导致的,处理这种情况可在切换的时候通过setImageAssetDelegate来重新设置图片获取方式。
1 | Log.w(VIEW_LOG_TAG, getClass().getSimpleName() + " not displayed because it is" |
- 出现上面这种情况一般是图片太大了,超出了规范的大小,有两种做法,一种做法是减小图片尺寸,还有一种做法是通过硬件加速绕过检测。好奇为什么做的宝宝可以去看源码,这属于原生的范畴,不止是lottie。
像这种涉及资源使用的玩意,必然逃不掉RecyclerView的禁忌,尽量不要用,不然你会疯了的。非要作死?那你必须好好思考资源的使用和回收,动画的执行和取消时机,看似简单,其实要命。
源码解析
其实对于Lottie来说,只要知道上面的内容就行了,源码理解的意义并不大,如果你是跟我一样是个有追求的程序员,那么咱们继续。
我分析源码还是喜欢从一个简单例子切入,整理各个分支流程,最后总结整个流程。当然知道结果再去看源码心境会不一样,这不是屁话么?我知道答案去考试和不知道答案去考试,这心境能一样吗?哪个更深刻呢?你应该知道答案了。
加载
1 | lottieAnimationView.setAnimation("xxx.json"); |
最简单的示例。xxx.json是assets目录下文件,OK,就从这入手。
一个多年开发经验的同学肯定立马判断setAnimation方法是初始化资源的。How?How?How?
1 | #LottieAnimationView |
从命名以及结构上大致判断,LottieTask是个资源加载任务,addListener和addFailureListener分别回调资源加载成功和失败。
要论证自己的猜想正确与否还是得从LottieTask的实际结构和创建流程出发。
1 | #LottieCompositionFactory |
从上面看lottie在任务管理做的不错,so,没必要自己再建一套。LottieTask创建方式我们已经很清楚,下一步是结构和任务执行。
1 | #LottieTask |
正常情况下,task执行完就宣布任务结束,产生结果,但这里不一样,task是通过外部传入,需要一个检测线程不断去检测是否已经执行完毕,选用FutureTask不就是这个目的吗?而下面的startTaskObserverIfNeeded检测的开始。
1 | #LottieTask |
整个资源的加载还是很巧妙的,值得学习。接下来介绍资源到底如何解析的。
解析
1 | #LottieCompositionFactory |
上面一系列信息的管家是Composition。而拥有这些信息就可以轻松实现复杂的动画。
设置
1 | #LottieAnimationView |
万事俱备,只欠渲染。
渲染
很显然我们直接跟踪lottieDrawable的渲染逻辑即可。
1 | #LottieDrawable |
感慨
整个源码分析到这就结束了,省略了很多烦杂的源码,主要是解析那部分,我们只分析了主架构的解析。其本身是没有意义的,什么时候你必须去研究研究呢?那就是出问题的时候!设计师只负责效果,发现根本跑不起来,这时候就到你装逼的时候了,前提是你也得略懂AE知识。
结束语
今天讲解的Lottie圆满结束,完结撒花。能从头看到这的同学我也挺佩服的,而那些直接拉到这的我只能嗤之以鼻。本篇文章对lottie知识点算是罗列的非常详细了,当你忘了就过来看看。当然,你放心,我不会根据lottie升级而调整的。我想核心点是不会变的,机制说改就改的,rubbish!!!