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