Lottie-Android 源码分析

Lottie-Android 项目地址:airbnb/lottie-android

Lottie 简介

Lottie 是一个用于解析 Adobe After Effects 使用 Bodymovin 插件导出的动画 json 文件,并在移动端进行渲染的类库。

简而言之,设计师可以使用 After Effects 制作动画,经由 Lottie 便可以很简单的在移动端渲染,而无须工程师进行大量的手动实现设计师动画的工作。俗话说“一图胜千言”,下面的动画均由 Lottie 渲染(图片源自 Lottie 文档):

Lottie-Android 的使用

首先,在 build.gradle 文件中引入 Lottie-Android 的依赖:

dependencies {
    ...
    compile 'com.airbnb.android:lottie:2.5.0-rc1'
    ...
}
复制代码

Lottie 支持 ICS(API 14)及以上。使用 Lottie 最简单的方法是通过 LottieAnimationView

<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"
        app:lottie_autoPlay="true" />
复制代码

此外,还可以在 Java 代码中加载 Lottie:

LottieAnimationView animationView = (LottieAnimationView) findViewById(R.id.animation_view);
animationView.setAnimation("hello-world.json");
animationView.loop(true);
animationView.playAnimation();
复制代码

LottieAnimationView 内部通过 LottieDrawable 来渲染动画,如有需要,开发者也可以直接使用该 drawable:

LottieDrawable drawable = new LottieDrawable();
LottieComposition.Factory.fromAssetFileName(getContext(), "hello-world.json", (composition) -> {
    drawable.setComposition(composition);
});
复制代码

Lottie-Android 源码分析

Lottie-Android 的工作流程大致可以分为两个阶段:

  1. 动画文件的加载
  2. 动画的渲染

下面逐一对 Lottie-Android 中上述过程的源码进行分析。

动画文件的加载

使用 Bodymovin 插件,设计师可以将 After Effects 的动画以 json 的形式导出。在 Lottie-Android 中,该 json 文件对应的 Java Model 即 LottieComposition 类。类 LottieComposition.Factory 提供了多个静态方法用于从不同途径加载 json 数据并将其解析为 LottieComposition

这些静态方法中,以 “Sync” 结尾的方法将在调用线程以同步的方式加载并解析 json 数据;其余方法则通过 Lottie 自定义的 AsyncTask 子类 AsyncCompositionLoader 异步地加载并解析 json 数据。

无论是以异步还是同步的方式,Lottie 均使用 JsonReader 来解析 json 数据。使用 JsonReader 可以从流中解析 json 数据,从而避免了因一次性将全部 json 数据加载到内存之中而带来的 OOM 问题。

实际执行 json 解析的是 LottieCompositionParserparse 方法。该方法中将 json 中的字段一一解析为 LottieComposition 的成员变量,进而构造了一个 LottieComposition 实例,逻辑较为简单,本文不予赘述。想要理解动画 json 数据中字段的含义的读者,可以自行查看 LottieCompositionParser 类的源码。

Lottie 动画渲染

图层

要了解 Lottie 动画渲染,首先要理解 After Effects 中的一个重要概念:Layer(图层)。

相信使用过 Adobe Photoshop 的读者对于图层的概念并不陌生(如果你使用过 After Effects 的话可以跳过这一部分)。在 After Effects 中,图层的概念 与 Photoshop 中的图层概念类似:图层相当于对于动画整体的图像进行了更细粒度的区分,将不同类型的元素进行了拆分,不同的形状、纯色、文本等元素分别位于不同的图层中,所有图层依序叠加在一起构成了所渲染的图像。在未与其它图层关联时,修改某一图层的属性不会影响其它图层,这样在执行动画的时候便可以逻辑更加明晰地对图像中的各个元素分别执行不同的动画逻辑。

Lottie 中,图层的概念被抽象为抽象类 BaseLayerBaseLayer 共有 6 个子类:

  • ShapeLayer
  • CompositionLayer
  • SolidLayer
  • ImageLayer
  • NullLayer
  • TextLayer

它们与 After Effects 中的图层的对应关系为:

  • ShapeLayer:形状图层
  • CompositionLayer:预合成图层
  • SolidLayer:纯色图层
  • ImageLayer:图片素材图层
  • NullLayer:空图层
  • TextLayer:文本图层

上述图层种类(除去空图层)在 After Effects 用户指南 《图层概述》 一文中均有提及。

除去预合成图层,其余图层的含义都显而易见。“预合成”一词是 After Effect 中的概念,Adobe 在 After Effects 用户指南 《关于预合成和嵌套》 一文中对“预合成”的概念进行了阐述:

如果要对合成中已存在的某些图层进行分组,可以预合成 这些图层。预合成图层会将这些图层放置在新合成中,这将替换原始合成中的图层。新的嵌套合成将成为原始合成中单个图层的源。

文中的合成一词是 After Effects 中的基本概念,即时间轴、图层及其空间与时间上的对应关系信息的集合。而预合成图层可以认为是合成的子集,将一个合成进行更为细粒度的划分,对图层进一步进行了分组以方便组织项目。而在 Lottie 中,CompositionLayer 便是一个可以包含多个 Layer 的 Layer(可以将 CompositionLayer 与 Layer 的关系类比为 ViewGroupView 的关系)。

Lottie 的绘制

正如 Lottie 文档中所述,LottieAnimationView 使用 LottieDrawable 来渲染动画,动画的实际执行者是 LottieDrawable

通过调用 LottieDrawablesetComposition 方法,可以将 LottieDrawable 与动画数据 Model LottieComposition 进行绑定。

setComposition 方法中,LottieDrawable 依据传入的 LottieComposition 构建了一个新的 CompositionLayer 作为整个动画的根图层,并且根据 animator 设置了当前动画的进度。

public boolean setComposition(LottieComposition composition) {
    ...
    buildCompositionLayer();
    animator.setComposition(composition);
    setProgress(animator.getAnimatedFraction());
    ...
}
复制代码

成员变量 animatorValueAnimator 的子类 LottieValueAnimator 的实例,借助 animatorLottieDrawable 得以控制整个动画的更新与进度。

animator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
      @Override public void onAnimationUpdate(ValueAnimator animation) {
        if (compositionLayer != null) {
          compositionLayer.setProgress(animator.getAnimatedValueAbsolute());
        }
      }
    });
复制代码

animatorgetAnimatedValueAbsolute 方法返回了当前动画的进度(该值为 float 型,取值范围是[0,1] ,与动画的速度、方向等均无关),将该值传给根图层 compositionLayer,接着,类似于 Android 中 View Tree 的绘制,根图层 compositionLayer 依序逐层地将动画进度传递给子图层并将其一一绘制。

 @Override void drawLayer(Canvas canvas, Matrix parentMatrix, int parentAlpha) {
    L.beginSection("CompositionLayer#draw");
    canvas.save();
    ...

    for (int i = layers.size() - 1; i >= 0 ; i--) {
      boolean nonEmptyClip = true;
      if (!newClipRect.isEmpty()) {
        nonEmptyClip = canvas.clipRect(newClipRect);
      }
      if (nonEmptyClip) {
        BaseLayer layer = layers.get(i);
        layer.draw(canvas, parentMatrix, parentAlpha);
      }
    }
    canvas.restore();
    L.endSection("CompositionLayer#draw");
    ...
 }
复制代码

而不同类型图层的绘制,便是各个图层依据对应的数据 Model 以及当前的动画进度,使用 canvas 进行绘图的过程了。对于这一过程,本文不再赘述,感兴趣的读者可以根据上文列出的图层类型查看不同种类图层的绘制实现(drawLayer 方法)。

至此,动画便在 LottieDrawable 中渲染了出来。

转载于:https://juejin.im/post/5a6934b9f265da3e467541c7

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值