孤舟蓑笠翁,独钓寒江雪

开源项目- Lottie 源码分析

Lottie的基本用法其实还是非常简单的,不熟悉的同学请阅读我的博客开源项目-Lottie简介。接下来我们就从源码角度分析一下这么强大的功能是怎么实现的。

实现思路

Lottie使用json文件来作为动画数据源,然后把解析这些数据源出来,建立数据到对象的映射关系,根据里面的数据建立合适的Drawable绘制到View上面。

源码分析

下面我们就从LottieAnimationView作为切入点来一步一步分析。

LottieAnimationView

LottieAnimationView继承自AppCompatImageView,封装了一些动画的操作:

1
2
3
4
5
6
7
public void playAnimation()
public void cancelAnimation()
public void pauseAnimation()
public void setProgress(@FloatRange(from = 0f, to = 1f)
public float getProgress()
public long getDuration()
public boolean isAnimating()

等等;
LottieAnimationView有两个很重要的成员变量:

1
2
@Nullable private LottieComposition.Cancellable compositionLoader;
private final LottieDrawable lottieDrawable = new LottieDrawable();

LottieCompositionLottieDrawable将会在下面专门进行分析,他们分别进行了两个重要的工作:json文件的解析和动画的绘制。
compositionLoader进行了动画解析工作,得到LottieComposition
我们看到的动画便是在LottieDrawable上面绘制出来的,lottieDrawablesetComposition方法中被添加到LottieAnimationView上面最终显示出来。

1
setImageDrawable(lottieDrawable);

解析JSON文件

JSON文件

其实在 Bodymovin 插件这里也是比较神奇的,它是怎么生成json文件的呢?这个后面有时间再研究。解析出来的json文件是这样子的:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
{
"assets": [

],
"layers": [
{
"ddd": 0,
"ind": 0,
"ty": 1,
"nm": "MASTER",
"ks": {
"o": {
"k": 0
},
"r": {
"k": 0
},
"p": {
"k": [
164.457,
140.822,
0
]
},
"a": {
"k": [
60,
60,
0
]
},
"s": {
"k": [
100,
100,
100
]
}
},
"ao": 0,
"sw": 120,
"sh": 120,
"sc": "#ffffff",
"ip": 12,
"op": 179,
"st": 0,
"bm": 0,
"sr": 1
},
……
],
"v": "4.4.26",
"ddd": 0,
"ip": 0,
"op": 179,
"fr": 30,
"w": 325,
"h": 202
}

重要的数据都在layers里面,后面会介绍。

LottieComposition

Lottie使用LottieComposition来作为存储json文件的对象,即把json文件映射到LottieCompositionLottieComposition中提供了解析json文件的几个静态方法:

1
2
3
4
5
6
public static Cancellable fromAssetFileName(Context context, String fileName, OnCompositionLoadedListener loadedListener);
public static Cancellable fromInputStream(Context context, InputStream stream, OnCompositionLoadedListener loadedListener);
public static LottieComposition fromFileSync(Context context, String fileName);
public static Cancellable fromJson(Resources res, JSONObject json, OnCompositionLoadedListener loadedListener);
public static LottieComposition fromInputStream(Resources res, InputStream file);
public static LottieComposition fromJsonSync(Resources res, JSONObject json);

其实上面这些函数最终的解析工作是在public static LottieComposition fromJsonSync(Resources res, JSONObject json)里面进行的。进行了动画几个属性的解析以及Layer解析。
下面看一下LottieComposition里面的几个变量:

1
2
private final LongSparseArray<Layer> layerMap = new LongSparseArray<>();
private final List<Layer> layers = new ArrayList<>();

layers存储json文件中的layers数组里面的数据,Layer就是对应了做图中图层的概念,一个完整的动画就是由这些图层叠加起来的,具体到下面再介绍。
layerMap存储了Layer和其id的映射关系。
下面几个是动画里面常用的几个属性:

1
2
3
4
5
6
7
8
private Rect bounds;
private long startFrame;
private long endFrame;
private int frameRate;
private long duration;
private boolean hasMasks;
private boolean hasMattes;
private float scale;

Layer

Layer就是对应了做图中图层的概念,一个完整的动画就是由这些图层叠加起来的。
Layer里面有个静态方法:

1
static Layer fromJson(JSONObject json, LottieComposition composition)

它解析json文件的数据并转化为Layer对象,

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
private final List<Object> shapes = new ArrayList<>();

private String layerName;
private long layerId;
private LottieLayerType layerType;
private long parentId = -1;
private long inFrame;
private long outFrame;
private int frameRate;

private final List<Mask> masks = new ArrayList<>();

private int solidWidth;
private int solidHeight;
private int solidColor;

private AnimatableIntegerValue opacity;
private AnimatableFloatValue rotation;
private IAnimatablePathValue position;

private AnimatablePathValue anchor;
private AnimatableScaleValue scale;

private boolean hasOutAnimation;
private boolean hasInAnimation;
private boolean hasInOutAnimation;
@Nullable private List<Float> inOutKeyFrames;
@Nullable private List<Float> inOutKeyTimes;

private MatteType matteType;

一些成员变量一一对应json文件layers数组中的属性,动画就是由他们组合而来的。

数据转换

LottieDrawable

LottieDrawable继承自AnimatableLayer,关于AnimatableLayer我们后面再分析。
AnimatableLayer还有其他的子类,LottieDrawable可以理解为根布局,里面包含着其他的AnimatableLayer的子类,他们的关系可以理解为ViewGroup以及View的关系,ViewGroup里面可以包含ViewGroup以及View。这部分暂且不细说,下面会详细介绍。
LottieDrawable会通过buildLayersForComposition(LottieComposition composition)进行动画数据到动画对象的映射。
会根据LottieComposition里面的每一个Layer生成一个对应的LayerView

LayerView

LayerView也是AnimatableLayer的子类,它在setupForModel()里面会根据Layer里面的数据生成不同的AnimatableLayer的子类,添加到变量layers中去。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
else if (item instanceof ShapePath) {
ShapePath shapePath = (ShapePath) item;
ShapeLayerView shapeLayer =
new ShapeLayerView(shapePath, currentFill, currentStroke, currentTrimPath,
new ShapeTransform(composition), getCallback());
addLayer(shapeLayer);
} else if (item instanceof RectangleShape) {
RectangleShape shapeRect = (RectangleShape) item;
RectLayer shapeLayer =
new RectLayer(shapeRect, currentFill, currentStroke, new ShapeTransform(composition),
getCallback());
addLayer(shapeLayer);
} else if (item instanceof CircleShape) {
CircleShape shapeCircle = (CircleShape) item;
EllipseShapeLayer shapeLayer =
new EllipseShapeLayer(shapeCircle, currentFill, currentStroke, currentTrimPath,
new ShapeTransform(composition), getCallback());
addLayer(shapeLayer);
}

AnimatableLayer

AnimatableLayer的子类,分别对应着json文件中的不同数据:

1
2
3
4
5
6
7
8
9
10
11
Drawable (android.graphics.drawable)
AnimatableLayer (com.airbnb.lottie)
ShapeLayerView (com.airbnb.lottie)
LottieDrawable (com.airbnb.lottie)
LayerView (com.airbnb.lottie)
RectLayer (com.airbnb.lottie)
RoundRectLayer in RectLayer (com.airbnb.lottie)
MaskLayer (com.airbnb.lottie)
EllipseShapeLayer (com.airbnb.lottie)
ShapeLayer (com.airbnb.lottie)
GroupLayerView (com.airbnb.lottie)

绘制

LottieDrawableanimator来触发整个动画的绘制,最终会调用LottieAnimationViewpublic void invalidateDrawable(Drawable dr)方法进行视图的更新和重绘。
绘制工作基本是由LottieDrawable来完成的,具体实在其父类AnimatableLayerpublic void draw(@NonNull Canvas canvas)方法中进行:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
@Override
public void draw(@NonNull Canvas canvas) {
int saveCount = canvas.save();
applyTransformForLayer(canvas, this);

int backgroundAlpha = Color.alpha(backgroundColor);
if (backgroundAlpha != 0) {
int alpha = backgroundAlpha;
if (this.alpha != null) {
alpha = alpha * this.alpha.getValue() / 255;
}
solidBackgroundPaint.setAlpha(alpha);
if (alpha > 0) {
canvas.drawRect(getBounds(), solidBackgroundPaint);
}
}
for (int i = 0; i < layers.size(); i++) {
layers.get(i).draw(canvas);
}
canvas.restoreToCount(saveCount);
}

先绘制了本层的内容,然后开始绘制包含的layers的内容,这个过程类似与界面中ViewGroup嵌套绘制。如此完成各个Layer的绘制工作。

总结

由上面的分析我们得到了Lottie绘制动画的思路:

  1. 创建 LottieAnimationView lottieAnimationView
  2. LottieAnimationView中创建LottieDrawable lottieDrawable
  3. LottieAnimationView中创建compositionLoader,进行json文件解析得到LottieComposition,完成数据到对象Layer的映射。
  4. 解析完后通过setComposition方法把LottieCompositionlottieDrawablelottieDrawablesetComposition方法中把Layer转换为LayerView,为绘制做好准备。
  5. LottieAnimationView中把lottieDrawable设置setImageDrawable
  6. 然后开始动画lottieDrawable.playAnimation()

Lottie的性能

参考资料

http://www.jianshu.com/p/81be1bf9600c