Lottie Series Three: Principle Analysis

A Principle Overview

1.1 Core classes

  • LottieAnimationViewExtends ImageView, the default and easiest way to load Lottie animations.
  • LottieDrawableHas most of the same API as LottieAnimationView, but you can use it on any view you want.
  • LottieCompositionA composition is a stateless model representation of an animation. This file can be safely cached for as long as you need it, and can be reused freely between drawables/views.
  • LottieCompositionFactoryAllows you to create a LottieCompostionFactory from many inputs. This is what the setAnimation (…) API on LottieDrawable and LottieAnimationView uses under the hood. Factory methods also share the same cache as these classes.

1.2 Overview

LottieAnimationView inherits from ImageView, and draws the canvas to display on the interface through the current time. There are two key classes here: LottieComposition is responsible for parsing the json description file and converting the json content into Java data objects; LottieDrawable is responsible for drawing, drawing the data objects converted by LottieComposition into drawables and displaying them on the View. The sequence is as follows:
image-20220718112821577

As can be seen from the above process, the process of Lottie parsing and displaying animation can be divided into three steps: parse Json into LottieComposition; convert LottieComposition into LottieDrawer; LottieDrawer plays animation; let's analyze it step by step:

2. Parse the Josn file

image-20220718112821577

Enter the setCompositionTask method:

public void setAnimation(@RawRes final int rawRes) {
    
    
    setCompositionTask(fromRawRes(rawRes));
}

Enter the fromRawRes method:

private LottieTask<LottieComposition> fromAssets(final String assetName) {
    
    
    if (isInEditMode()) {
    
     
         //避免可视化编辑报错问题
         ...
    } else {
    
    
   	 //cacheComposition记录是否已缓存
   	 //最终拿到json文件的LottieComposition数据模型
   	 return cacheComposition ?
       LottieCompositionFactory.fromAsset(getContext(), assetName) : LottieCompositionFactory.fromAsset(getContext(), assetName, null);
   }
}

Enter the LottieCompositionFactory.fromAsset method, and finally a series of function calls, go to the LottieCompositionMoshiParser.parse
method:

// 对字节流内容进行解析:
LottieComposition composition = LottieCompositionMoshiParser
.parse(reader);
   if (cacheKey != null) {
     LottieCompositionCache.getInstance().put(cacheKey, composition);
}

The parse process needs to map and parse each field of json. The mapping relationship is as follows:

// 解析json字段:
private static final JsonReader.Options NAMES = JsonReader.Options.of(
   "w", // 0
   "h", // 1
   "ip", // 2
   "op", // 3
   "fr", // 4
   "v", // 5
   "layers", // 6
   "assets", // 7
...
);

Options is the static inner class of JsonReader, of is the method;

4 Generate LottieDrawer

LottieAnimationView passes the LottieComposition object generated after parsing to LottieDrawer;

/**
 * 设置一个composition.
 * 如果这个视图使用R.attr.lottie_cacheComposition填充xml,则可以设置默认缓存策略。
 */
public void setComposition(@NonNull LottieComposition composition) {
    
    
  if (L.DBG) {
    
    
    Log.v(TAG, "Set Composition \n" + composition);
  }
  lottieDrawable.setCallback(this);

  this.composition = composition;
  ignoreUnschedule = true;
  //将解析后的LottieComposition传递给LottieDrawable
  boolean isNewComposition = lottieDrawable.setComposition(composition);
  ignoreUnschedule = false;
  enableOrDisableHardwareLayer();
  if (getDrawable() == lottieDrawable && !isNewComposition) {
    
    
    // We can avoid re-setting the drawable, and invalidating the view, since the composition
    // hasn't changed.
    //我们可以避免重新设置drawable,并使视图无效,因为合成并没有改变。
    return;
  } else if (!isNewComposition) {
    
    
    // The current drawable isn't lottieDrawable but the drawable already has the right composition.
    // 当前的drawable不是lottieDrawable,但drawable已经有正确的组成。
    setLottieDrawable();
  }

  // This is needed to makes sure that the animation is properly played/paused for the current visibility state.
  // 需要确保动画在当前可见状态是正确播放/暂停。
  // It is possible that the drawable had a lazy composition task to play the animation but this view subsequently
  // became invisible. Comment this out and run the espresso tests to see a failing test.
  onVisibilityChanged(this, getVisibility());

  requestLayout();

  for (LottieOnCompositionLoadedListener lottieOnCompositionLoadedListener : lottieOnCompositionLoadedListeners) {
    
    
    lottieOnCompositionLoadedListener.onCompositionLoaded(composition);
  }
}

LottieDrawable constructs the LottieComposition object as a CompositionLayer

public boolean setComposition(LottieComposition composition) {
    
    
  if (this.composition == composition) {
    
    
    return false;
  }

  isDirty = false;
  clearComposition();
  this.composition = composition;
  buildCompositionLayer();
  ...
 private void buildCompositionLayer() {
    
    
    compositionLayer = new CompositionLayer(
        this, LayerParser.parse(composition), composition.getLayers(), composition);
    if (outlineMasksAndMattes) {
    
    
      compositionLayer.setOutlineMasksAndMattes(true);
    }
  }

CompositionLayer inherits from Baselayer, and it traverses all layer layers during construction and converts them to BaseLayer objects.

public CompositionLayer(LottieDrawable lottieDrawable, Layer layerModel, List<Layer> layerModels,
    LottieComposition composition) {
    
    
  super(lottieDrawable, layerModel);
  ...
  LongSparseArray<BaseLayer> layerMap =
      new LongSparseArray<>(composition.getLayers().size());
  BaseLayer mattedLayer = null;
  for (int i = layerModels.size() - 1; i >= 0; i--) {
    
    
    Layer lm = layerModels.get(i);
    BaseLayer layer = BaseLayer.forModel(this, lm, lottieDrawable, composition);
   ...
  }
}

Here, the forModel method of BaseLayer is used to abstract the subtypes of BaseLayer

@Nullable
static BaseLayer forModel(
    CompositionLayer compositionLayer, Layer layerModel, LottieDrawable drawable, LottieComposition composition) {
    
    
  switch (layerModel.getLayerType()) {
    
    
    case SHAPE:
      return new ShapeLayer(drawable, layerModel, compositionLayer);
    case PRE_COMP:
      return new CompositionLayer(drawable, layerModel,
          composition.getPrecomps(layerModel.getRefId()), composition);
    case SOLID:
      return new SolidLayer(drawable, layerModel);
    case IMAGE:
      return new ImageLayer(drawable, layerModel);
    case NULL:
      return new NullLayer(drawable, layerModel);
    case TEXT:
      return new TextLayer(drawable, layerModel);
    case UNKNOWN:
    default:
      // Do nothing
      Logger.warning("Unknown layer type " + layerModel.getLayerType());
      return null;
  }
}

The following are the different layer types of Lottie:
insert image description here

At this point, LottieDrawable instantiates each type of layer through CompositionLayer, and then completes the drawing of all layers in the draw() method of LottieDrawable:

@RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
public void draw(Canvas canvas, Matrix matrix) {
    
    
  CompositionLayer compositionLayer = this.compositionLayer;
  if (compositionLayer == null) {
    
    
    return;
  }
  compositionLayer.draw(canvas, matrix, alpha);
}

Five play animation

insert image description here
Through the playAnimation method of LottieAnimationView, we can see that the playAnimation method of LottieDrawable will be called internally, and then the playAnimation method of LottieValueAnimator will be triggered. LottieValueAnimator is actually a ValueAnimator, so in essence Lottie is also driven by attribute animation.

Specifically, it can be seen in LottieDrawable that after LottieValueAnimator calls updateListener, it will refresh the progress of CompositionLayer.

private final ValueAnimator.AnimatorUpdateListener progressUpdateListener = new ValueAnimator.AnimatorUpdateListener() {
    
    
  @Override
  public void onAnimationUpdate(ValueAnimator animation) {
    
    
    if (compositionLayer != null) {
    
    
      compositionLayer.setProgress(animator.getAnimatedValueAbsolute());
    }
  }
};

Enter setProgress and you can see that CompositionLayer will traverse all layer layers and call its setProgress method one by one.

@Override public void setProgress(@FloatRange(from = 0f, to = 1f) float progress) {
    
    
  super.setProgress(progress);
  ...
  for (int i = layers.size() - 1; i >= 0; i--) {
    
    
    layers.get(i).setProgress(progress);
  }
}

Entering the setProgress method of BaseLayer, you will find that all the setProgress methods of BaseKeyframeAnimation will be called, and the invalidateSelf() method will be called back in BaseLayer.

private void invalidateSelf() {
    
    
  lottieDrawable.invalidateSelf();
}

After calling back the invalidateSelf() method, LottieDrawable will call back the draw method

@RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
public void draw(Canvas canvas, Matrix matrix) {
    
    
  CompositionLayer compositionLayer = this.compositionLayer;
  if (compositionLayer == null) {
    
    
    return;
  }
  compositionLayer.draw(canvas, matrix, alpha);
}

Enter the draw method of CompositionLayer

@Override
public void draw(Canvas canvas, Matrix parentMatrix, int parentAlpha) {
    
    
  L.beginSection(drawTraceName);
  if (!visible || layerModel.isHidden()) {
    
    
    L.endSection(drawTraceName);
    return;
  }
  buildParentLayerListIfNeeded();
  L.beginSection("Layer#parentMatrix");
  matrix.reset();
  matrix.set(parentMatrix);
  for (int i = parentLayers.size() - 1; i >= 0; i--) {
    
    
    matrix.preConcat(parentLayers.get(i).transform.getMatrix());
  }
  L.endSection("Layer#parentMatrix");
  int opacity = transform.getOpacity() == null ? 100 : transform.getOpacity().getValue();
  int alpha = (int)
      ((parentAlpha / 255f * (float) opacity / 100f) * 255);
  if (!hasMatteOnThisLayer() && !hasMasksOnThisLayer()) {
    
    
    matrix.preConcat(transform.getMatrix());
    L.beginSection("Layer#drawLayer");
    drawLayer(canvas, matrix, alpha);
    L.endSection("Layer#drawLayer");
    recordRenderTime(L.endSection(drawTraceName));
    return;
  }
  ……

In fact, this constitutes a cycle. As the animator animation proceeds, LottieDrawable will continue to draw, so that the Lottie animation will run. The flow chart is as follows:

Screenshot 2022-02-14 AM 10.59.52.png

Six summary

First, the json resource file will be set through the corresponding type of LottieCompositionFactory;

Then in the fromJsonSync method, the json file will be parsed out of the size of the layer and the corresponding image resource file and layer will be drawn;

After the resources are loaded, the Composition of LottieAnimationView will be set in the callback, thereby calling the setComposition() method of LottieDrawable. In the
setComposition method, a CompositionLayer layer will be created through the buildCompositionLayer() method, where CompositionLayer inherits from BaseLayer, and different layer types can be obtained through the static method of BaseLayer's forModel();

Then LottieDrawable's setComposition() method will start to execute a ValueAnimation animation. This animation will drive the BaseLayer's draw() method to execute continuously, and draw each layer continuously through the matrix form of Matrix to form an animation. The matrix transformation data of these layers comes from a Keyframe object in BaseKeyframeAnimation, which will go to Json to obtain the corresponding data.

Attached picture (reference):

[External link image transfer failed, the source site may have an anti-leeching mechanism, it is recommended to save the image and upload it directly (img-Rv4eJcr5-1658136002844)(/Users/a58/Library/Application Support/typora-user-images/image-20220718145339827.png)]

source code

Example source code: https://github.com/LucasXu01/lottiedemo

reference:

Lottie official website

Lottie advanced and principle analysis

Lottie-Android articles that support click interaction

Lottie— json file parsing

Lottie animation usage and principle analysis

How to get the total number of frames available in a Lottie animation

Flutter advanced tutorial - using Lottie animation in Flutter

Use Lottie from a designer and developer perspective

Guess you like

Origin blog.csdn.net/LucasXu01/article/details/125855992