Lottie 源码阅读.

纯手打原创,转载请注明出处,感谢!

参考资料

概略介绍

Lottie is the native engine that Airbnb’s awesome team built. It uses Bodymovin as the animation exporter and is the ideal complement for getting animations to play natively everywhere. Follow these links to get each player:

官方效果图

整体开发流程

如图所示,通过安装AE上的bodymovin的插件,能够将AE中的动画工程文件转换为通用的json格式描述文件(bodymovin插件本身是用于网页上呈现各种AE效果的一个开源库),lottie所做的事情就是实现在不同移动端平台上呈现AE动画的方式,从而达到动画文件的一次绘制、一次转换,随处可用的效果,这个跟Java一次编译随处运行效果一样

各平台 代码简单示范
android:

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

iOS:

1
2
3
4
5
LOTAnimationView *animation = [LOTAnimationView animationNamed:@"Lottie"];
[self.view addSubview:animation];
[animation playWithCompletion:^(BOOL animationFinished) {
// Do Something
}];

WEB:

1.8
1
2
3
4
5
LOTAnimationView *animation = [LOTAnimationView animationNamed:@"Lottie"];
[self.view addSubview:animation];
[animation playWithCompletion:^(BOOL animationFinished) {
// Do Something
}];

思路分析

我们先从底层思考下如何在屏幕上绘制动画,最简单的方式是把动画分为多张图片,然后通过周期替换屏幕上绘制的图片来形成动画,这种暴力的方式非常简单,但缺点明显,很耗内存,动画播放中前后两张替换的图片在很多元素并没有变化,重复的内容浪费了空间。
为了提高空间利用率,可以把图片中的元素进行拆分,使用过photoshop的同学知道,其实在处理一张图片时,可以把一张复杂的图片使用多个图层来表示,每个图层上展示一部分内容,图层中的内容也可以拆分为多个元素。拆分元素之后,根据动画需求,可以单独对图层,甚至图层中的元素设置平移、旋转、收缩等动画。

Lottie使用json文件来作为动画数据源,json文件是通过Bodymovin插件导出的,查看sample中给出的json文件,其实就是把图片中的元素进行来拆分,并且描述每个元素的动画执行路径和执行时间。Lottie的功能就是读取这些数据,然后绘制到屏幕上。
现在思考如果我们拿到一份json格式动画如何展示到屏幕上。首先要解析json,建立数据到对象的映射,然后根据数据对象创建合适的Drawable绘制到View上,动画的实现可以通过操作读取到的元素完成。

从android 源码解析

项目结构图

该图因为太大了,所以有可能会看不清楚,感兴趣的同学可以下载到本地放大查看
核心类

通过如下3个核心类来来完成整个工作流程,因而使用起来比较简单。以下是三个比较核心的类的说明:

  • LottieComposition (json->数据对象)
    Lottie使用LottieComposition来作为After Effects的数据对象,即把Json文件映射为到LottieComposition,该类中提供了解析json的静态方法。
  • LottieDrawable (数据对象->Drawable)
    这个类是最上层使用的重要的一个类,动画的绘制就是由这个类来实现。
  • LottieAnimationView(绘制)
    操作集合,LottieAnimationView 继承自 AppCompatImageView,封装了一些动画的操作,具体的绘制时委托为 LottieDrawable 完成的。
    数据解析
    数据的解析,主要参考 LottieComposition.fromJsonSync 方法:
    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
    public static LottieComposition fromJsonSync(Resources res, JSONObject json) {
    Rect bounds = null;
    float scale = res.getDisplayMetrics().density;
    int width = json.optInt("w", -1);
    int height = json.optInt("h", -1);

    if (width != -1 && height != -1) {
    int scaledWidth = (int) (width * scale);
    int scaledHeight = (int) (height * scale);
    bounds = new Rect(0, 0, scaledWidth, scaledHeight);
    }

    long startFrame = json.optLong("ip", 0);
    long endFrame = json.optLong("op", 0);
    float frameRate = (float) json.optDouble("fr", 0);
    String version = json.optString("v");
    String[] versions = version.split("[.]");
    int major = Integer.parseInt(versions[0]);
    int minor = Integer.parseInt(versions[1]);
    int patch = Integer.parseInt(versions[2]);
    LottieComposition composition = new LottieComposition(
    bounds, startFrame, endFrame, frameRate, scale, major, minor, patch);
    JSONArray assetsJson = json.optJSONArray("assets");
    parseImages(assetsJson, composition);
    parsePrecomps(assetsJson, composition);
    parseFonts(json.optJSONObject("fonts"), composition);
    parseChars(json.optJSONArray("chars"), composition);
    parseLayers(json, composition);
    return composition;
    }

还有 parseImages、parsePrecomps、 parseLayers 这几个方法。
JSON文件的属性含义

动画描述文件是通过 bodymovin 插件导出来的,里面包含了动画的一切信息,包括了帧率,动画形态,如何做动画等。接下来将简单说明一下动画描述文件中的主要属性。

最外部结构:
1
2
3
4
5
6
7
8
9
10
11
12
{
"v": "4.6.2", // bodymovin的版本
"fr": 25, // 帧率
"ip": 0, // 起始关键帧
"op": 1000, // 结束关键帧
"w": 720, // 动画宽度
"h": 800, // 动画高度
"nm": "合成 1",
"ddd": 0,
"assets":[], // 动画图片资源信息
"layers":[] // 动画图层信息
}

从这里可以获取 设计的动画的宽高,帧相关的信息,动画所需要的图片资源的信息以及图层信息。

assets

图片资源信息, 相关类 LottieImageAsset、 ImageAssetBitmapManager。

1
2
3
4
5
6
7
8
9
"assets": [
{
"id": "image_0", // 图片id
"w": 58, // 图片宽度
"h": 31, // 图片高度
"u": "images/",
"p": "img_0.png" // 图片名称
}
}

layers

图层信息,相关类:Layer、BaseLayer以及 BaseLayer 的实现类。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
{
"ddd": 0,
"ind": 0, // layer id 图层 id
"ty": 2, // layertype 图层类型
"nm": "btnSlice.png", // layerName 图层信息
"cl": "png",
"refId": "image_0", // 引用的资源 id,如果是 ImageLayer 那么就是图片的id
"ks": {....},
"ao": 0,
"ip": 0, // inFrame 该图层起始关键帧
"op": 90.0000036657751, // outFrame 该图层结束关键帧
"st": 0, // startFrame 开始
"bm": 0,
"sr": 1
}

播放动画

官方说法
  • 如果没有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) 。
Share Comments