为了账号安全,请及时绑定邮箱和手机立即绑定

开源项目- Lottie 源码分析

标签:
Android

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()

原文链接:http://www.apkbus.com/blog-719059-63200.html

点击查看更多内容
TA 点赞

若觉得本文不错,就分享一下吧!

评论

作者其他优质文章

正在加载中
  • 推荐
  • 评论
  • 收藏
  • 共同学习,写下你的评论
感谢您的支持,我会继续努力的~
扫码打赏,你说多少就多少
赞赏金额会直接到老师账户
支付方式
打开微信扫一扫,即可进行扫码打赏哦
今天注册有机会得

100积分直接送

付费专栏免费学

大额优惠券免费领

立即参与 放弃机会
意见反馈 帮助中心 APP下载
官方微信

举报

0/150
提交
取消