Lottie使用介绍及原理浅析

目前,有没有发现,不管什么app里面,多少都带有了些带动画的广告。
比如:支付宝春节期间有个放烟花的动画效果,是不是感觉app的体验效果瞬间上了一个档次呢?
那,如果我们需要实现这种类似效果,由于android那么多系统需要兼容,直接播放gif可以达到效果么?
或者直接使用帧动画播放。前者放在网络上,后者放在本地,但有木有觉得,这样的文件都挺大的啊,并且不是所有的机型都能达到类似的体验效果啊!
那,有没有什么更优的方案呢?嗯,自问自答一下。试试使用airbnb公司开源的Lottie android项目
并且,它对android、ios、h5等都支持哦~!


Lottie 简介

  • Lottie支持Jellybean (API 16) Android4.2 及以上的系统
  • Lottie是一个开源动画库,它能够同时支持iOS,Android与ReactNative的开发
  • 工作流程
    • 设计师 通过安装AE上的bodymovin的插件,能够将AE中的动画工程文件转换为通用的json格式
    • 然后各平台的开发,可以使用对应的开源库,来通过原生代码,解析对应的json文件,绘制相对应的动画。
  • Lottie所要达到的目的是:动画文件一次绘制、一次转换,随处可用的效果,和Java一次编译随处运行效果一样

以下是效果预览,炫酷吧!


这里给设计师一个关于AE软件下载安装的传送门:

Lottie 使用介绍

Lottie库的使用,其实非常简单,使用Lottie提供的一个继承自ImageView的控件,绑定json数据即可。

可支持绑定的数据,可来源于

  • 本地assets、raw等目录下的json文件
  • 通过网络下载在本地json 或 (下载的zip文件解压后的json)
  • 存放在内容提供者中的json

绑定在指定的LottieAnimationView(AppCompatImageView的子类)上。不仅仅只是简单的支持播放动画额~
还提供了以下功能:

  • 自动适配播放区域大小
  • 加边框
  • 修改背景颜色
  • 播放指定开始和结束的frame
  • 动画区域指定放缩
  • 控制指定倍率的播放速度
  • 支持硬件加速
  • 针对Android4.4以上有优化

Lottie 库引入

方式1

  • 在需要使用项目build.gradle导入当前最新版本库

    1
    implementation 'com.airbnb.android:lottie:2.5.2'

    注:lottie 库,里面有一个依赖需要指定版本号,源码如下

    1
    implementation "com.android.support:appcompat-v7:$supportLibVersion"

    不看源码,会遇到坑有没有了,所以你需要进行下一步骤

  • 在根项目外的build.gradle给lottie 库指定appcompat-v7版本

    1
    2
    3
    buildscript {
    ext.supportLibVersion = '27.1.0' //指定和项目统一的版本号
    }

方式2

可能是最新2.5.2版本还没正常发布吧,可直接使用2.5.1,是没有问题的

  • 在需要使用项目build.gradle导入当前最新版本库
1
implementation 'com.airbnb.android:lottie:2.5.1'

方式3

直接拉 https://github.com/airbnb/lottie-android.git 上的代码到本地
然后当前项目直接引入这个库,然后在lottie 库的build.gradle中修改$supportLibVersion版本号

1
implementation "com.android.support:appcompat-v7:$supportLibVersion"

Lottie 简单使用介绍

LottieAnimationView简单使用方法:

方式1,在指定xml布局文件中播放Asset下的文件

1
2
3
4
5
6
7
8
<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"
/>
  • lottie_loop属性为是否重复无限期播放动画,为true时,动画无限次数播放,为false时,播放一次。
  • lottie_fileName 的文件名是 Asset目录下存放的。
  • lottie_autoPlay true自动播放(取消关联屏幕时会自动停止播放,但在关联在屏幕上时,设置为true后,会自动调用播放)

方式2,java方式播放Asset下的文件

1
2
3
4
5
6
7
8
9
LottieAnimationView animationView = (LottieAnimationView)findViewById(R.id.animationView);
LottieComposition.Factory.fromAssetFileName(this,"Logo/LogoSmall.json", new OnCompositionLoadedListener() {
@Override
public void onCompositionLoaded(@Nullable LottieComposition composition) {
animationView.setComposition(composition);//绑定json文件关键代码
}
});
animationView.setRepeatCount(-1);//循环播放
animationView.playAnimation();//开始播放

其他方式绑定json到LottieAnimationView

关于json到LottieAnimationView这步骤,其实不要忘了。这里的json不仅仅只使用本地的,其实也可以存放在任何地方,只要在使用的时候能拿到即可。即:支持网络后台存放,前端获取再展示等。

  • 异步方式

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    // Raw目录下的动画json文件解析为LottieComposition
    Cancellable cancellable = LottieComposition.Factory.fromRawFile(Context context, @RawRes int resId, OnCompositionLoadedListener listener)

    //动画json相关文件流解析为LottieComposition
    Cancellable cancellable = LottieComposition.Factory.fromInputStream(InputStream stream, OnCompositionLoadedListener listener)

    //直接将文件中的json数据读取为String再解析为控件所能理解的LottieComposition
    Cancellable cancellable = LottieComposition.Factory.fromJsonString(String jsonString, OnCompositionLoadedListener listener)

    //直接将JsonReader解析为控件所能理解的LottieComposition
    Cancellable cancellable = LottieComposition.Factory.fromJsonReader(JsonReader reader, OnCompositionLoadedListener listener)
  • 同步方式

    1
    2
    3
    4
    5
    6
    7
    8
    9
    //直接同步将Asset下文件解析为控件所能理解的LottieComposition
    LottieComposition lottieComposition = LottieComposition.Factory.fromFileSync(Context context, String fileName)
    //其他类似相关
    fromInputStreamSync(InputStream stream)
    fromInputStreamSync(InputStream stream, boolean close)

    fromJsonSync(@SuppressWarnings("unused") Resources res, JSONObject json)
    fromJsonSync(String string)
    fromJsonSync(JsonReader reader)

其他api

前面已经有介绍,这不仅仅有播放功能,其次还有以上参数配置等功能。可以查阅源码取相应api设置即可,以下为简单列举:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16

//设置播放速度。如果速度< 0,动画将会倒退。
LottieAnimationView.setSpeed(float speed)

//设置播放区域片段动画
LottieAnimationView.setMinAndMaxProgress(@FloatRange(from = 0f, to = 1f) float minProgress,@FloatRange(from = 0f, to = 1f) float maxProgress)
LottieAnimationView.setMinAndMaxFrame(int minFrame, int maxFrame)

//设置是否循环
LottieAnimationView.loop(boolean loop)

//设置Assets下对应的目录
LottieAnimationView.setImageAssetsFolder(String imageAssetsFolder)

//暂停动画
LottieAnimationView.pauseAnimation()

原理浅剖

Lottie使用json文件来作为动画数据源,json文件由AE衷的Bodymovin插件导出,Json中的文件其实就是把图片中的元素进行来拆分,并且描述每个元素的动画执行路径和执行时间。Lottie的功能就是读取这些数据,然后绘制到屏幕上。

具体过程为:

  • json文件——>Component——>Drawable——>View

相对应的:

LottieComposition(json->数据对象)

  • fromAssetFileName(资源file)
  • fromFileSync(异步文件,通常是网络数据)
  • fromJson(直接的json)
  • 通过这三个入口接收json文件、json流,然后异步都通过AsynTask来异步处理,最终核心处理都是在fromJsonSync中进行json数据的解析
  • 对应json数据:
    • width = json.getInt(“w”);
    • height = json.getInt(“h”);
    • 根据上面2参数决定: composition.bounds = new Rect(0, 0, scaledWidth, scaledHeight);
    • composition.startFrame = json.getLong(“ip”);
    • composition.endFrame = json.getLong(“op”);
    • composition.frameRate = json.getInt(“fr”);
    • long frameDuration = composition.endFrame - composition.startFrame;
    • 根据上面3参数决定:composition.duration = (long) (frameDuration / (float) composition.frameRate * 1000);
    • JSONArray jsonLayers = json.getJSONArray(“layers”); 核心解析类为 Layer fromJson),对应解析为下面的变量:
      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      private final LongSparseArray<Layer> layerMap = new LongSparseArray<>();
      private final List<Layer> layers = new ArrayList<>();
      private Rect bounds;
      private long startFrame;
      private long endFrame;
      private int frameRate;
      private long duration;
      private boolean hasMasks;
      private boolean hasMattes;
      private float scale;

LottieDrawable(数据对象->Drawable)

该类的继承关系

1
LottieDrawable extends AnimatableLayer extends Drawable
  • AnimatableLayer

主要重写了draw,在代码中可以看出,借用canvas的save、restoreToCount来实现像PS那种图层叠加的效果

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);
}
  • LottieDrawable

该类的核心方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
void setComposition(LottieComposition composition) {
if (getCallback() == null) {
throw new IllegalStateException(
"You or your view must set a Drawable.Callback before setting the composition. This " +
"gets done automatically when added to an ImageView. " +
"Either call ImageView.setImageDrawable() before setComposition() or call " +
"setCallback(yourView.getCallback()) first.");
}
//清除之前的数据
clearComposition();
this.composition = composition;
animator.setDuration(composition.getDuration());
setBounds(0, 0, composition.getBounds().width(), composition.getBounds().height());
//核心函数:即根据lottieComposition建立多个layerView,此时已经创建好了多个Drawable,并通过List建立的为以lottieDrawable为根的一个drawable树。
buildLayersForComposition(composition);
getCallback().invalidateDrawable(this);
}

以上该方法中,最核心的函数:

1
void buildLayersForComposition(LottieComposition composition)
  • 它的作用重点做的是为AnimatableLayer创建关键信息
    • 将得到的bitmap(mainBitmap, maskBitmap, matteBitmap)+ layer(通过之前setComposition获得的)信息合成 LayerView
    • 将LayerView通过super.addLayer(AnimatableLayer#layers),在AnimatableLayer#layers中去draw

LottieAnimationView(绘制)

  • 操作集合,LottieAnimationView 继承自 AppCompatImageView,封装了一些动画的操作,具体的绘制时委托给 LottieDrawable 完成的。
  • 主要通过LottieComposition类给View数据,然后View提供了对于动画的一系列播放、暂停、等等api

关于性能

官方说法:

  • 如果没有mask和mattes,那么性能和内存非常好,没有bitmap创建,大部分操作都是简单的cavas绘制。
  • 如果存在mattes,将会创建2~3个bitmap。bitmap在动画加载到window时被创建,被window删除时回收。所以不宜在RecyclerView中使用包涵mattes或者mask的动画,否则会引起bitmap抖动。除了内存抖动,mattes和mask中必要的bitmap.eraseColor()和canvas.drawBitmap()也会降低动画性能。对于简单的动画,在实际使用时性能不太明显。
  • 如果在列表中使用动画,推荐使用缓存LottieAnimationView.setAnimation(String, CacheStrategy) 。

参考资料

相关网站