0 x0 background

Hernan Torrisi created the Bodymovin plugin in 2015 to allow AE to export JSON described animations. In 2017, Airbnb engineers wrote iOS and Android libraries that could render the JSON file, and Lottie became a popular choice for complex animations on mobile clients.

In general, the elements and styles of the animation are uniquely determined by the Lottie description file and cannot be changed once the output is delivered. In common Lottie usage scenarios, canvas elements are fixed and some elements have animation effects, such as the GIF of alipay’s New Year red envelope and the atmosphere on the home page of Singles’ Day.

In marketing activities, it is very common to see dazzling red envelope pop-ups. Beautiful animations have obvious effects on click rate and verification rate. For example, in the example above, the complex comprehensive animation effects of multiple elements such as displacement, scaling, dazzle and rebound are very beautiful and smooth. For such complex animations, the communication and coding costs directly realized by UIKit or CoreAnimation are huge, and it is almost the only choice for motion designers to deliver Lottie using AE.

The first problem we encountered was that the display data was delivered by the server, and different users presented different content. In order to realize the animation requirements of this scene, this paper does some exploration based on Lottie, hoping to give readers some reference.

0x1 Scheme exploration

  • 1. Animation is used for fixed elements, while original and hard combination are used for different elements
    • Cons: It takes a lot of time to calculate and debug when native controls should be displayed and how to fit the behavior trajectory of other elements of Lottie animation.
  • 2. The Lottie file reserves placeholders for the differential data, and the server data is returned to replace the placeholder string in the file
    • Disadvantages: There are a lot of text contents that need to be replaced, some of which need to be replaced are pictures, which increases the design cost and replacement understanding cost of Lottie manuscript
  • 3. Mix render in the same layer
    • Of the first two schemes, we used the first scheme in production and felt a lot of inconvenience, and gave up the second scheme after evaluation. For the idea of same-layer mixed rendering, on the one hand, inspired by the small program, the small program can mix UI controls and Webkit DOM elements well, Lottie itself is a Native implementation of the theory can work better with UI controls.

0x2 Same-layer interface rendering

1. Pre-knowledge

  • View structure of Lottie animation control

The Lottie animation flattens all the elements and draws them in the same UIView subclass that doesn’t have any subviews. However, the CALayer hierarchy is very rich. For example, the boat loading animation in the figure below contains 271 Calayers.

  • LottieSDK holds the relationship between JSON layer conversion strategy and layer

After effects layers include normal layers in the stage and pre-composable layers that are not in the stage. After being parsed by the client, they correspond to instances of the iOS CALayer class. LottieView’s compContainer acts as the root Layer and holds all other layers directly or indirectly. The first level of sublayer is stored in the properties childLayers and childMap. The childLayers element also has the childLayers and childMap properties to hold other layers referenced or contained.

  • The composition of iOS view controls

In the iOS native framework UIKit, all view controls inherit from the UIView class, which consists of CALayer+UIResponder touch response. When no interaction is required, CALayer can do all the visual rendering that UIView can do. UIView holds a CALayer, and a CALayer’s delegate points to UIView.

2. Implementation

As mentioned earlier, CALayer is responsible for the presentation part of UIView. Add a custom drawn view’s CALayer, such as CustomedView. layer, directly to the layer where the image to be replaced is specified using the system method -[CALayer addSublayer:].

  • LottieSDK ability

Reality. IO/Lottie / # / IO…

Existing problems:

1. Since the SDK adds custom View methods, “. It is used to indicate the depth of search, such as “layer.shape group.stroke 1.Color “. Therefore, when searching layers, the part after the “.” in the Layer name is ignored, that is, the Layer name cannot be” card.png “.

2. The search path only supports normal layers in the stage and cannot find pre-composed layers. Git Issue has no fix plan: github.com/airbnb/lott…

  • Same-level capability expansion

In View of the defects supported by SDK, it is necessary to expand the development and add the method interface of adding custom View. The layer adding logic is the same as the SDK method, mainly changing the target layer search method.

As shown in the following code, first search the target layer with LottieSDK method. If no search is found, recursively traverse the sub-layer tree (including the pre-composite layer) held by Lottie root layer.

- (void)addSubview:(nonnull UIView *)view withLayerName:(nonnull NSString *)layerName { CALayer *layer = [self searchWithLayerName:layerName]; if (layer) { [self _layoutAndForceUpdate]; CGRect viewRect = view.frame; LOTView *wrapperView = [[LOTView alloc] initWithFrame:viewRect]; view.frame = view.bounds; view.autoresizingMask = UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight; [wrapperView addSubview:view]; [self addSubview:wrapperView]; layer.contents = nil; [layer addSublayer:wrapperView.layer]; [self.mixLayers addObject:view.layer]; } } - (CALayer *)searchWithLayerName:(NSString *)layerName { CALayer *layer = [self normalSearchWithLayerName:layerName]; if (! layer) { layer = [self customSearchWithLayerName:layerName]; } return layer; } - (CALayer *)normalSearchWithLayerName:(NSString *)layerName { LOTCompositionContainer *_compContainer = [self valueForKey:@"_compContainer"]; LOTKeypath *keypath = [LOTKeypath keypathWithString:layerName]; CALayer *layer = [_compContainer _layerForKeypath:keypath]; return layer; } - (CALayer *)customSearchWithLayerName:(NSString *)layerName { LOTCompositionContainer *_compContainer = [self valueForKey:@"_compContainer"]; LOTLayerContainer *layerContainer = [self traversLayer:_compContainer withLayerName:layerName]; return layerContainer.wrapperLayer; } - (LOTLayerContainer *)traversLayer:(LOTCompositionContainer *)layer withLayerName:(NSString *)layerName { if ([layer.layerName isEqualToString:layerName]) { return layer; } else if ([layer isKindOfClass:[LOTCompositionContainer class]]) { for (LOTCompositionContainer *sublayer in layer.childLayers) { LOTLayerContainer *targetLayer = [self traversLayer:sublayer withLayerName:layerName]; if (targetLayer) { return targetLayer; } } } return nil; }Copy the code

0x3 Elements of the same level interact

IOS click response mainly relies on the delivery chain of hit-testing events, so as long as the click area is determined to be in the Layer of the custom View when clicking LottieView (The LottieView View hierarchy is all Layer, without embedding the custom View itself), Pass the hit-testing event directly to the Layer’s Delegate custom View, which then passes the hit-testing event internally.

As mentioned above, the key problem is “how to determine if the click area is in the custom View when clicking LottieView”.

1. Click the region to determine

  • Scheme 1 using iOS system method -(CALayer *)[CALayer hitTest:(CGPoint)]

Using the system method can ensure that the Layer to be touched is found accurately. Problem: When the custom View has a transparent Layer or PNG image Layer with transparent area on top of the Layer, the custom View cannot hit the query (the button can be seen but cannot be clicked on in response, which is against the user’s intuition).

- (UIView *)hitTest:(CGPoint)point withEvent:(UIEvent *)event
{
     CGPoint newPoint = [self convertPoint:point toView:self.superview];
     CALayer *layer = [self.layer.presentationLayer hitTest:newPoint].modelLayer;
     UIView *view = (UIView *)layer.delegate;
     if ([layer.delegate isKindOfClass:[UIView class]]) {
         return [view hitTest:newPoint withEvent:event];;
     }
     return [super hitTest:point withEvent:event];
}
Copy the code
  • Scheme 2 only iterates through the added custom views to judge the inclusion relationship of CGPoints

As shown in the code, the added custom view is judged in reverse order, that is, when the touch point is in more than one custom view, the newly added custom view receives the click. Problem: When the custom view is obscured by the visible Lottie layer but the touch point is inside the custom view, it is misinterpreted as the custom view responding to the click (invisible button responding to the click, contrary to the user’s intuition).

- (UIView *)hitTest:(CGPoint)point withEvent:(UIEvent *)event { NSArray *reversedItems = [[self.mixLayers reverseObjectEnumerator] allObjects]; for (CALayer *layer in reversedItems) { CGPoint newPoint = [self.layer.presentationLayer convertPoint:point toLayer:layer.presentationLayer]; if ([layer.presentationLayer containsPoint:newPoint] && [layer.delegate isKindOfClass:[UIView class]]) { return [(UIView  *)layer.delegate hitTest:newPoint withEvent:event]; } } return [super hitTest:point withEvent:event]; }Copy the code
  • Scheme selection

After internal discussion, we adopted plan 2. The problem of “invisible button responds to the click” is solved. On the one hand, the dynamic effect designer ensures that the interactive control only moves to the stage when it is at the top level, and on the other hand, the r&d students deal with it in the logic of receiving the click. To determine the current Lottie animation’s broadcast frame (NSNumber *) LOTAnimationView. CurrentFrame, decide whether to perform a logical response.

2. Transparent control processing

In practice, we encountered a problem where the motion designer only set the transparency of the button to 0 after the button was clicked, but the position and size remained unchanged. So in the following code, we add the transparency judgment.

- (UIView *)hitTest:(CGPoint)point withEvent:(UIEvent *)event { NSArray *reversedItems = [[self.mixLayers reverseObjectEnumerator] allObjects]; for (CALayer *layer in reversedItems) { CGPoint newPoint = [self.layer.presentationLayer convertPoint:point toLayer:layer.presentationLayer]; CALayer *origLayer = layer.superlayer.superlayer; BOOL ignore4Opacity = NO; while (origLayer ! = self. Layer && origLayer. AllowsGroupOpacity) {if (origLayer. Opacity < 0.1) {ignore4Opacity = YES; break; } origLayer = origLayer.superlayer; } if (! ignore4Opacity && [layer.presentationLayer containsPoint:newPoint] && [layer.delegate isKindOfClass:[UIView class]]) { return [(UIView *)layer.delegate hitTest:newPoint withEvent:event]; } } return [super hitTest:point withEvent:event]; }Copy the code

0 x4 summary

1. Implementation example

In the above video, the original Lottie file is on the left and the layer implementation is on the right. As you can see, even on a frame-by-frame basis, the custom added View has fine track synchronization with other animation elements, and the button controls respond properly to clicks.

2. Restrictions on usage scenarios

  • When the touch point is in more than one custom view, the custom view added later receives the click.
  • When the custom view is obscured by the visible Lottie layer, but the touch point is inside the custom view, it may be mistaken as if the custom view needs to respond to the click (the invisible button responds to the click, contrary to the user’s intuition). In this case, the business determines whether to perform the response logic in target-Action based on the current playing frame of the animation.
  • At present, just verify UIButton button click, register target-Action in the custom view, the code is the same as the native development.

3. Follow-up planning

  • Explore if there is a better way to handle the click area
  • Validate richer interaction scenarios, such as long lists, players, and so on

Hi, I’m Glen 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 ~ 😘