Android Lottie 使用以及源码解析

Airbnb在GitHub上面开源了一个项目lottie-android,最近火的不要不要的,牢牢占据Trending排行榜(日、周、月)首位,下面我们就见识一下这个项目。 
首先放上Lottie在GitHub上面的项目地址:AndroidiOS, 和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
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值