Android Lottie 使用以及源码解析

来源:互联网 发布:网络电视30天回看 编辑:程序博客网 时间:2024/05/17 04:53

Airbnb在GitHub上面开源了一个项目lottie-android,最近火的不要不要的,牢牢占据Trending排行榜(日、周、月)首位,下面我们就见识一下这个项目。 
首先放上Lottie在GitHub上面的项目地址:Android,iOS, 和React Native。

Lottie简介

Lottie是一个为Android和IOS设备提供的一个开源框架,它能够解析通过Adobe After Effects 软件做出来的动画,动画文件通过Bodymovin导出json文件,就可以通过Lottie中的LottieAnimationView来使用了。 
Bodymovin是一个After Effects的插件,它由Hernan Torrisi开发。 
我们先看看官方给出的实现的动画效果:

这些动画如果让你实现起来,你可能会觉得很麻烦,但是通过Lottie这一切就变得很容易。 
想了解更多请参考官方介绍

使用方法

首先由视觉设计师通过Adobe After Effects做好这些动画,这个比我们用代码来实现会容易的很多,然后Bodymovin导出json文件,这些json文件描述了该动画的一些关键点的坐标以及运动轨迹,然后把json文件放到项目的app/src/main/assets目录下,代码中在build.gradle中添加依赖:

dependencies {  compile 'com.airbnb.android:lottie:1.0.1'}
  • 1
  • 2
  • 3

在布局文件上加上:

<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"
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6

或者代码中实现:

LottieAnimationView animationView = (LottieAnimationView) findViewById(R.id.animation_view);animationView.setAnimation("hello-world.json");animationView.loop(true);
  • 1
  • 2
  • 3

此方法将加载文件并在后台解析动画,并在完成后异步开始呈现动画。 
Lottie只支持Jellybean (API 16)或以上版本。 
通过源码我们可以发现LottieAnimationView是继承自AppCompatImageView,我们可以像使用其他View一样来使用它。

LottieAnimationView animationView = (LottieAnimationView) findViewById(R.id.animation_view);
  • 1

甚至可以从网络上下载json数据:

 LottieComposition composition = LottieComposition.fromJson(getResources(), jsonObject, (composition) -> {     animationView.setComposition(composition);     animationView.playAnimation(); });
  • 1
  • 2
  • 3
  • 4

或者使用

setAnimation(JSONObject);
  • 1

我们还可以控制动画或者添加监听器:

animationView.addAnimatorUpdateListener((animation) -> {    // Do something.});animationView.playAnimation();...if (animationView.isAnimating()) {    // Do something.}...animationView.setProgress(0.5f);...// Custom animation speed or duration.ValueAnimator animator = ValueAnimator.ofFloat(0f, 1f)    .setDuration(500);animator.addUpdateListener(animation -> {    animationView.setProgress(animation.getAnimatedValue());});animator.start();...animationView.cancelAnimation();
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20

LottieAnimationView是使用LottieDrawable来渲染动画的,如果有必要,还可以直接使用LottieDrawable

LottieDrawable drawable = new LottieDrawable();LottieComposition.fromAssetFileName(getContext(), "hello-world.json", (composition) -> {    drawable.setComposition(composition);});
  • 1
  • 2
  • 3
  • 4

如果动画会被频繁的复用,LottieAnimationView有一套缓存策略,可以使用

LottieAnimationView#setAnimation(String, CacheStrategy)
  • 1

来实现它,CacheStrategy可以是StrongWeak或者是None,这样LottieAnimationView就可以持有一个已经加载和解析动画的强引用或者弱引用。


源码分析

下面我们就从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()
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7

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

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

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

setImageDrawable(lottieDrawable);
  • 1

解析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}
  • 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

重要的数据都在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);
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6

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

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

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;
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8

Layer

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

static Layer fromJson(JSONObject json, LottieComposition composition)
  • 1

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

  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;
  • 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

一些成员变量一一对应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);      }
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19

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)
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12

绘制

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);  }
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21

先绘制了本层的内容,然后开始绘制包含的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://blog.csdn.net/heqiangflytosky/article/details/60770415