Lottie

608 阅读4分钟

Lottie

json文件到对象的映射

  1. LottieCompositionFactory类提供了多个静态方法用于从不同途径加载 json 数据并将其解析为 LottieComposition
  2. 以 “Sync” 结尾的方法将在调用线程以同步的方式加载并解析 json 数据;其余方法则通过 Lottie 自定义的 AsyncTask 子类 AsyncCompositionLoader 异步地加载并解析 json 数据。
  3. 无论是以异步还是同步的方式,Lottie 均使用 JsonReader 来解析 json 数据,从而避免了因一次性将全部 json 数据加载到内存之中而带来的 OOM 问题。
  4. 实际执行 json 解析的是 LottieCompositionParserparse 方法。该方法中将 json 中的字段一一解析为 LottieComposition 的成员变量,进而构造了一个 LottieComposition 实例

image.png

image.png

image.png

image.png

解析外部资源

{
  "v": "5.1.10",  //bodymovin的版本
  "fr": 24,     //帧率
  "ip": 0,      //起始关键帧
  "op": 277,    //结束关键帧
  "w": 110,     //动画宽度
  "h": 110,     //动画高度
  "nm": "合成 2",
  "ddd": 0,
   "assets": [...]   //资源信息
   "layers": [...]   //图层信息
}

在最外层中,可以获取插件 bodymovin 的版本号,起始关键帧,动画帧率及动画的宽高等属性。

解析图片

{
  "id": "image_0",   //图片id
  "w": 750,       //图片宽度
  "h": 1334,      //图片高度
  "u": "images/",      //图片路径
  "p": "img_0.png"     //图片名称
}

图片资源是在 “asset” 里边的,可以有多张图片,每张图片数据解析后会转换为实体类 LottieImageAsset

解析图层

"layers": [
  {
    "ddd": 0,
    "ind": 1,     //图层 id
    "ty": 2,      //图层类型
    "nm": "eye-right 2",
    "parent": 3,
    "refId": "image_0",  //引用资源Id
    "sr": 1,
    "ks": {       //动画属性值  
        "s": {   //s:缩放的数据值
        "a": 0,
        "k": [
          100,
          100,
          100
        ],
        "ix": 6
      }...},  
    "ip": 0,   //inFrame 该图层起始关键帧
    "op": 241,   //outFrame 该图层结束关键帧
    "st": 0,    //startFrame 开始关键帧
    "bm": 0
  },

一张复杂的图片可以使用多个图层来表示,每个图层展示一部分内容,图层中的内容也可以拆分为多个元素。在 Lottie 解析的 Json 中,图层可以分为以下几种:PreComp,Solid,Image,Null,Shape,Text。在数据解析时,先会转换为数据实体类 Layer,根据 type 来区分是哪种图层。

从 LottieCompisition 到 CompositionLayer

使用 Lottie 第二步是设置数据对象 LottieComposition,把数据对象的数据转换为具有绘制能力的 BaseLayer,以下是转换的部分类图:

不足

1. 有 mask、matters 时,有很大的性能影响

可以查看源码在 BaseLayer,是如何对有 mask 或 matte 进行计算的:

lottie_mask_1

如果不含 mask 或 matte,这里直接调用 drawLayer 就返回了,但是对于 mask 或 matte 会先进行 saveLayer,再去绘制 drawLayer。

在 saveLayer 中,可以看到这是个非常耗时的操作,需要分配和绘制一个 offscreen 的缓冲区,渲染的成本增加了一倍以上。

lottie_mask_2

所以这里 mask 或 matte 的边界越小,性能损耗也会越小。

2. 解码图片在主线程

查看 Lottie 获取图片的 ImageAssetManager 源码:

@Nullable public Bitmap bitmapForId(String id) {
  Bitmap bitmap = bitmaps.get(id);
  if (bitmap == null) {
    LottieImageAsset imageAsset = imageAssets.get(id);
    if (imageAsset == null) {
      return null;
    }
    if (delegate != null) {
      bitmap = delegate.fetchBitmap(imageAsset);
      if (bitmap != null) {
        bitmaps.put(id, bitmap);
      }
      return bitmap;
    }

    InputStream is;
    try {
      if (TextUtils.isEmpty(imagesFolder)) {
        throw new IllegalStateException("You must set an images folder before loading an image." +
            " Set it with LottieComposition#setImagesFolder or LottieDrawable#setImagesFolder");
      }
      is = context.getAssets().open(imagesFolder + imageAsset.getFileName());
    } catch (IOException e) {
      Log.w(L.TAG, "Unable to open asset.", e);
      return null;
    }
    BitmapFactory.Options opts = new BitmapFactory.Options();
    opts.inScaled = true;
    opts.inDensity = 160;
    bitmap = BitmapFactory.decodeStream(is, null, opts);
    bitmaps.put(id, bitmap);
  }
  return bitmap;
}

​ 这里如果没有设置图片代理,则直接解码图片。如果有设置图片代理,则在代理监听里获取图片。

mLottieAnim.setImageAssetDelegate(new ImageAssetDelegate() {
          @Override
          public Bitmap fetchBitmap(LottieImageAsset asset) {
              try {
                    FileInputStream fileInputStream = new FileInputStream("/sdcard/data/images/" + asset.getFileName());
                  return BitmapFactory.decodeStream(fileInputStream);
              } catch (Exception e) {
                  e.printStackTrace();
              }
              return null;
          }
      });

​ 这俩种方式,都将在主线程中调用,第一次都会从文件中解码,之后会直接从缓存中获取。对于一些 Android 低端机可能无法播放动画,甚至引发 ANR;

可能 Lottie 更多考虑的是没有图片或图片很少的情况,比如一些矢量动画,这种性能会很好。

数据对象到Drawable的映射

AnimatableLayer 继承自 Drawable,我们看下它的子类:

image.png 其中LayerView对应着Layer数据,Layer中有:

image.png

image.png

Lottie播放流程

image.png

绘制

LottieAnimationView 继承自 AppCompatImageView,封装了一些动画的操作,如:

image.png

具体的绘制时委托为 LottieDrawable 完成的,我们看下 LottieDrawable 中的 draw() 方法:

image.png

LottieDrawable 继承自AnimatableLayer,其draw()方法如下:

image.png 可以看到先绘制了本层的内容,然后开始绘制包含的layers的内容:

image.png