Lottie-Android 项目地址:airbnb/lottie-android
Lottie 简介
Lottie 是一个用于解析 Adobe After Effects 使用 Bodymovin 插件导出的动画 json 文件,并在移动端进行渲染的类库。
简而言之,设计师可以使用 After Effects 制作动画,经由 Lottie 便可以很简单的在移动端渲染,而无须工程师进行大量的手动实现设计师动画的工作。俗话说“一图胜千言”,下面的动画均由 Lottie 渲染(图片源自 Lottie 文档):
Lottie-Android 的使用
首先,在 build.gradle 文件中引入 Lottie-Android 的依赖:
dependencies {
...
compile 'com.airbnb.android:lottie:2.5.0-rc1'
...
}
复制代码
Lottie 支持 ICS(API 14)及以上。使用 Lottie 最简单的方法是通过 LottieAnimationView
:
<com.airbnb.lottie.LottieAnimationView
android:id="@+id/animation_view"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
app:lottie_fileName="hello-world.json"
app:lottie_loop="true"
app:lottie_autoPlay="true" />
复制代码
此外,还可以在 Java 代码中加载 Lottie:
LottieAnimationView animationView = (LottieAnimationView) findViewById(R.id.animation_view);
animationView.setAnimation("hello-world.json");
animationView.loop(true);
animationView.playAnimation();
复制代码
LottieAnimationView
内部通过 LottieDrawable
来渲染动画,如有需要,开发者也可以直接使用该 drawable:
LottieDrawable drawable = new LottieDrawable();
LottieComposition.Factory.fromAssetFileName(getContext(), "hello-world.json", (composition) -> {
drawable.setComposition(composition);
});
复制代码
Lottie-Android 源码分析
Lottie-Android 的工作流程大致可以分为两个阶段:
- 动画文件的加载
- 动画的渲染
下面逐一对 Lottie-Android 中上述过程的源码进行分析。
动画文件的加载
使用 Bodymovin 插件,设计师可以将 After Effects 的动画以 json 的形式导出。在 Lottie-Android 中,该 json 文件对应的 Java Model 即 LottieComposition
类。类 LottieComposition.Factory
提供了多个静态方法用于从不同途径加载 json 数据并将其解析为 LottieComposition
。
这些静态方法中,以 “Sync” 结尾的方法将在调用线程以同步的方式加载并解析 json 数据;其余方法则通过 Lottie 自定义的 AsyncTask
子类 AsyncCompositionLoader
异步地加载并解析 json 数据。
无论是以异步还是同步的方式,Lottie 均使用 JsonReader
来解析 json 数据。使用 JsonReader
可以从流中解析 json 数据,从而避免了因一次性将全部 json 数据加载到内存之中而带来的 OOM 问题。
实际执行 json 解析的是 LottieCompositionParser
的 parse
方法。该方法中将 json 中的字段一一解析为 LottieComposition
的成员变量,进而构造了一个 LottieComposition
实例,逻辑较为简单,本文不予赘述。想要理解动画 json 数据中字段的含义的读者,可以自行查看 LottieCompositionParser
类的源码。
Lottie 动画渲染
图层
要了解 Lottie 动画渲染,首先要理解 After Effects 中的一个重要概念:Layer(图层)。
相信使用过 Adobe Photoshop 的读者对于图层的概念并不陌生(如果你使用过 After Effects 的话可以跳过这一部分)。在 After Effects 中,图层的概念 与 Photoshop 中的图层概念类似:图层相当于对于动画整体的图像进行了更细粒度的区分,将不同类型的元素进行了拆分,不同的形状、纯色、文本等元素分别位于不同的图层中,所有图层依序叠加在一起构成了所渲染的图像。在未与其它图层关联时,修改某一图层的属性不会影响其它图层,这样在执行动画的时候便可以逻辑更加明晰地对图像中的各个元素分别执行不同的动画逻辑。
Lottie 中,图层的概念被抽象为抽象类 BaseLayer
,BaseLayer
共有 6 个子类:
ShapeLayer
CompositionLayer
SolidLayer
ImageLayer
NullLayer
TextLayer
它们与 After Effects 中的图层的对应关系为:
ShapeLayer
:形状图层CompositionLayer
:预合成图层SolidLayer
:纯色图层ImageLayer
:图片素材图层NullLayer
:空图层TextLayer
:文本图层
上述图层种类(除去空图层)在 After Effects 用户指南 《图层概述》 一文中均有提及。
除去预合成图层,其余图层的含义都显而易见。“预合成”一词是 After Effect 中的概念,Adobe 在 After Effects 用户指南 《关于预合成和嵌套》 一文中对“预合成”的概念进行了阐述:
如果要对合成中已存在的某些图层进行分组,可以预合成 这些图层。预合成图层会将这些图层放置在新合成中,这将替换原始合成中的图层。新的嵌套合成将成为原始合成中单个图层的源。
文中的合成一词是 After Effects 中的基本概念,即时间轴、图层及其空间与时间上的对应关系信息的集合。而预合成图层可以认为是合成的子集,将一个合成进行更为细粒度的划分,对图层进一步进行了分组以方便组织项目。而在 Lottie 中,CompositionLayer
便是一个可以包含多个 Layer 的 Layer(可以将 CompositionLayer
与 Layer 的关系类比为 ViewGroup
与 View
的关系)。
Lottie 的绘制
正如 Lottie 文档中所述,LottieAnimationView
使用 LottieDrawable
来渲染动画,动画的实际执行者是 LottieDrawable
。
通过调用 LottieDrawable
的 setComposition
方法,可以将 LottieDrawable
与动画数据 Model LottieComposition
进行绑定。
在 setComposition
方法中,LottieDrawable
依据传入的 LottieComposition
构建了一个新的 CompositionLayer
作为整个动画的根图层,并且根据 animator
设置了当前动画的进度。
public boolean setComposition(LottieComposition composition) {
...
buildCompositionLayer();
animator.setComposition(composition);
setProgress(animator.getAnimatedFraction());
...
}
复制代码
成员变量 animator
是 ValueAnimator
的子类 LottieValueAnimator
的实例,借助 animator
,LottieDrawable
得以控制整个动画的更新与进度。
animator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
@Override public void onAnimationUpdate(ValueAnimator animation) {
if (compositionLayer != null) {
compositionLayer.setProgress(animator.getAnimatedValueAbsolute());
}
}
});
复制代码
animator
的 getAnimatedValueAbsolute
方法返回了当前动画的进度(该值为 float
型,取值范围是[0,1] ,与动画的速度、方向等均无关),将该值传给根图层 compositionLayer
,接着,类似于 Android 中 View Tree 的绘制,根图层 compositionLayer
依序逐层地将动画进度传递给子图层并将其一一绘制。
@Override void drawLayer(Canvas canvas, Matrix parentMatrix, int parentAlpha) {
L.beginSection("CompositionLayer#draw");
canvas.save();
...
for (int i = layers.size() - 1; i >= 0 ; i--) {
boolean nonEmptyClip = true;
if (!newClipRect.isEmpty()) {
nonEmptyClip = canvas.clipRect(newClipRect);
}
if (nonEmptyClip) {
BaseLayer layer = layers.get(i);
layer.draw(canvas, parentMatrix, parentAlpha);
}
}
canvas.restore();
L.endSection("CompositionLayer#draw");
...
}
复制代码
而不同类型图层的绘制,便是各个图层依据对应的数据 Model 以及当前的动画进度,使用 canvas
进行绘图的过程了。对于这一过程,本文不再赘述,感兴趣的读者可以根据上文列出的图层类型查看不同种类图层的绘制实现(drawLayer
方法)。
至此,动画便在 LottieDrawable
中渲染了出来。