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