• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 package com.airbnb.lottie.model.layer;
2 
3 import android.graphics.*;
4 import android.os.Build;
5 import androidx.annotation.CallSuper;
6 import androidx.annotation.FloatRange;
7 import androidx.annotation.Nullable;
8 import com.airbnb.lottie.L;
9 import com.airbnb.lottie.LottieComposition;
10 import com.airbnb.lottie.LottieDrawable;
11 import com.airbnb.lottie.animation.LPaint;
12 import com.airbnb.lottie.animation.content.Content;
13 import com.airbnb.lottie.animation.content.DrawingContent;
14 import com.airbnb.lottie.animation.keyframe.BaseKeyframeAnimation;
15 import com.airbnb.lottie.animation.keyframe.FloatKeyframeAnimation;
16 import com.airbnb.lottie.animation.keyframe.MaskKeyframeAnimation;
17 import com.airbnb.lottie.animation.keyframe.TransformKeyframeAnimation;
18 import com.airbnb.lottie.model.KeyPath;
19 import com.airbnb.lottie.model.KeyPathElement;
20 import com.airbnb.lottie.model.content.Mask;
21 import com.airbnb.lottie.model.content.ShapeData;
22 import com.airbnb.lottie.utils.Logger;
23 import com.airbnb.lottie.utils.Utils;
24 import com.airbnb.lottie.value.LottieValueCallback;
25 
26 import java.util.ArrayList;
27 import java.util.Collections;
28 import java.util.List;
29 
30 public abstract class BaseLayer
31     implements DrawingContent, BaseKeyframeAnimation.AnimationListener, KeyPathElement {
32   /**
33    * These flags were in Canvas but they were deprecated and removed.
34    * TODO: test removing these on older versions of Android.
35    */
36   private static final int CLIP_SAVE_FLAG = 0x02;
37   private static final int CLIP_TO_LAYER_SAVE_FLAG = 0x10;
38   private static final int MATRIX_SAVE_FLAG = 0x01;
39   private static final int SAVE_FLAGS = CLIP_SAVE_FLAG | CLIP_TO_LAYER_SAVE_FLAG | MATRIX_SAVE_FLAG;
40 
41   @Nullable
forModel( Layer layerModel, LottieDrawable drawable, LottieComposition composition)42   static BaseLayer forModel(
43       Layer layerModel, LottieDrawable drawable, LottieComposition composition) {
44     switch (layerModel.getLayerType()) {
45       case SHAPE:
46         return new ShapeLayer(drawable, layerModel);
47       case PRE_COMP:
48         return new CompositionLayer(drawable, layerModel,
49             composition.getPrecomps(layerModel.getRefId()), composition);
50       case SOLID:
51         return new SolidLayer(drawable, layerModel);
52       case IMAGE:
53         return new ImageLayer(drawable, layerModel);
54       case NULL:
55         return new NullLayer(drawable, layerModel);
56       case TEXT:
57         return new TextLayer(drawable, layerModel);
58       case UNKNOWN:
59       default:
60         // Do nothing
61         Logger.warning("Unknown layer type " + layerModel.getLayerType());
62         return null;
63     }
64   }
65 
66   private final Path path = new Path();
67   private final Matrix matrix = new Matrix();
68   private final Paint contentPaint = new LPaint(Paint.ANTI_ALIAS_FLAG);
69   private final Paint dstInPaint = new LPaint(Paint.ANTI_ALIAS_FLAG, PorterDuff.Mode.DST_IN);
70   private final Paint dstOutPaint = new LPaint(Paint.ANTI_ALIAS_FLAG, PorterDuff.Mode.DST_OUT);
71   private final Paint mattePaint = new LPaint(Paint.ANTI_ALIAS_FLAG);
72   private final Paint clearPaint = new LPaint(PorterDuff.Mode.CLEAR);
73   private final RectF rect = new RectF();
74   private final RectF maskBoundsRect = new RectF();
75   private final RectF matteBoundsRect = new RectF();
76   private final RectF tempMaskBoundsRect = new RectF();
77   private final String drawTraceName;
78   final Matrix boundsMatrix = new Matrix();
79   final LottieDrawable lottieDrawable;
80   final Layer layerModel;
81   @Nullable
82   private MaskKeyframeAnimation mask;
83   @Nullable
84   private BaseLayer matteLayer;
85   /**
86    * This should only be used by {@link #buildParentLayerListIfNeeded()}
87    * to construct the list of parent layers.
88    */
89   @Nullable
90   private BaseLayer parentLayer;
91   private List<BaseLayer> parentLayers;
92 
93   private final List<BaseKeyframeAnimation<?, ?>> animations = new ArrayList<>();
94   final TransformKeyframeAnimation transform;
95   private boolean visible = true;
96 
BaseLayer(LottieDrawable lottieDrawable, Layer layerModel)97   BaseLayer(LottieDrawable lottieDrawable, Layer layerModel) {
98     this.lottieDrawable = lottieDrawable;
99     this.layerModel = layerModel;
100     drawTraceName = layerModel.getName() + "#draw";
101     if (layerModel.getMatteType() == Layer.MatteType.INVERT) {
102       mattePaint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.DST_OUT));
103     } else {
104       mattePaint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.DST_IN));
105     }
106 
107     this.transform = layerModel.getTransform().createAnimation();
108     transform.addListener(this);
109 
110     if (layerModel.getMasks() != null && !layerModel.getMasks().isEmpty()) {
111       this.mask = new MaskKeyframeAnimation(layerModel.getMasks());
112       for (BaseKeyframeAnimation<?, Path> animation : mask.getMaskAnimations()) {
113         // Don't call addAnimation() because progress gets set manually in setProgress to
114         // properly handle time scale.
115         animation.addUpdateListener(this);
116       }
117       for (BaseKeyframeAnimation<Integer, Integer> animation : mask.getOpacityAnimations()) {
118         addAnimation(animation);
119         animation.addUpdateListener(this);
120       }
121     }
122     setupInOutAnimations();
123   }
124 
125   @Override
onValueChanged()126   public void onValueChanged() {
127     invalidateSelf();
128   }
129 
getLayerModel()130   Layer getLayerModel() {
131     return layerModel;
132   }
133 
setMatteLayer(@ullable BaseLayer matteLayer)134   void setMatteLayer(@Nullable BaseLayer matteLayer) {
135     this.matteLayer = matteLayer;
136   }
137 
hasMatteOnThisLayer()138   boolean hasMatteOnThisLayer() {
139     return matteLayer != null;
140   }
141 
setParentLayer(@ullable BaseLayer parentLayer)142   void setParentLayer(@Nullable BaseLayer parentLayer) {
143     this.parentLayer = parentLayer;
144   }
145 
setupInOutAnimations()146   private void setupInOutAnimations() {
147     if (!layerModel.getInOutKeyframes().isEmpty()) {
148       final FloatKeyframeAnimation inOutAnimation =
149           new FloatKeyframeAnimation(layerModel.getInOutKeyframes());
150       inOutAnimation.setIsDiscrete();
151       inOutAnimation.addUpdateListener(new BaseKeyframeAnimation.AnimationListener() {
152         @Override
153         public void onValueChanged() {
154           setVisible(inOutAnimation.getFloatValue() == 1f);
155         }
156       });
157       setVisible(inOutAnimation.getValue() == 1f);
158       addAnimation(inOutAnimation);
159     } else {
160       setVisible(true);
161     }
162   }
163 
invalidateSelf()164   private void invalidateSelf() {
165     lottieDrawable.invalidateSelf();
166   }
167 
addAnimation(@ullable BaseKeyframeAnimation<?, ?> newAnimation)168   public void addAnimation(@Nullable BaseKeyframeAnimation<?, ?> newAnimation) {
169     if (newAnimation == null) {
170       return;
171     }
172     animations.add(newAnimation);
173   }
174 
removeAnimation(BaseKeyframeAnimation<?, ?> animation)175   public void removeAnimation(BaseKeyframeAnimation<?, ?> animation) {
176     animations.remove(animation);
177   }
178 
179   @CallSuper
180   @Override
getBounds( RectF outBounds, Matrix parentMatrix, boolean applyParents)181   public void getBounds(
182       RectF outBounds, Matrix parentMatrix, boolean applyParents) {
183     rect.set(0, 0, 0, 0);
184     buildParentLayerListIfNeeded();
185     boundsMatrix.set(parentMatrix);
186 
187     if (applyParents) {
188       if (parentLayers != null) {
189         for (int i = parentLayers.size() - 1; i >= 0; i--) {
190           boundsMatrix.preConcat(parentLayers.get(i).transform.getMatrix());
191         }
192       } else if (parentLayer != null) {
193         boundsMatrix.preConcat(parentLayer.transform.getMatrix());
194       }
195     }
196 
197     boundsMatrix.preConcat(transform.getMatrix());
198   }
199 
200   @Override
draw(Canvas canvas, Matrix parentMatrix, int parentAlpha)201   public void draw(Canvas canvas, Matrix parentMatrix, int parentAlpha) {
202     L.beginSection(drawTraceName);
203     if (!visible || layerModel.isHidden()) {
204       L.endSection(drawTraceName);
205       return;
206     }
207     buildParentLayerListIfNeeded();
208     L.beginSection("Layer#parentMatrix");
209     matrix.reset();
210     matrix.set(parentMatrix);
211     for (int i = parentLayers.size() - 1; i >= 0; i--) {
212       matrix.preConcat(parentLayers.get(i).transform.getMatrix());
213     }
214     L.endSection("Layer#parentMatrix");
215     int opacity = transform.getOpacity() == null ? 100 : transform.getOpacity().getValue();
216     int alpha = (int)
217         ((parentAlpha / 255f * (float) opacity / 100f) * 255);
218     if (!hasMatteOnThisLayer() && !hasMasksOnThisLayer()) {
219       matrix.preConcat(transform.getMatrix());
220       L.beginSection("Layer#drawLayer");
221       drawLayer(canvas, matrix, alpha);
222       L.endSection("Layer#drawLayer");
223       recordRenderTime(L.endSection(drawTraceName));
224       return;
225     }
226 
227     L.beginSection("Layer#computeBounds");
228     getBounds(rect, matrix, false);
229 
230     // Uncomment this to draw matte outlines.
231     /* Paint paint = new LPaint();
232     paint.setColor(Color.RED);
233     paint.setStyle(Paint.Style.STROKE);
234     paint.setStrokeWidth(3);
235     canvas.drawRect(rect, paint); */
236 
237     intersectBoundsWithMatte(rect, parentMatrix);
238 
239     matrix.preConcat(transform.getMatrix());
240     intersectBoundsWithMask(rect, matrix);
241 
242     if (!rect.intersect(0, 0, canvas.getWidth(), canvas.getHeight())) {
243       rect.set(0, 0, 0, 0);
244     }
245 
246     L.endSection("Layer#computeBounds");
247 
248     if (!rect.isEmpty()) {
249       L.beginSection("Layer#saveLayer");
250       Utils.saveLayerCompat(canvas, rect, contentPaint);
251       L.endSection("Layer#saveLayer");
252 
253       // Clear the off screen buffer. This is necessary for some phones.
254       clearCanvas(canvas);
255       L.beginSection("Layer#drawLayer");
256       drawLayer(canvas, matrix, alpha);
257       L.endSection("Layer#drawLayer");
258 
259       if (hasMasksOnThisLayer()) {
260         applyMasks(canvas, matrix);
261       }
262 
263       if (hasMatteOnThisLayer()) {
264         L.beginSection("Layer#drawMatte");
265         L.beginSection("Layer#saveLayer");
266         Utils.saveLayerCompat(canvas, rect, mattePaint, SAVE_FLAGS);
267         L.endSection("Layer#saveLayer");
268         clearCanvas(canvas);
269         //noinspection ConstantConditions
270         matteLayer.draw(canvas, parentMatrix, alpha);
271         L.beginSection("Layer#restoreLayer");
272         canvas.restore();
273         L.endSection("Layer#restoreLayer");
274         L.endSection("Layer#drawMatte");
275       }
276 
277       L.beginSection("Layer#restoreLayer");
278       canvas.restore();
279       L.endSection("Layer#restoreLayer");
280     }
281 
282     recordRenderTime(L.endSection(drawTraceName));
283   }
284 
recordRenderTime(float ms)285   private void recordRenderTime(float ms) {
286     lottieDrawable.getComposition()
287         .getPerformanceTracker().recordRenderTime(layerModel.getName(), ms);
288 
289   }
290 
clearCanvas(Canvas canvas)291   private void clearCanvas(Canvas canvas) {
292     L.beginSection("Layer#clearLayer");
293     // If we don't pad the clear draw, some phones leave a 1px border of the graphics buffer.
294     canvas.drawRect(rect.left - 1, rect.top - 1, rect.right + 1, rect.bottom + 1, clearPaint);
295     L.endSection("Layer#clearLayer");
296   }
297 
intersectBoundsWithMask(RectF rect, Matrix matrix)298   private void intersectBoundsWithMask(RectF rect, Matrix matrix) {
299     maskBoundsRect.set(0, 0, 0, 0);
300     if (!hasMasksOnThisLayer()) {
301       return;
302     }
303     //noinspection ConstantConditions
304     int size = mask.getMasks().size();
305     for (int i = 0; i < size; i++) {
306       Mask mask = this.mask.getMasks().get(i);
307       BaseKeyframeAnimation<?, Path> maskAnimation = this.mask.getMaskAnimations().get(i);
308       Path maskPath = maskAnimation.getValue();
309       path.set(maskPath);
310       path.transform(matrix);
311 
312       switch (mask.getMaskMode()) {
313         case MASK_MODE_NONE:
314           // Mask mode none will just render the original content so it is the whole bounds.
315           return;
316         case MASK_MODE_SUBTRACT:
317           // If there is a subtract mask, the mask could potentially be the size of the entire
318           // canvas so we can't use the mask bounds.
319           return;
320         case MASK_MODE_INTERSECT:
321         case MASK_MODE_ADD:
322           if (mask.isInverted()) {
323             return;
324           }
325         default:
326           path.computeBounds(tempMaskBoundsRect, false);
327           // As we iterate through the masks, we want to calculate the union region of the masks.
328           // We initialize the rect with the first mask. If we don't call set() on the first call,
329           // the rect will always extend to (0,0).
330           if (i == 0) {
331             maskBoundsRect.set(tempMaskBoundsRect);
332           } else {
333             maskBoundsRect.set(
334                 Math.min(maskBoundsRect.left, tempMaskBoundsRect.left),
335                 Math.min(maskBoundsRect.top, tempMaskBoundsRect.top),
336                 Math.max(maskBoundsRect.right, tempMaskBoundsRect.right),
337                 Math.max(maskBoundsRect.bottom, tempMaskBoundsRect.bottom)
338             );
339           }
340       }
341     }
342 
343     boolean intersects = rect.intersect(maskBoundsRect);
344     if (!intersects) {
345       rect.set(0f, 0f, 0f, 0f);
346     }
347   }
348 
intersectBoundsWithMatte(RectF rect, Matrix matrix)349   private void intersectBoundsWithMatte(RectF rect, Matrix matrix) {
350     if (!hasMatteOnThisLayer()) {
351       return;
352     }
353 
354     if (layerModel.getMatteType() == Layer.MatteType.INVERT) {
355       // We can't trim the bounds if the mask is inverted since it extends all the way to the
356       // composition bounds.
357       return;
358     }
359     //noinspection ConstantConditions
360     matteBoundsRect.set(0f, 0f, 0f, 0f);
361     matteLayer.getBounds(matteBoundsRect, matrix, true);
362     boolean intersects = rect.intersect(matteBoundsRect);
363     if (!intersects) {
364       rect.set(0f, 0f, 0f, 0f);
365     }
366   }
367 
drawLayer(Canvas canvas, Matrix parentMatrix, int parentAlpha)368   abstract void drawLayer(Canvas canvas, Matrix parentMatrix, int parentAlpha);
369 
applyMasks(Canvas canvas, Matrix matrix)370   private void applyMasks(Canvas canvas, Matrix matrix) {
371     L.beginSection("Layer#saveLayer");
372     Utils.saveLayerCompat(canvas, rect, dstInPaint, SAVE_FLAGS);
373     if (Build.VERSION.SDK_INT < Build.VERSION_CODES.P) {
374       // Pre-Pie, offscreen buffers were opaque which meant that outer border of a mask
375       // might get drawn depending on the result of float rounding.
376       canvas.drawColor(Color.TRANSPARENT);
377     }
378     L.endSection("Layer#saveLayer");
379     for (int i = 0; i < mask.getMasks().size(); i++) {
380       Mask mask = this.mask.getMasks().get(i);
381       BaseKeyframeAnimation<ShapeData, Path> maskAnimation = this.mask.getMaskAnimations().get(i);
382       BaseKeyframeAnimation<Integer, Integer> opacityAnimation = this.mask.getOpacityAnimations().get(i);
383       switch (mask.getMaskMode()) {
384         case MASK_MODE_NONE:
385           // None mask should have no effect. If all masks are NONE, fill the
386           // mask canvas with a rectangle so it fully covers the original layer content.
387           // However, if there are other masks, they should be the only ones that have an effect so
388           // this should noop.
389           if (areAllMasksNone()) {
390             contentPaint.setAlpha(255);
391             canvas.drawRect(rect, contentPaint);
392           }
393           break;
394         case MASK_MODE_ADD:
395           if (mask.isInverted()) {
396             applyInvertedAddMask(canvas, matrix, mask, maskAnimation, opacityAnimation);
397           } else {
398             applyAddMask(canvas, matrix, mask, maskAnimation, opacityAnimation);
399           }
400           break;
401         case MASK_MODE_SUBTRACT:
402           if (i == 0) {
403             contentPaint.setColor(Color.BLACK);
404             contentPaint.setAlpha(255);
405             canvas.drawRect(rect, contentPaint);
406           }
407           if (mask.isInverted()) {
408             applyInvertedSubtractMask(canvas, matrix, mask, maskAnimation, opacityAnimation);
409           } else {
410             applySubtractMask(canvas, matrix, mask, maskAnimation, opacityAnimation);
411           }
412           break;
413         case MASK_MODE_INTERSECT:
414           if (mask.isInverted()) {
415             applyInvertedIntersectMask(canvas, matrix, mask, maskAnimation, opacityAnimation);
416           } else {
417             applyIntersectMask(canvas, matrix, mask, maskAnimation, opacityAnimation);
418           }
419           break;
420       }
421     }
422     L.beginSection("Layer#restoreLayer");
423     canvas.restore();
424     L.endSection("Layer#restoreLayer");
425   }
426 
areAllMasksNone()427   private boolean areAllMasksNone() {
428     if (mask.getMaskAnimations().isEmpty()) {
429       return false;
430     }
431     boolean areAllMasksNone = true;
432     for (int i = 0; i < mask.getMasks().size(); i++) {
433       if (mask.getMasks().get(i).getMaskMode() != Mask.MaskMode.MASK_MODE_NONE) {
434         return false;
435       }
436     }
437     return true;
438   }
439 
applyAddMask(Canvas canvas, Matrix matrix, Mask mask, BaseKeyframeAnimation<ShapeData, Path> maskAnimation, BaseKeyframeAnimation<Integer, Integer> opacityAnimation)440   private void applyAddMask(Canvas canvas, Matrix matrix, Mask mask,
441       BaseKeyframeAnimation<ShapeData, Path> maskAnimation, BaseKeyframeAnimation<Integer, Integer> opacityAnimation) {
442     Path maskPath = maskAnimation.getValue();
443     path.set(maskPath);
444     path.transform(matrix);
445     contentPaint.setAlpha((int) (opacityAnimation.getValue() * 2.55f));
446     canvas.drawPath(path, contentPaint);
447   }
448 
applyInvertedAddMask(Canvas canvas, Matrix matrix, Mask mask, BaseKeyframeAnimation<ShapeData, Path> maskAnimation, BaseKeyframeAnimation<Integer, Integer> opacityAnimation)449   private void applyInvertedAddMask(Canvas canvas, Matrix matrix, Mask mask,
450       BaseKeyframeAnimation<ShapeData, Path> maskAnimation, BaseKeyframeAnimation<Integer, Integer> opacityAnimation) {
451     Utils.saveLayerCompat(canvas, rect, contentPaint);
452     canvas.drawRect(rect, contentPaint);
453     Path maskPath = maskAnimation.getValue();
454     path.set(maskPath);
455     path.transform(matrix);
456     contentPaint.setAlpha((int) (opacityAnimation.getValue() * 2.55f));
457     canvas.drawPath(path, dstOutPaint);
458     canvas.restore();
459   }
460 
applySubtractMask(Canvas canvas, Matrix matrix, Mask mask, BaseKeyframeAnimation<ShapeData, Path> maskAnimation, BaseKeyframeAnimation<Integer, Integer> opacityAnimation)461   private void applySubtractMask(Canvas canvas, Matrix matrix, Mask mask,
462       BaseKeyframeAnimation<ShapeData, Path> maskAnimation, BaseKeyframeAnimation<Integer, Integer> opacityAnimation) {
463     Path maskPath = maskAnimation.getValue();
464     path.set(maskPath);
465     path.transform(matrix);
466     canvas.drawPath(path, dstOutPaint);
467   }
468 
applyInvertedSubtractMask(Canvas canvas, Matrix matrix, Mask mask, BaseKeyframeAnimation<ShapeData, Path> maskAnimation, BaseKeyframeAnimation<Integer, Integer> opacityAnimation)469   private void applyInvertedSubtractMask(Canvas canvas, Matrix matrix, Mask mask,
470       BaseKeyframeAnimation<ShapeData, Path> maskAnimation, BaseKeyframeAnimation<Integer, Integer> opacityAnimation) {
471     Utils.saveLayerCompat(canvas, rect, dstOutPaint);
472     canvas.drawRect(rect, contentPaint);
473     dstOutPaint.setAlpha((int) (opacityAnimation.getValue() * 2.55f));
474     Path maskPath = maskAnimation.getValue();
475     path.set(maskPath);
476     path.transform(matrix);
477     canvas.drawPath(path, dstOutPaint);
478     canvas.restore();
479   }
480 
applyIntersectMask(Canvas canvas, Matrix matrix, Mask mask, BaseKeyframeAnimation<ShapeData, Path> maskAnimation, BaseKeyframeAnimation<Integer, Integer> opacityAnimation)481   private void applyIntersectMask(Canvas canvas, Matrix matrix, Mask mask,
482       BaseKeyframeAnimation<ShapeData, Path> maskAnimation, BaseKeyframeAnimation<Integer, Integer> opacityAnimation) {
483     Utils.saveLayerCompat(canvas, rect, dstInPaint);
484     Path maskPath = maskAnimation.getValue();
485     path.set(maskPath);
486     path.transform(matrix);
487     contentPaint.setAlpha((int) (opacityAnimation.getValue() * 2.55f));
488     canvas.drawPath(path, contentPaint);
489     canvas.restore();
490   }
491 
applyInvertedIntersectMask(Canvas canvas, Matrix matrix, Mask mask, BaseKeyframeAnimation<ShapeData, Path> maskAnimation, BaseKeyframeAnimation<Integer, Integer> opacityAnimation)492   private void applyInvertedIntersectMask(Canvas canvas, Matrix matrix, Mask mask,
493       BaseKeyframeAnimation<ShapeData, Path> maskAnimation, BaseKeyframeAnimation<Integer, Integer> opacityAnimation) {
494     Utils.saveLayerCompat(canvas, rect, dstInPaint);
495     canvas.drawRect(rect, contentPaint);
496     dstOutPaint.setAlpha((int) (opacityAnimation.getValue() * 2.55f));
497     Path maskPath = maskAnimation.getValue();
498     path.set(maskPath);
499     path.transform(matrix);
500     canvas.drawPath(path, dstOutPaint);
501     canvas.restore();
502   }
503 
hasMasksOnThisLayer()504   boolean hasMasksOnThisLayer() {
505     return mask != null && !mask.getMaskAnimations().isEmpty();
506   }
507 
setVisible(boolean visible)508   private void setVisible(boolean visible) {
509     if (visible != this.visible) {
510       this.visible = visible;
511       invalidateSelf();
512     }
513   }
514 
setProgress(@loatRangefrom = 0f, to = 1f) float progress)515   void setProgress(@FloatRange(from = 0f, to = 1f) float progress) {
516     // Time stretch should not be applied to the layer transform.
517     transform.setProgress(progress);
518     if (mask != null) {
519       for (int i = 0; i < mask.getMaskAnimations().size(); i++) {
520         mask.getMaskAnimations().get(i).setProgress(progress);
521       }
522     }
523     if (layerModel.getTimeStretch() != 0) {
524       progress /= layerModel.getTimeStretch();
525     }
526     if (matteLayer != null) {
527       // The matte layer's time stretch is pre-calculated.
528       float matteTimeStretch = matteLayer.layerModel.getTimeStretch();
529       matteLayer.setProgress(progress * matteTimeStretch);
530     }
531     for (int i = 0; i < animations.size(); i++) {
532       animations.get(i).setProgress(progress);
533     }
534   }
535 
buildParentLayerListIfNeeded()536   private void buildParentLayerListIfNeeded() {
537     if (parentLayers != null) {
538       return;
539     }
540     if (parentLayer == null) {
541       parentLayers = Collections.emptyList();
542       return;
543     }
544 
545     parentLayers = new ArrayList<>();
546     BaseLayer layer = parentLayer;
547     while (layer != null) {
548       parentLayers.add(layer);
549       layer = layer.parentLayer;
550     }
551   }
552 
553   @Override
getName()554   public String getName() {
555     return layerModel.getName();
556   }
557 
558   @Override
setContents(List<Content> contentsBefore, List<Content> contentsAfter)559   public void setContents(List<Content> contentsBefore, List<Content> contentsAfter) {
560     // Do nothing
561   }
562 
563   @Override
resolveKeyPath( KeyPath keyPath, int depth, List<KeyPath> accumulator, KeyPath currentPartialKeyPath)564   public void resolveKeyPath(
565       KeyPath keyPath, int depth, List<KeyPath> accumulator, KeyPath currentPartialKeyPath) {
566     if (!keyPath.matches(getName(), depth)) {
567       return;
568     }
569 
570     if (!"__container".equals(getName())) {
571       currentPartialKeyPath = currentPartialKeyPath.addKey(getName());
572 
573       if (keyPath.fullyResolvesTo(getName(), depth)) {
574         accumulator.add(currentPartialKeyPath.resolve(this));
575       }
576     }
577 
578     if (keyPath.propagateToChildren(getName(), depth)) {
579       int newDepth = depth + keyPath.incrementDepthBy(getName(), depth);
580       resolveChildKeyPath(keyPath, newDepth, accumulator, currentPartialKeyPath);
581     }
582   }
583 
resolveChildKeyPath( KeyPath keyPath, int depth, List<KeyPath> accumulator, KeyPath currentPartialKeyPath)584   void resolveChildKeyPath(
585       KeyPath keyPath, int depth, List<KeyPath> accumulator, KeyPath currentPartialKeyPath) {
586   }
587 
588   @CallSuper
589   @Override
addValueCallback(T property, @Nullable LottieValueCallback<T> callback)590   public <T> void addValueCallback(T property, @Nullable LottieValueCallback<T> callback) {
591     transform.applyValueCallback(property, callback);
592   }
593 }
594