Lottie的基本用法其实还是非常简单的,不熟悉的同学请阅读我的博客开源项目-Lottie简介。接下来我们就从源码角度分析一下这么强大的功能是怎么实现的。
实现思路
Lottie使用json文件来作为动画数据源,然后把解析这些数据源出来,建立数据到对象的映射关系,根据里面的数据建立合适的Drawable
绘制到View
上面。
源码分析
下面我们就从LottieAnimationView
作为切入点来一步一步分析。
LottieAnimationView
LottieAnimationView
继承自AppCompatImageView
,封装了一些动画的操作:1
2
3
4
5
6
7public 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
2private LottieComposition.Cancellable compositionLoader;
private final LottieDrawable lottieDrawable = new LottieDrawable();
LottieComposition
和LottieDrawable
将会在下面专门进行分析,他们分别进行了两个重要的工作:json文件的解析和动画的绘制。compositionLoader
进行了动画解析工作,得到LottieComposition
。
我们看到的动画便是在LottieDrawable
上面绘制出来的,lottieDrawable
在setComposition
方法中被添加到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文件映射到LottieComposition
,LottieComposition
中提供了解析json文件的几个静态方法:
1 | public static Cancellable fromAssetFileName(Context context, String fileName, OnCompositionLoadedListener loadedListener); |
其实上面这些函数最终的解析工作是在public static LottieComposition fromJsonSync(Resources res, JSONObject json)
里面进行的。进行了动画几个属性的解析以及Layer
解析。
下面看一下LottieComposition
里面的几个变量:
1 | private final LongSparseArray<Layer> layerMap = new LongSparseArray<>(); |
layers
存储json文件中的layers
数组里面的数据,Layer
就是对应了做图中图层的概念,一个完整的动画就是由这些图层叠加起来的,具体到下面再介绍。layerMap
存储了Layer
和其id
的映射关系。
下面几个是动画里面常用的几个属性:1
2
3
4
5
6
7
8private 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
30private 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;
private List<Float> inOutKeyFrames;
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
19else 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
11Drawable (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)
绘制
LottieDrawable
的animator
来触发整个动画的绘制,最终会调用LottieAnimationView
的public void invalidateDrawable(Drawable dr)
方法进行视图的更新和重绘。
绘制工作基本是由LottieDrawable
来完成的,具体实在其父类AnimatableLayer
的public 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
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绘制动画的思路:
- 创建
LottieAnimationView lottieAnimationView
- 在
LottieAnimationView
中创建LottieDrawable lottieDrawable
- 在
LottieAnimationView
中创建compositionLoader
,进行json文件解析得到LottieComposition
,完成数据到对象Layer
的映射。 - 解析完后通过
setComposition
方法把LottieComposition
给lottieDrawable
,lottieDrawable
在setComposition
方法中把Layer
转换为LayerView
,为绘制做好准备。 - 在
LottieAnimationView
中把lottieDrawable
设置setImageDrawable
, - 然后开始动画
lottieDrawable.playAnimation()
。