Lottie动画库 Android 端源码浅析

惊艳的Lottie

前段时间airbnb开源的动画库Lottie得到了不错的反响,旨在解决Android、IOS、RN 上面开发动画成本高、表现不一致的问题,可以说降低了三端动画的开发成本。

项目地址:https://github.com/airbnb/lottie-android

先上几个git上的效果:

demo1
demo2

如果需要在3端都分别完成这些动画,可能就需要折磨设计&开发同学了。人肉写出这些效果简直是处女座也无法完成的一件事。

Lottie在这件事上就是来拯救移动开发程序员的。

思路

Lottie借助AE生成动画,再利用AE插件bodymovin来导出可描述的json文件,而Lottie负责在不同端上解析json文件完成动画的绘制。

从设计思路上可以看出,这确实是一个很好的解法,Lottie抹平了各端的差异性,通过统一描述(JSON)来表述动画。这看上去和近年来很火的Weex思路一致。

作为一个Android搬砖程序员,接下来的篇幅主要以Lottie Android SDK(ios也不会呀~~~)来分析一下Lottie的解法。

用法

先来感受一下用法,感觉还是很清爽的:

Dependency:

compile'com.airbnb.android:lottie:1.5.3'

使用lottie_fileName="hello-world.json"来指定动画路径,也可以使用:setAnimation("hello-world.json")来指定。


xml usage


java usage

然后你就可以像使用普通动画对待Lottie了。

原理浅析

绘制

我们先从能接触到的类com.airbnb.lottie.LottieAnimationView看起,所有的逻辑处理、json解析、绘制工作都在这里完成。

LottieAnimationView

可以看到这个是AppCompatImageView,里面有个很重要的变量:

LottieDrawable lottieDrawable;

LottieAnimationView在初始化时会调用setImageDrawable(lottieDrawable);方法,使用LottieDrawable作为ImageDrawable。并且同时重写invalidateDrawable方法,当需要invalidate时通知lottieDrawable。

invalidateDrawable

LottieDrawable

继承关系

从类结构看,继承自AnimatableLayer,里面实现了Drawable需要实现的方法,主要是public void draw(@NonNull Canvas canvas)用来实现Drawable的绘制工作。

我们先来看看父类AnimatableLayer定义了什么行为。

首先当然是来看最重要的:draw


draw

可以看到,在绘制前,调用applyTransformForLayer对画布canvas进行了transform操作。接着绘制background、layers。

而layers的定义是:final List layers = new ArrayList<>();每一层,都是一个AnimatableLayer对象。

我们来看看AnimatableLayer的子类。

我们目前大概可以分析出,Lottie 根据JSON 构造了一个树形结构,root节点是LottieDrawable,将绘制的元素添加到了LottieDrawable.layers中。递归的描述每一个元素。

我们接着看“root”:LottieDrawable都做了些什么。

让Drawable“动起来”

由于Drawable本身只是负责绘制工作,并不会像动画一样,有start、end、duration等属性。因此在LottieDrawable引入了一个简单的animator = ValueAnimator.ofFloat(0f, 1f)来解决这个问题。

所有对动画的操作(repeat、start),都是对这个ValueAnimator元素的操作。

setProgerss

从上面可以看到,调用父类AnimatableLayer的setProgerss会通知animations、layers。之前我们已经了解过layers,那么animations是什么呢?来看定义:

private final List> animations = new ArrayList<>();

BaseKeyframeAnimation

BaseKeyframeAnimation从字面意思大概是用来描述关键帧的动画。

做过动画的朋友大概清楚“关键帧”的概念。BaseKeyframeAnimation的意义就是存放了一个动画中所有的关键帧,以及每一帧的过度进度。

listeners可以注册对动画的监听,当外界调用setProgerss时,会通知给监听者。

keyframes存放了当前动画所有关键帧。

最终对外的方法是:

abstract A getValue(Keyframekeyframe, float keyframeProgress);

从入参看很清晰,传入了当前所在的关键帧,以及帧进度。

KeyFrame

KeyFrame是对每一帧的描述,字段如下:

LottieComposition是当前帧的所有数据,我们后面会讲到。

并且提供了静态工厂方法来生成一个KeyFrame。将JSON文件解析成一个KeyFrame对象。

setTransform

前面提到的animations是在何时注册的呢?

我们在AnimatableLayer.setTransform方法中看到了注册方法。这个方法在root节点不会被调用,但是在具体的Layer(如EllipseLayer、ShapeLayerView)中会使用。具体如下:


可以看到,在addAnimation后以及 注册的updatelistener中调用了invalidateSelf()方法。而invalidateSelf()方法会触发draw流程。draw会递归的调用layers进行绘制。由于每一个layer都拥有当前的 progress,因此就可以正确的绘制出来啦。

JSON 解析

解析工作在调用animationView.setAnimation后,就开始了。

JSON 解析 会用InputStream异步读取json内容,经过处理后封装成LottieComposition对象。

当加载完成后,会回调animationView.setComposition方法。animationView会接着调用LottieDrawable.setComposition。


buildLayersForComposition

这里完成了前面提到的layers的构建:

里面做了一次转换,构造了一个LayerView对象,这个是关键所在。

LayerView

LayerView也是继承自AnimatableLayer。 完成了所有Layer类型的转换。

在初始化方式中会调用:setupForModel();方法。


摘取了setupShapeLayer的代码片段,里面会根据不同的数据类型,生成不同的LayerView,这些细节就不再做细究了。

总结

为了实现基本的动画能力,Lottie做了这么些事情。

1. 绘制:Lottie 使用 ImageView.Drawable来处理动画。使用统一Canvas来绘制。

2.动画控制:借助一个ValueAnimator来控制动画的进度,通过一系列监听器来通知Drawable进行绘制(draw)。

3.资源转换:将JSON文件经过处理得到LottieComposition对象,包括了每一层动画对象、资源(images)等信息。

写在最后

首先给Lottie的工程师们一个star。

本文只是对Lottie的主要逻辑进行了梳理,没有梳理动画格式之类,里面比较复杂的的部分是不同的Layer的绘制、KeyFrame的计算。这部分由于对AE以及bodymovin不太了解就没有深究了。旨在掌握Lottie的大体设计。如有错误之处,请指出。

最后吐槽一波Lottie的代码结构,虽然源码不多,但是这个一层结构。。。。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 143,639评论 1 302
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 61,591评论 1 258
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 95,050评论 0 213
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 41,169评论 0 180
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 48,976评论 1 258
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 38,876评论 1 178
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 30,476评论 2 273
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 29,229评论 0 167
  • 想象着我的养父在大火中拼命挣扎,窒息,最后皮肤化为焦炭。我心中就已经是抑制不住地欢快,这就叫做以其人之道,还治其人...
    爱写小说的胖达阅读 29,095评论 6 234
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 32,579评论 0 213
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 29,356评论 2 215
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 30,698评论 1 232
  • 白月光回国,霸总把我这个替身辞退。还一脸阴沉的警告我。[不要出现在思思面前, 不然我有一百种方法让你生不如死。]我...
    爱写小说的胖达阅读 24,272评论 0 32
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 27,168评论 2 214
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 31,605评论 3 210
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 25,645评论 0 9
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,035评论 0 166
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 33,609评论 2 232
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 33,698评论 2 233

推荐阅读更多精彩内容