--wip-- [skip ci]
diff --git a/.idea/codeStyles/Project.xml b/.idea/codeStyles/Project.xml
index 0e53a14..3a45825 100644
--- a/.idea/codeStyles/Project.xml
+++ b/.idea/codeStyles/Project.xml
@@ -1,5 +1,6 @@
 <component name="ProjectCodeStyleConfiguration">
   <code_scheme name="Project" version="173">
+    <option name="ENABLE_SECOND_REFORMAT" value="true" />
     <JavaCodeStyleSettings>
       <option name="DO_NOT_WRAP_AFTER_SINGLE_ANNOTATION" value="true" />
       <option name="ANNOTATION_PARAMETER_WRAP" value="1" />
diff --git a/.idea/inspectionProfiles/Project_Default.xml b/.idea/inspectionProfiles/Project_Default.xml
index 601ecd9..5e186a4 100644
--- a/.idea/inspectionProfiles/Project_Default.xml
+++ b/.idea/inspectionProfiles/Project_Default.xml
@@ -2,6 +2,7 @@
   <profile version="1.0">
     <option name="myName" value="Project Default" />
     <inspection_tool class="DeprecatedCallableAddReplaceWith" enabled="false" level="INFO" enabled_by_default="false" />
+    <inspection_tool class="EnhancedSwitchMigration" enabled="false" level="WARNING" enabled_by_default="false" />
     <inspection_tool class="EqualsReplaceableByObjectsCall" enabled="false" level="WEAK WARNING" enabled_by_default="false" />
     <inspection_tool class="ForCanBeForeach" enabled="false" level="WARNING" enabled_by_default="false">
       <option name="REPORT_INDEXED_LOOP" value="false" />
diff --git a/issue-repro-compose/src/main/java/com/airbnb/lottie/issues/compose/ComposeIssueReproActivity.kt b/issue-repro-compose/src/main/java/com/airbnb/lottie/issues/compose/ComposeIssueReproActivity.kt
index abf6dc2..f4f7b01 100755
--- a/issue-repro-compose/src/main/java/com/airbnb/lottie/issues/compose/ComposeIssueReproActivity.kt
+++ b/issue-repro-compose/src/main/java/com/airbnb/lottie/issues/compose/ComposeIssueReproActivity.kt
@@ -21,7 +21,7 @@
 
     @Composable
     fun Content() {
-        val composition by rememberLottieComposition(LottieCompositionSpec.RawRes(R.raw.heart))
+        val composition by rememberLottieComposition(LottieCompositionSpec.RawRes(R.raw.color))
         val progress by animateLottieCompositionAsState(composition, iterations = LottieConstants.IterateForever)
         LottieAnimation(composition, { progress })
     }
diff --git a/issue-repro-compose/src/main/res/raw/color.json b/issue-repro-compose/src/main/res/raw/color.json
new file mode 100644
index 0000000..a0c1a15
--- /dev/null
+++ b/issue-repro-compose/src/main/res/raw/color.json
@@ -0,0 +1 @@
+{"v":"5.10.2","fr":60,"ip":0,"op":60,"w":256,"h":256,"nm":"Comp 1","ddd":0,"assets":[],"layers":[{"ddd":0,"ind":1,"ty":4,"nm":"Shape Layer 1","sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[128,128,0],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ty":"rc","d":1,"s":{"a":0,"k":[174.203,174.203],"ix":2},"p":{"a":0,"k":[0,0],"ix":3},"r":{"a":0,"k":0,"ix":4},"nm":"Rectangle Path 1","mn":"ADBE Vector Shape - Rect","hd":false},{"ty":"fl","c":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":0,"s":[1,0,0,1]},{"t":59,"s":[0.056433867663,0,1,1]}],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[1.102,7.102],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Rectangle 1","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":60,"st":0,"ct":1,"bm":0}],"markers":[]}
\ No newline at end of file
diff --git a/lottie/src/main/java/com/airbnb/lottie/LottieDrawable.java b/lottie/src/main/java/com/airbnb/lottie/LottieDrawable.java
index e493288..3edc1d9 100644
--- a/lottie/src/main/java/com/airbnb/lottie/LottieDrawable.java
+++ b/lottie/src/main/java/com/airbnb/lottie/LottieDrawable.java
@@ -515,7 +515,7 @@
       return;
     }
     compositionLayer = new CompositionLayer(
-        this, LayerParser.parse(composition), composition.getLayers(), composition);
+        this, null, LayerParser.parse(composition), composition.getLayers(), composition);
     if (outlineMasksAndMattes) {
       compositionLayer.setOutlineMasksAndMattes(true);
     }
diff --git a/lottie/src/main/java/com/airbnb/lottie/animation/content/Content.java b/lottie/src/main/java/com/airbnb/lottie/animation/content/Content.java
index e8801a8..15998c0 100644
--- a/lottie/src/main/java/com/airbnb/lottie/animation/content/Content.java
+++ b/lottie/src/main/java/com/airbnb/lottie/animation/content/Content.java
@@ -5,5 +5,7 @@
 public interface Content {
   String getName();
 
+  void updateDirtyNodes();
+
   void setContents(List<Content> contentsBefore, List<Content> contentsAfter);
 }
diff --git a/lottie/src/main/java/com/airbnb/lottie/animation/content/FillContent.java b/lottie/src/main/java/com/airbnb/lottie/animation/content/FillContent.java
index 46732a8..94b274d 100644
--- a/lottie/src/main/java/com/airbnb/lottie/animation/content/FillContent.java
+++ b/lottie/src/main/java/com/airbnb/lottie/animation/content/FillContent.java
@@ -31,6 +31,11 @@
 
 public class FillContent
     implements DrawingContent, BaseKeyframeAnimation.AnimationListener, KeyPathElementContent {
+  private static final int DIRTY_FLAG_COLOR = 1;
+  private static final int DIRTY_FLAG_OPACITY = 1 << 1;
+  private static final int DIRTY_FLAG_COLOR_FILTER = 1 << 2;
+  private static final int DIRTY_FLAG_BLUR = 1 << 3;
+  private static final int DIRTY_FLAG_DROP_SHADOW = 1 << 3;
   private final Path path = new Path();
   private final Paint paint = new LPaint(Paint.ANTI_ALIAS_FLAG);
   private final BaseLayer layer;
@@ -46,6 +51,8 @@
 
   @Nullable private DropShadowKeyframeAnimation dropShadowAnimation;
 
+  private int dirtyFlags = 0;
+
   public FillContent(final LottieDrawable lottieDrawable, BaseLayer layer, ShapeFill fill) {
     this.layer = layer;
     name = fill.getName();
@@ -53,7 +60,7 @@
     this.lottieDrawable = lottieDrawable;
     if (layer.getBlurEffect() != null) {
       blurAnimation = layer.getBlurEffect().getBlurriness().createAnimation();
-      blurAnimation.addUpdateListener(this);
+      blurAnimation.addUpdateListener(() -> setDirty(DIRTY_FLAG_BLUR));
       layer.addAnimation(blurAnimation);
     }
     if (layer.getDropShadowEffect() != null) {
@@ -80,6 +87,14 @@
     lottieDrawable.invalidateSelf();
   }
 
+  @Override public void updateDirtyNodes() {
+    if (isDirty(DIRTY_FLAG_COLOR | DIRTY_FLAG_OPACITY)) {
+      int color = ((ColorKeyframeAnimation) this.colorAnimation).getIntValue();
+      int alpha = (int) ((parentAlpha / 255f * opacityAnimation.getValue() / 100f) * 255);
+      paint.setColor((clamp(alpha, 0, 255) << 24) | (color & 0xFFFFFF));
+    }
+  }
+
   @Override public void setContents(List<Content> contentsBefore, List<Content> contentsAfter) {
     for (int i = 0; i < contentsAfter.size(); i++) {
       Content content = contentsAfter.get(i);
@@ -98,9 +113,6 @@
       return;
     }
     L.beginSection("FillContent#draw");
-    int color = ((ColorKeyframeAnimation) this.colorAnimation).getIntValue();
-    int alpha = (int) ((parentAlpha / 255f * opacityAnimation.getValue() / 100f) * 255);
-    paint.setColor((clamp(alpha, 0, 255) << 24) | (color & 0xFFFFFF));
 
     if (colorFilterAnimation != null) {
       paint.setColorFilter(colorFilterAnimation.getValue());
@@ -191,4 +203,13 @@
       dropShadowAnimation.setRadiusCallback((LottieValueCallback<Float>) callback);
     }
   }
+
+  private void setDirty(int flag) {
+    dirtyFlags |= flag;
+    layer.onValueChanged();
+  }
+
+  private boolean isDirty(int flag) {
+    return (dirtyFlags & flag) != 0;
+  }
 }
diff --git a/lottie/src/main/java/com/airbnb/lottie/animation/content/ShapeContent.java b/lottie/src/main/java/com/airbnb/lottie/animation/content/ShapeContent.java
index 1f54ac0..a37ef4c 100644
--- a/lottie/src/main/java/com/airbnb/lottie/animation/content/ShapeContent.java
+++ b/lottie/src/main/java/com/airbnb/lottie/animation/content/ShapeContent.java
@@ -19,7 +19,7 @@
 
   private final String name;
   private final boolean hidden;
-  private final LottieDrawable lottieDrawable;
+  private final BaseLayer layer;
   private final ShapeKeyframeAnimation shapeAnimation;
   @Nullable private List<ShapeModifierContent> shapeModifierContents;
 
@@ -29,19 +29,15 @@
   public ShapeContent(LottieDrawable lottieDrawable, BaseLayer layer, ShapePath shape) {
     name = shape.getName();
     hidden = shape.isHidden();
-    this.lottieDrawable = lottieDrawable;
+    this.layer = layer;
     shapeAnimation = shape.getShapePath().createAnimation();
     layer.addAnimation(shapeAnimation);
     shapeAnimation.addUpdateListener(this);
   }
 
   @Override public void onValueChanged() {
-    invalidate();
-  }
-
-  private void invalidate() {
     isPathValid = false;
-    lottieDrawable.invalidateSelf();
+    layer.onValueChanged();
   }
 
   @Override public void setContents(List<Content> contentsBefore, List<Content> contentsAfter) {
diff --git a/lottie/src/main/java/com/airbnb/lottie/model/layer/BaseLayer.java b/lottie/src/main/java/com/airbnb/lottie/model/layer/BaseLayer.java
index 5775c18..582bb3f 100644
--- a/lottie/src/main/java/com/airbnb/lottie/model/layer/BaseLayer.java
+++ b/lottie/src/main/java/com/airbnb/lottie/model/layer/BaseLayer.java
@@ -39,8 +39,7 @@
 import java.util.Collections;
 import java.util.List;
 
-public abstract class BaseLayer
-    implements DrawingContent, BaseKeyframeAnimation.AnimationListener, KeyPathElement {
+public abstract class BaseLayer implements DrawingContent, BaseKeyframeAnimation.AnimationListener, KeyPathElement {
   /**
    * These flags were in Canvas but they were deprecated and removed.
    * TODO: test removing these on older versions of Android.
@@ -50,23 +49,22 @@
   private static final int MATRIX_SAVE_FLAG = 0x01;
   private static final int SAVE_FLAGS = CLIP_SAVE_FLAG | CLIP_TO_LAYER_SAVE_FLAG | MATRIX_SAVE_FLAG;
 
-  @Nullable
-  static BaseLayer forModel(
-      CompositionLayer compositionLayer, Layer layerModel, LottieDrawable drawable, LottieComposition composition) {
+  private static final int DIRTY_FLAG_CHILD_LAYER = 1;
+
+  @Nullable static BaseLayer forModel(CompositionLayer compositionLayer, Layer layerModel, LottieDrawable drawable, LottieComposition composition) {
     switch (layerModel.getLayerType()) {
       case SHAPE:
         return new ShapeLayer(drawable, layerModel, compositionLayer, composition);
       case PRE_COMP:
-        return new CompositionLayer(drawable, layerModel,
-            composition.getPrecomps(layerModel.getRefId()), composition);
+        return new CompositionLayer(drawable, compositionLayer, layerModel, composition.getPrecomps(layerModel.getRefId()), composition);
       case SOLID:
-        return new SolidLayer(drawable, layerModel);
+        return new SolidLayer(drawable, compositionLayer, layerModel);
       case IMAGE:
-        return new ImageLayer(drawable, layerModel);
+        return new ImageLayer(drawable, compositionLayer, layerModel);
       case NULL:
-        return new NullLayer(drawable, layerModel);
+        return new NullLayer(drawable, compositionLayer, layerModel);
       case TEXT:
-        return new TextLayer(drawable, layerModel);
+        return new TextLayer(drawable, compositionLayer, layerModel);
       case UNKNOWN:
       default:
         // Do nothing
@@ -91,19 +89,16 @@
   private final String drawTraceName;
   final Matrix boundsMatrix = new Matrix();
   final LottieDrawable lottieDrawable;
+  @Nullable private final CompositionLayer compositionLayer;
   final Layer layerModel;
-  @Nullable
-  private MaskKeyframeAnimation mask;
-  @Nullable
-  private FloatKeyframeAnimation inOutAnimation;
-  @Nullable
-  private BaseLayer matteLayer;
+  @Nullable private MaskKeyframeAnimation mask;
+  @Nullable private FloatKeyframeAnimation inOutAnimation;
+  @Nullable private BaseLayer matteLayer;
   /**
    * This should only be used by {@link #buildParentLayerListIfNeeded()}
    * to construct the list of parent layers.
    */
-  @Nullable
-  private BaseLayer parentLayer;
+  @Nullable private BaseLayer parentLayer;
   private List<BaseLayer> parentLayers;
 
   private final List<BaseKeyframeAnimation<?, ?>> animations = new ArrayList<>();
@@ -116,8 +111,11 @@
   float blurMaskFilterRadius = 0f;
   @Nullable BlurMaskFilter blurMaskFilter;
 
-  BaseLayer(LottieDrawable lottieDrawable, Layer layerModel) {
+  private int dirtyFlags;
+
+  BaseLayer(LottieDrawable lottieDrawable, CompositionLayer compositionLayer, Layer layerModel) {
     this.lottieDrawable = lottieDrawable;
+    this.compositionLayer = compositionLayer;
     this.layerModel = layerModel;
     drawTraceName = layerModel.getName() + "#draw";
     if (layerModel.getMatteType() == Layer.MatteType.INVERT) {
@@ -157,8 +155,7 @@
     outlineMasksAndMattes = outline;
   }
 
-  @Override
-  public void onValueChanged() {
+  @Override public void onValueChanged() {
     invalidateSelf();
   }
 
@@ -190,8 +187,16 @@
     }
   }
 
-  private void invalidateSelf() {
-    lottieDrawable.invalidateSelf();
+  public boolean isDirty() {
+    return dirtyFlags != 0;
+  }
+
+  public void invalidateSelf() {
+    if (compositionLayer == null) {
+      lottieDrawable.invalidateSelf();
+    } else {
+      compositionLayer.invalidateSelf();
+    }
   }
 
   public void addAnimation(@Nullable BaseKeyframeAnimation<?, ?> newAnimation) {
@@ -205,10 +210,7 @@
     animations.remove(animation);
   }
 
-  @CallSuper
-  @Override
-  public void getBounds(
-      RectF outBounds, Matrix parentMatrix, boolean applyParents) {
+  @CallSuper @Override public void getBounds(RectF outBounds, Matrix parentMatrix, boolean applyParents) {
     rect.set(0, 0, 0, 0);
     buildParentLayerListIfNeeded();
     boundsMatrix.set(parentMatrix);
@@ -226,8 +228,7 @@
     boundsMatrix.preConcat(transform.getMatrix());
   }
 
-  @Override
-  public void draw(Canvas canvas, Matrix parentMatrix, int parentAlpha) {
+  @Override public void draw(Canvas canvas, Matrix parentMatrix, int parentAlpha) {
     L.beginSection(drawTraceName);
     if (!visible || layerModel.isHidden()) {
       L.endSection(drawTraceName);
@@ -338,8 +339,7 @@
   }
 
   private void recordRenderTime(float ms) {
-    lottieDrawable.getComposition()
-        .getPerformanceTracker().recordRenderTime(layerModel.getName(), ms);
+    lottieDrawable.getComposition().getPerformanceTracker().recordRenderTime(layerModel.getName(), ms);
 
   }
 
@@ -391,12 +391,8 @@
           if (i == 0) {
             maskBoundsRect.set(tempMaskBoundsRect);
           } else {
-            maskBoundsRect.set(
-                Math.min(maskBoundsRect.left, tempMaskBoundsRect.left),
-                Math.min(maskBoundsRect.top, tempMaskBoundsRect.top),
-                Math.max(maskBoundsRect.right, tempMaskBoundsRect.right),
-                Math.max(maskBoundsRect.bottom, tempMaskBoundsRect.bottom)
-            );
+            maskBoundsRect.set(Math.min(maskBoundsRect.left, tempMaskBoundsRect.left), Math.min(maskBoundsRect.top, tempMaskBoundsRect.top),
+                Math.max(maskBoundsRect.right, tempMaskBoundsRect.right), Math.max(maskBoundsRect.bottom, tempMaskBoundsRect.bottom));
           }
       }
     }
@@ -496,8 +492,8 @@
     return true;
   }
 
-  private void applyAddMask(Canvas canvas, Matrix matrix,
-      BaseKeyframeAnimation<ShapeData, Path> maskAnimation, BaseKeyframeAnimation<Integer, Integer> opacityAnimation) {
+  private void applyAddMask(Canvas canvas, Matrix matrix, BaseKeyframeAnimation<ShapeData, Path> maskAnimation,
+      BaseKeyframeAnimation<Integer, Integer> opacityAnimation) {
     Path maskPath = maskAnimation.getValue();
     path.set(maskPath);
     path.transform(matrix);
@@ -505,8 +501,8 @@
     canvas.drawPath(path, contentPaint);
   }
 
-  private void applyInvertedAddMask(Canvas canvas, Matrix matrix,
-      BaseKeyframeAnimation<ShapeData, Path> maskAnimation, BaseKeyframeAnimation<Integer, Integer> opacityAnimation) {
+  private void applyInvertedAddMask(Canvas canvas, Matrix matrix, BaseKeyframeAnimation<ShapeData, Path> maskAnimation,
+      BaseKeyframeAnimation<Integer, Integer> opacityAnimation) {
     Utils.saveLayerCompat(canvas, rect, contentPaint);
     canvas.drawRect(rect, contentPaint);
     Path maskPath = maskAnimation.getValue();
@@ -524,8 +520,8 @@
     canvas.drawPath(path, dstOutPaint);
   }
 
-  private void applyInvertedSubtractMask(Canvas canvas, Matrix matrix,
-      BaseKeyframeAnimation<ShapeData, Path> maskAnimation, BaseKeyframeAnimation<Integer, Integer> opacityAnimation) {
+  private void applyInvertedSubtractMask(Canvas canvas, Matrix matrix, BaseKeyframeAnimation<ShapeData, Path> maskAnimation,
+      BaseKeyframeAnimation<Integer, Integer> opacityAnimation) {
     Utils.saveLayerCompat(canvas, rect, dstOutPaint);
     canvas.drawRect(rect, contentPaint);
     dstOutPaint.setAlpha((int) (opacityAnimation.getValue() * 2.55f));
@@ -536,8 +532,8 @@
     canvas.restore();
   }
 
-  private void applyIntersectMask(Canvas canvas, Matrix matrix,
-      BaseKeyframeAnimation<ShapeData, Path> maskAnimation, BaseKeyframeAnimation<Integer, Integer> opacityAnimation) {
+  private void applyIntersectMask(Canvas canvas, Matrix matrix, BaseKeyframeAnimation<ShapeData, Path> maskAnimation,
+      BaseKeyframeAnimation<Integer, Integer> opacityAnimation) {
     Utils.saveLayerCompat(canvas, rect, dstInPaint);
     Path maskPath = maskAnimation.getValue();
     path.set(maskPath);
@@ -547,8 +543,8 @@
     canvas.restore();
   }
 
-  private void applyInvertedIntersectMask(Canvas canvas, Matrix matrix,
-      BaseKeyframeAnimation<ShapeData, Path> maskAnimation, BaseKeyframeAnimation<Integer, Integer> opacityAnimation) {
+  private void applyInvertedIntersectMask(Canvas canvas, Matrix matrix, BaseKeyframeAnimation<ShapeData, Path> maskAnimation,
+      BaseKeyframeAnimation<Integer, Integer> opacityAnimation) {
     Utils.saveLayerCompat(canvas, rect, dstInPaint);
     canvas.drawRect(rect, contentPaint);
     dstOutPaint.setAlpha((int) (opacityAnimation.getValue() * 2.55f));
@@ -601,6 +597,9 @@
     L.endSection("BaseLayer#setProgress");
   }
 
+  void updateDirtyNodes() {
+  }
+
   private void buildParentLayerListIfNeeded() {
     if (parentLayers != null) {
       return;
@@ -618,13 +617,11 @@
     }
   }
 
-  @Override
-  public String getName() {
+  @Override public String getName() {
     return layerModel.getName();
   }
 
-  @Nullable
-  public BlurEffect getBlurEffect() {
+  @Nullable public BlurEffect getBlurEffect() {
     return layerModel.getBlurEffect();
   }
 
@@ -637,19 +634,15 @@
     return blurMaskFilter;
   }
 
-  @Nullable
-  public DropShadowEffect getDropShadowEffect() {
+  @Nullable public DropShadowEffect getDropShadowEffect() {
     return layerModel.getDropShadowEffect();
   }
 
-  @Override
-  public void setContents(List<Content> contentsBefore, List<Content> contentsAfter) {
+  @Override public void setContents(List<Content> contentsBefore, List<Content> contentsAfter) {
     // Do nothing
   }
 
-  @Override
-  public void resolveKeyPath(
-      KeyPath keyPath, int depth, List<KeyPath> accumulator, KeyPath currentPartialKeyPath) {
+  @Override public void resolveKeyPath(KeyPath keyPath, int depth, List<KeyPath> accumulator, KeyPath currentPartialKeyPath) {
     if (matteLayer != null) {
       KeyPath matteCurrentPartialKeyPath = currentPartialKeyPath.addKey(matteLayer.getName());
       if (keyPath.fullyResolvesTo(matteLayer.getName(), depth)) {
@@ -680,13 +673,10 @@
     }
   }
 
-  void resolveChildKeyPath(
-      KeyPath keyPath, int depth, List<KeyPath> accumulator, KeyPath currentPartialKeyPath) {
+  void resolveChildKeyPath(KeyPath keyPath, int depth, List<KeyPath> accumulator, KeyPath currentPartialKeyPath) {
   }
 
-  @CallSuper
-  @Override
-  public <T> void addValueCallback(T property, @Nullable LottieValueCallback<T> callback) {
+  @CallSuper @Override public <T> void addValueCallback(T property, @Nullable LottieValueCallback<T> callback) {
     transform.applyValueCallback(property, callback);
   }
 }
diff --git a/lottie/src/main/java/com/airbnb/lottie/model/layer/CompositionLayer.java b/lottie/src/main/java/com/airbnb/lottie/model/layer/CompositionLayer.java
index 7c923a6..b5dd3c4 100644
--- a/lottie/src/main/java/com/airbnb/lottie/model/layer/CompositionLayer.java
+++ b/lottie/src/main/java/com/airbnb/lottie/model/layer/CompositionLayer.java
@@ -37,9 +37,9 @@
 
   private boolean clipToCompositionBounds = true;
 
-  public CompositionLayer(LottieDrawable lottieDrawable, Layer layerModel, List<Layer> layerModels,
-      LottieComposition composition) {
-    super(lottieDrawable, layerModel);
+  public CompositionLayer(LottieDrawable lottieDrawable, @Nullable CompositionLayer compositionLayer,
+      Layer layerModel, List<Layer> layerModels, LottieComposition composition) {
+    super(lottieDrawable, compositionLayer, layerModel);
 
     AnimatableFloatValue timeRemapping = layerModel.getTimeRemapping();
     if (timeRemapping != null) {
@@ -51,8 +51,7 @@
       this.timeRemapping = null;
     }
 
-    LongSparseArray<BaseLayer> layerMap =
-        new LongSparseArray<>(composition.getLayers().size());
+    LongSparseArray<BaseLayer> layerMap = new LongSparseArray<>(composition.getLayers().size());
 
     BaseLayer mattedLayer = null;
     for (int i = layerModels.size() - 1; i >= 0; i--) {
@@ -166,9 +165,22 @@
     for (int i = layers.size() - 1; i >= 0; i--) {
       layers.get(i).setProgress(progress);
     }
+    updateDirtyNodes();
     L.endSection("CompositionLayer#setProgress");
   }
 
+  @Override
+  public void updateDirtyNodes() {
+    L.beginSection("CompositionLayer#updateDirtyNodes");
+    for (int i = layers.size() - 1; i >= 0; i--) {
+      BaseLayer layer = layers.get(i);
+      if (layer.isDirty()) {
+        layer.updateDirtyNodes();
+      }
+    }
+    L.endSection("CompositionLayer#updateDirtyNodes");
+  }
+
   public float getProgress() {
     return progress;
   }
@@ -210,17 +222,13 @@
     return hasMatte;
   }
 
-  @Override
-  protected void resolveChildKeyPath(KeyPath keyPath, int depth, List<KeyPath> accumulator,
-      KeyPath currentPartialKeyPath) {
+  @Override protected void resolveChildKeyPath(KeyPath keyPath, int depth, List<KeyPath> accumulator, KeyPath currentPartialKeyPath) {
     for (int i = 0; i < layers.size(); i++) {
       layers.get(i).resolveKeyPath(keyPath, depth, accumulator, currentPartialKeyPath);
     }
   }
 
-  @SuppressWarnings("unchecked")
-  @Override
-  public <T> void addValueCallback(T property, @Nullable LottieValueCallback<T> callback) {
+  @SuppressWarnings("unchecked") @Override public <T> void addValueCallback(T property, @Nullable LottieValueCallback<T> callback) {
     super.addValueCallback(property, callback);
 
     if (property == LottieProperty.TIME_REMAP) {
diff --git a/lottie/src/main/java/com/airbnb/lottie/model/layer/ImageLayer.java b/lottie/src/main/java/com/airbnb/lottie/model/layer/ImageLayer.java
index 6cd60b6..7d5b7a7 100644
--- a/lottie/src/main/java/com/airbnb/lottie/model/layer/ImageLayer.java
+++ b/lottie/src/main/java/com/airbnb/lottie/model/layer/ImageLayer.java
@@ -29,8 +29,8 @@
   @Nullable private BaseKeyframeAnimation<ColorFilter, ColorFilter> colorFilterAnimation;
   @Nullable private BaseKeyframeAnimation<Bitmap, Bitmap> imageAnimation;
 
-  ImageLayer(LottieDrawable lottieDrawable, Layer layerModel) {
-    super(lottieDrawable, layerModel);
+  ImageLayer(LottieDrawable lottieDrawable, CompositionLayer compositionLayer, Layer layerModel) {
+    super(lottieDrawable, compositionLayer, layerModel);
     lottieImageAsset = lottieDrawable.getLottieImageAssetForId(layerModel.getRefId());
   }
 
diff --git a/lottie/src/main/java/com/airbnb/lottie/model/layer/NullLayer.java b/lottie/src/main/java/com/airbnb/lottie/model/layer/NullLayer.java
index 045cabf..3959598 100644
--- a/lottie/src/main/java/com/airbnb/lottie/model/layer/NullLayer.java
+++ b/lottie/src/main/java/com/airbnb/lottie/model/layer/NullLayer.java
@@ -7,8 +7,8 @@
 import com.airbnb.lottie.LottieDrawable;
 
 public class NullLayer extends BaseLayer {
-  NullLayer(LottieDrawable lottieDrawable, Layer layerModel) {
-    super(lottieDrawable, layerModel);
+  NullLayer(LottieDrawable lottieDrawable, CompositionLayer compositionLayer, Layer layerModel) {
+    super(lottieDrawable, compositionLayer, layerModel);
   }
 
   @Override void drawLayer(Canvas canvas, Matrix parentMatrix, int parentAlpha) {
diff --git a/lottie/src/main/java/com/airbnb/lottie/model/layer/ShapeLayer.java b/lottie/src/main/java/com/airbnb/lottie/model/layer/ShapeLayer.java
index 3a82138..2866b71 100644
--- a/lottie/src/main/java/com/airbnb/lottie/model/layer/ShapeLayer.java
+++ b/lottie/src/main/java/com/airbnb/lottie/model/layer/ShapeLayer.java
@@ -24,7 +24,7 @@
   private final CompositionLayer compositionLayer;
 
   ShapeLayer(LottieDrawable lottieDrawable, Layer layerModel, CompositionLayer compositionLayer, LottieComposition composition) {
-    super(lottieDrawable, layerModel);
+    super(lottieDrawable, compositionLayer, layerModel);
     this.compositionLayer = compositionLayer;
 
     // Naming this __container allows it to be ignored in KeyPath matching.
@@ -33,6 +33,10 @@
     contentGroup.setContents(Collections.<Content>emptyList(), Collections.<Content>emptyList());
   }
 
+  @Override void updateDirtyNodes() {
+    super.updateDirtyNodes();
+  }
+
   @Override void drawLayer(@NonNull Canvas canvas, Matrix parentMatrix, int parentAlpha) {
     contentGroup.draw(canvas, parentMatrix, parentAlpha);
   }
diff --git a/lottie/src/main/java/com/airbnb/lottie/model/layer/SolidLayer.java b/lottie/src/main/java/com/airbnb/lottie/model/layer/SolidLayer.java
index 993e1b3..36147e3 100644
--- a/lottie/src/main/java/com/airbnb/lottie/model/layer/SolidLayer.java
+++ b/lottie/src/main/java/com/airbnb/lottie/model/layer/SolidLayer.java
@@ -25,8 +25,8 @@
   private final Layer layerModel;
   @Nullable private BaseKeyframeAnimation<ColorFilter, ColorFilter> colorFilterAnimation;
 
-  SolidLayer(LottieDrawable lottieDrawable, Layer layerModel) {
-    super(lottieDrawable, layerModel);
+  SolidLayer(LottieDrawable lottieDrawable, CompositionLayer compositionLayer, Layer layerModel) {
+    super(lottieDrawable, compositionLayer, layerModel);
     this.layerModel = layerModel;
 
     paint.setAlpha(0);
diff --git a/lottie/src/main/java/com/airbnb/lottie/model/layer/TextLayer.java b/lottie/src/main/java/com/airbnb/lottie/model/layer/TextLayer.java
index 99e9023..eb4f97f 100644
--- a/lottie/src/main/java/com/airbnb/lottie/model/layer/TextLayer.java
+++ b/lottie/src/main/java/com/airbnb/lottie/model/layer/TextLayer.java
@@ -76,8 +76,8 @@
   @Nullable
   private BaseKeyframeAnimation<Typeface, Typeface> typefaceCallbackAnimation;
 
-  TextLayer(LottieDrawable lottieDrawable, Layer layerModel) {
-    super(lottieDrawable, layerModel);
+  TextLayer(LottieDrawable lottieDrawable, CompositionLayer compositionLayer, Layer layerModel) {
+    super(lottieDrawable, compositionLayer, layerModel);
     this.lottieDrawable = lottieDrawable;
     composition = layerModel.getComposition();
     //noinspection ConstantConditions