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