Why do YOU need to extend Lottie?

Disadvantages of native Lottie

Lottie believe that students must be very familiar with client side development, play the astonishing was born, direct animation development efficiency increased to the highest level, will we develop the pulled out from the abyss of animation, can say no Lottie before meet animation project hair off the floor and after a Lottie animation needs true vacuum bubble goji berries.

So we can watch Lottie perform the following effects



With business iteration, er, the designer, pushes animation to a new height. They are not only satisfied with display animation, but also want to add animation to more business scenes to improve interactive experience, such as opening a red envelope, cutting a price and so on

The animation below is for your reference:

Is the thermos still holding?

Our needs

As shown in the figure above, it is an animation of opening a red envelope. The grab button in the animation can be clicked. The coupon information on the result page of the red envelope is dynamically delivered by the interface.

There are many similar interactive animations involving dynamic business data in the scene of Kuaitou E-commerce, but we cannot continue to use Lottie for such requirements, and are forced to return to the original native code scheme, and the development efficiency is back to before liberation. Therefore, the development efficiency of such scenes needs to be improved.

preparation

Project research

After the requirement scene is clear, the pre-research scheme is the next step. We first disintegrate the functions and find that dynamic text replacement needs to be satisfied in our animation, and the background of the text needs to be self-adaptive stretching. There should also be other scenes such as the replacement of texture, plus the interactive events of button clicking.

Once we know what our target functionality is, we need to find an entry point in Lottie’s open or semi-open capabilities

Lottie gives us the ability to replace text and tiles, does that meet our needs?

Lottie can replace text and maps, so the above animated scene text can be replaced dynamically

But can’t:

  • Text background adaptive stretching
  • Countdown and other dynamic control effects
  • Support button click events

Simple version scheme

Aside from button click events (there are some crude schemes for clicking) and dynamic control effects (which are not very common), do we have a scheme that supports this?

For a moment, does this dynamic data look very similar to a native XML layout populated with data? Since Lottie supports dynamic tile replacement, can we dynamically generate tiles and then replace them?

Obviously yes, we can place all the dynamic parts of the animation with a map in the animation, and then dynamically convert the layout to a map to replace the map at runtime, so that our animation can achieve dynamic binding of business data

A simple demo was written to verify the feasibility of the scheme, as shown in the figure below

Step 1: Generate a bitmap for the dynamic layout (there is plenty of code on the web)

/** * Get the bitmap of the view already displayed@param view
   * @return* /
  public static Bitmap getCacheBitmapFromView(View view) {
    final boolean drawingCacheEnabled = true;
    view.setDrawingCacheEnabled(drawingCacheEnabled);
    view.buildDrawingCache(drawingCacheEnabled);
    final Bitmap drawingCache = view.getDrawingCache();
    Bitmap bitmap = null;
    if(drawingCache ! =null) {
      bitmap = Bitmap.createBitmap(drawingCache);
      view.setDrawingCacheEnabled(false);
    }
    return bitmap;
  }

  /** * Get the bitmap of an undisplayed view *@param view
   * @param width
   * @param height
   * @return* /
  public static Bitmap getBitmapFromView(View view, int width, int height) {
    layoutView(view, width, height);
    return getCacheBitmapFromView(view);
  }

  /** * layout control *@param view
   * @param width
   * @param height
   */
  private static void layoutView(View view, int width, int height) {
    view.layout(0.0, width, height);
    int measuredWidth = View.MeasureSpec.makeMeasureSpec(width, View.MeasureSpec.EXACTLY);
    int measuredHeight = View.MeasureSpec.makeMeasureSpec(height, View.MeasureSpec.EXACTLY);
    view.measure(measuredWidth, measuredHeight);
    view.layout(0.0, view.getMeasuredWidth(), view.getMeasuredHeight());
  }
Copy the code

Step 2: Use LottieAssetDelegate to dynamically replace the placeholder map

public class LottieAssetDelegate implements ImageAssetDelegate {
  
  private Context context;
  private String replaceImgName;
  private Bitmap replaceBitmap;
  private String imagesFolder;

  public LottieAssetDelegate(Context context, String replaceImgName, Bitmap replaceBitmap, String imagesFolder) {
    this.context = context;
    this.replaceImgName = replaceImgName;
    this.replaceBitmap = replaceBitmap;
    if(! TextUtils.isEmpty(imagesFolder) && imagesFolder.charAt(imagesFolder.length() -1) != '/') {
      this.imagesFolder = imagesFolder + '/';
    } else {
      this.imagesFolder = imagesFolder; }}@Nullable
  @Override
  public Bitmap fetchBitmap(LottieImageAsset asset) {
    if (replaceImgName.equals(asset.getFileName())) {
      return replaceBitmap;
    }
    return getBitmap(asset);
  }

  private Bitmap getBitmap(LottieImageAsset asset) {

    Bitmap bitmap = null;
    String filename = asset.getFileName();
    BitmapFactory.Options opts = new BitmapFactory.Options();
    opts.inScaled = true;
    opts.inDensity = 160;
    InputStream is;
    try {
      is = context.getAssets().open(imagesFolder + filename);
    } catch (IOException e) {
      return null;
    }
    try {
      bitmap = BitmapFactory.decodeStream(is, null, opts);
    } catch (IllegalArgumentException e) {
      return null;
    }
    returnbitmap; }}Copy the code

Advanced version of the scheme

The simple version meets some of the requirements, but it’s not perfect and many scenarios are limited. What if the part I need to replace is a countdown? For example, the text in the picture above, which coupon is about to expire, is a countdown, the designer needs the countdown to run, but the simple version of the scheme is not good, because it is static texture generation can not be updated, so the simple version of the scheme is not enough flesh and blood, not strong and powerful!

Why can’t the adult world have it all? We want to support all the scenarios we might encounter in the future, we want to support clicks perfectly, we want to support dynamic business data perfectly, we want to support dynamic components perfectly, and we want Lottie to support our functionality the way we want it to.

So let’s open our minds completely. Can we replace placeholder maps with native layout controls? That is, when rendering a placeholder map, switch to rendering the native layout, so that the animation and the native layout work seamlessly together

Schematic diagram

Our choice

Scenario covering The business logic Dynamic layout Click on the interaction scalability
Simple version of 60% support Does not support Does not support poor
premium 100% support support support good

A comparison with the simple version makes it easy to make a choice

Plan to introduce

Core principles

The core principle of the scheme is to create a DynamicLayoutLayer DynamicLayoutLayer. Like the ImageLayer, TextLayer and CompostionLayer supported by Lottie, the drawing logic is realized by itself. Then, during runtime, hook the original animation placeholder layer (ImageLayer) and replace it with DynamicLayoutLayer. All the attribute changes on the placeholder layer are proxy to the DynamicLayoutLayer, so as to achieve seamless replacement.

The class diagram is as follows:

The core problem

To implement the plan to solve the problem of some of the core, the first thing to solve the problem of layer’s render tree to replace the layers and the original up a placeholder layer at the same level, to achieve seamless, second original layer of animation effects also needs to be synchronized to replace layer, such effect on the original layer animation effect only on replacing the layer, Finally, click interactive events and dynamic layout refresh need to be solved to fully support all requirement scenarios. Detailed scheme analysis of each core problem will be conducted below.

The code posted below is informal code, only to understand the general principle

  • The render tree

Each layer of Lottie will call its own DRAW to draw on the Canvas. In order to achieve same-layer rendering after replacement, native controls also need to be drawn on the Canvas of Lottie according to the placeholder layer level. Therefore, our solution is to pass the placeholder layer’s drawing proxy to the DynamicLayoutLayer, pass Lottie’s canvas in, and then call the DynamicLayoutLayer’s drawing logic to draw the content onto the passed canvas

Sample code:

static BaseLayer forModel( Layer layerModel, LottieDrawable drawable, LottieComposition composition) {
    switch (layerModel.getLayerType()) {
      case SHAPE:
        return new ShapeLayer(drawable, layerModel);
      case PRE_COMP:
        return new CompositionLayer(drawable, layerModel,
            composition.getPrecomps(layerModel.getRefId()), composition);
      case SOLID:
        return new SolidLayer(drawable, layerModel);
      case IMAGE:
        // if it is a DynamicLayoutLayer, replace it with DynamicLayoutLayer
        if (isDynamicLayout(layerModel)) {
          return new DynamicLayoutLayer(drawable, layerModel);
        }
        return new ImageLayer(drawable, layerModel);
      case NULL:
        return new NullLayer(drawable, layerModel);
      case TEXT:
        return new TextLayer(drawable, layerModel);
      case UNKNOWN:
      default:
        // Do nothing
        L.warn("Unknown layer type " + layerModel.getLayerType());
        return null; }}Copy the code
public class DynamicLayoutLayer extends BaseLayer{...@Override
  void drawLayer(Canvas canvas, Matrix parentMatrix, int parentAlpha) {
    // Dynamic layout rendering}}Copy the code
  • Animation synchronization

After the replacement, the layer hierarchy problem is solved, but the animation bound on the layer also needs to be synchronized to the replacement layer, which is the second problem we need to solve. For the animation problem, we need to start from the principle of Lottie animation, and need to understand the timeline of two concept frames and Matrix transformation

Frame the timeline

The Lottie animation data is composed of numerous key frames. When the designer sets the property data on each key frame, the data transformation between the two key frames is called frame timeline. The principle of Lottie animation is to calculate the property data of the current frame as the frame axis runs, and then set the data to the layer. Each layer synchronizes the corresponding property data in the corresponding frame to achieve the effect of animation.

A simple example, I in the first frame set a zoom key frames, data set to 100%, then in the fifth frame set on a scale keyframes, data set to 50%, and in the 10th frame set the zoom key frames, data is 150%, the present animation effects is the layer from the original size in the first 5 within the time frame shrunk to 50%, 5 again within the time frame amplification from 50% to 150%, and then run animation with the flash to the number of frames to calculate the current frame of data, such as the first frame data is 100%, and then play to frame 2, calculate the data was 90%, the data set to layer, and so on each frame is calculated his data set, String together to form the animation effect

Matrix transformation

Matrix is a Matrix transformation, which can be used in general image processing, and there are also a large number of application scenarios in Android. Some of the properties of View that we are familiar with are transformed by Matrix. The properties of View can be changed by Matrix transformation, such as scaling value, displacement value, rotation Angle, etc. The animation effect of Lottie is also obtained by Matrix data transformation. The exported data in AE will be converted into a group of Matix, and the corresponding Matrix data will be calculated during each frame rendering and then set to layer, thus realizing the property transformation effect of layer. Layers are the basic elements that make up the Lottie animation, and together they form a complete Lottie animation

Knowledge points related to Matrix can be learned by ourselves. Only concepts are introduced here

Through the analysis of the animation principle, it is very simple for us to solve the problem of animation synchronization. We only need to substitute all the basic data and matrix transformation data applied to the placeholder layer in the original animation to the dynamic layout layer

Sample code:

// Dynamic layout layer drawing
void drawLayer(Canvas canvas, Matrix parentMatrix, int parentAlpha) {View View = getReplaceView();// The key is the following code, which sets the matrix to the canvas and draws the native control to the current canvas
  canvas.save();
  canvas.concat(parentMatrix); 
  view.draw(canvas);
}
Copy the code
  • Click on the event

After solving the above two problems, our solution is roughly 60% completed. However, one of the biggest pain points of Lottie animation is click events. Most Lottie animations have a high probability of button clicking even without dynamic business data. When I used Lottie before, WHEN I met the need to click, I directly added a virtual click area on the corresponding position of Lottie animation. Was it too rough and violent? Is the click event not a problem if we use the scheme we have now?

In fact, there is still a little problem, because our dynamic control is to replace the placeholder layer, there will be some matrix transformation in the animation, the control position after transformation is not the original position, that is to say, your matrix transformation may have displacement or scaling, resulting in the click area dislocation, then how to solve this problem?

In fact, we can refer to the property animation, why does the property animation also adjust the click area after scaling or panning? In fact, there is a matrix reverse correction inside the property animation, we can also refer to the implementation of this section to correct the region

Sample code:

private MotionEvent getTransformedMotionEvent(MotionEvent event, View child) {
    final float offsetX = mScrollX - child.mLeft;
    final float offsetY = mScrollY - child.mTop;
    final MotionEvent transformedEvent = MotionEvent.obtain(event);
    transformedEvent.offsetLocation(offsetX, offsetY);
    if(! child.hasIdentityMatrix()) { transformedEvent.transform(child.getInverseMatrix()); }return transformedEvent;
}

public final Matrix getInverseMatrix(a) {
    ensureTransformationInfo();
    if (mTransformationInfo.mInverseMatrix == null) {
        mTransformationInfo.mInverseMatrix = new Matrix();
    }
    final Matrix matrix = mTransformationInfo.mInverseMatrix;
    mRenderNode.getInverseMatrix(matrix);
    return matrix;
} 
Copy the code
  • Dynamic Layout refresh

After all, Lottie was designed to provide me with a framework for animation display, but it cannot support various customization and function expansion. Moreover, its life cycle is very clear. If the animation has 130 frames, So Lottie starts rendering at frame 1 and ends rendering at frame 130, but what if you have a dynamic layout that goes beyond that life cycle and needs to be updated? E.g. red envelope inside out coupons that the us is not a static text but the period of validity of a countdown, that after Lottie playing the last frame to the countdown controls, there is no way to continue to go down, because the driver countdown redraw Lottie’s canvas, Lottie because life cycle has ended, the canvas is not continue to refresh, The corresponding driving force is broken, so how do we solve this situation?

Simply provide a redraw refresh interface to the control itself to trigger

Sample code:

/** * request a redraw */
public void redraw(a) {
  LottieAnimationView lottieAnimationView = getLottieAnimationView();
  lottieAnimationView.invalidate();
}
Copy the code

The final result

The final effect is shown below (original left image & slow down)

Program benefits

Our Lottie extension solution has two very big benefits for us

The first benefit is to improve the effect, if there is no this program, we have to return to the use of the most native code to achieve animation, the low efficiency of the experience of friends have experienced, as for the extension of the specific effect of how much is proportional to the complexity of animation, the more complex the effect is better!

The second benefit is the ability to “control” Lottie’s source code. The use of the word “control” here is a bit of an overstatement, but it is true that only when we fully understand Lottie’s implementation principles can we expand Lottie aggressively. After understanding Lottie’s implementation principles, we can modify some of Lottie’s problems and extend more features. Let’s say Lottie supports audio? Even support video resources and some more advanced capabilities!

The follow-up plan

Current scheme and some less common scene does not support, such as animation embedded in a scrolling list, such as animation block play again logic (suitable for interactive games), if there are similar in the subsequent development demand will consider the relevant scenario extension support, we will also share the synchronization scheme ideas for everyone, At the same time, the scheme will be tried out in other project teams, and there will be an open source plan after stable and mature iterations.

Hi, I am HD from Kuaishou E-commerce

Kuaishou e-commerce wireless technology team is recruiting talents 🎉🎉🎉! We are the core business line of the company, which is full of talents, opportunities and challenges. With the rapid development of the business, the team is also expanding rapidly. Welcome to join us and create world-class e-commerce products together

Hot job: Android/iOS Senior Developer, Android/iOS expert, Java Architect, Product Manager (e-commerce background), Test development… Plenty of HC waiting for you

Internal recommendation please send resume to >>> our email: [email protected] <<<, note my roster success rate is higher oh ~ 😘