开源项目- Lottie 源码分析

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

实现思路

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

源码分析

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

LottieAnimationView

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

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有两个很重要的成员变量:

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

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

setImageDrawable(lottieDrawable);

解析JSON文件

JSON文件

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

{
  "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文件的几个静态方法:

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里面的几个变量:

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

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

    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里面有个静态方法:

static Layer fromJson(JSONObject json, LottieComposition composition)

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

  private final List 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 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 inOutKeyFrames;
  @Nullable private List 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中去。

      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文件中的不同数据:

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)方法中进行:

  @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

你可能感兴趣的:(Android开源项目)