Added support for dynamic gradient colors (#1153)

Other dynamic properties can be added later but adding support for colors is the most valuable right now.
#905
diff --git a/After Effects Samples/DynamicGradient.aep b/After Effects Samples/DynamicGradient.aep
new file mode 100644
index 0000000..b24a438
--- /dev/null
+++ b/After Effects Samples/DynamicGradient.aep
Binary files differ
diff --git a/LottieSample/src/androidTest/java/com/airbnb/lottie/LottieTest.kt b/LottieSample/src/androidTest/java/com/airbnb/lottie/LottieTest.kt
index 842b6e3..ba2417b 100644
--- a/LottieSample/src/androidTest/java/com/airbnb/lottie/LottieTest.kt
+++ b/LottieSample/src/androidTest/java/com/airbnb/lottie/LottieTest.kt
@@ -568,6 +568,42 @@
                 LottieProperty.TRANSFORM_OPACITY,
                 LottieInterpolatedIntegerValue(10, 100),
                 1f)
+
+        withDrawable("Tests/DynamicGradient.json", "Gradient Colors", "Linear Gradient Fill") { drawable ->
+            val value = object : LottieValueCallback<Array<Int>>() {
+                override fun getValue(frameInfo: LottieFrameInfo<Array<Int>>?): Array<Int>? {
+                    return arrayOf(Color.YELLOW, Color.GREEN)
+                }
+            }
+            drawable.addValueCallback(KeyPath("Linear", "Rectangle", "Gradient Fill"), LottieProperty.GRADIENT_COLOR, value)
+        }
+
+        withDrawable("Tests/DynamicGradient.json", "Gradient Colors", "Radial Gradient Fill") { drawable ->
+            val value = object : LottieValueCallback<Array<Int>>() {
+                override fun getValue(frameInfo: LottieFrameInfo<Array<Int>>?): Array<Int>? {
+                    return arrayOf(Color.YELLOW, Color.GREEN)
+                }
+            }
+            drawable.addValueCallback(KeyPath("Radial", "Rectangle", "Gradient Fill"), LottieProperty.GRADIENT_COLOR, value)
+        }
+
+        withDrawable("Tests/DynamicGradient.json", "Gradient Colors", "Linear Gradient Stroke") { drawable ->
+            val value = object : LottieValueCallback<Array<Int>>() {
+                override fun getValue(frameInfo: LottieFrameInfo<Array<Int>>?): Array<Int>? {
+                    return arrayOf(Color.YELLOW, Color.GREEN)
+                }
+            }
+            drawable.addValueCallback(KeyPath("Linear", "Rectangle", "Gradient Stroke"), LottieProperty.GRADIENT_COLOR, value)
+        }
+
+        withDrawable("Tests/DynamicGradient.json", "Gradient Colors", "Radial Gradient Stroke") { drawable ->
+            val value = object : LottieValueCallback<Array<Int>>() {
+                override fun getValue(frameInfo: LottieFrameInfo<Array<Int>>?): Array<Int>? {
+                    return arrayOf(Color.YELLOW, Color.GREEN)
+                }
+            }
+            drawable.addValueCallback(KeyPath("Radial", "Rectangle", "Gradient Stroke"), LottieProperty.GRADIENT_COLOR, value)
+        }
     }
 
     private suspend fun <T> testDynamicProperty(name: String, keyPath: KeyPath, property: T, callback: LottieValueCallback<T>, progress: Float = 0f) {
diff --git a/LottieSample/src/main/assets/DynamicGradient.json b/LottieSample/src/main/assets/DynamicGradient.json
new file mode 100644
index 0000000..133c20c
--- /dev/null
+++ b/LottieSample/src/main/assets/DynamicGradient.json
@@ -0,0 +1 @@
+{"v":"5.4.4","fr":30.0000305175781,"ip":0,"op":300.000305175781,"w":800,"h":400,"nm":"DyanamicGradient","ddd":0,"assets":[],"layers":[{"ddd":0,"ind":1,"ty":4,"nm":"Radial","sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[836,202,0],"ix":2},"a":{"a":0,"k":[0,0,0],"ix":1},"s":{"a":0,"k":[100,100,100],"ix":6}},"ao":0,"shapes":[{"ty":"gr","it":[{"ty":"rc","d":1,"s":{"a":0,"k":[250,250],"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":"gs","o":{"a":0,"k":100,"ix":9},"w":{"a":0,"k":45,"ix":10},"g":{"p":2,"k":{"a":0,"k":[0,1,1,1,1,0,0,0],"ix":8}},"s":{"a":0,"k":[0,0],"ix":4},"e":{"a":0,"k":[300,0],"ix":5},"t":2,"h":{"a":0,"k":0,"ix":6},"a":{"a":0,"k":0,"ix":7},"lc":1,"lj":1,"ml":4,"ml2":{"a":0,"k":4,"ix":13},"bm":0,"nm":"Gradient Stroke","mn":"ADBE Vector Graphic - G-Stroke","hd":false},{"ty":"gf","o":{"a":0,"k":100,"ix":10},"r":1,"bm":0,"g":{"p":2,"k":{"a":0,"k":[0,1,1,1,1,0,0,0],"ix":9}},"s":{"a":0,"k":[0,0],"ix":5},"e":{"a":0,"k":[101,0],"ix":6},"t":2,"h":{"a":0,"k":0,"ix":7},"a":{"a":0,"k":0,"ix":8},"nm":"Gradient Fill","mn":"ADBE Vector Graphic - G-Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[-218,0],"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","np":3,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":300.000305175781,"st":0,"bm":0},{"ddd":0,"ind":2,"ty":4,"nm":"Linear","sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[400,202,0],"ix":2},"a":{"a":0,"k":[0,0,0],"ix":1},"s":{"a":0,"k":[100,100,100],"ix":6}},"ao":0,"shapes":[{"ty":"gr","it":[{"ty":"rc","d":1,"s":{"a":0,"k":[250,250],"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":"gs","o":{"a":0,"k":100,"ix":9},"w":{"a":0,"k":45,"ix":10},"g":{"p":2,"k":{"a":0,"k":[0,1,1,1,1,0,0,0],"ix":8}},"s":{"a":0,"k":[0,0],"ix":4},"e":{"a":0,"k":[100,0],"ix":5},"t":1,"lc":1,"lj":1,"ml":4,"ml2":{"a":0,"k":4,"ix":13},"bm":0,"nm":"Gradient Stroke","mn":"ADBE Vector Graphic - G-Stroke","hd":false},{"ty":"gf","o":{"a":0,"k":100,"ix":10},"r":1,"bm":0,"g":{"p":2,"k":{"a":0,"k":[0,1,1,1,1,0,0,0],"ix":9}},"s":{"a":0,"k":[-75,0],"ix":5},"e":{"a":0,"k":[75,0],"ix":6},"t":1,"nm":"Gradient Fill","mn":"ADBE Vector Graphic - G-Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[-218,0],"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","np":3,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":300.000305175781,"st":0,"bm":0}],"markers":[]}
\ No newline at end of file
diff --git a/LottieSample/src/main/assets/Tests/DynamicGradient.json b/LottieSample/src/main/assets/Tests/DynamicGradient.json
new file mode 100644
index 0000000..133c20c
--- /dev/null
+++ b/LottieSample/src/main/assets/Tests/DynamicGradient.json
@@ -0,0 +1 @@
+{"v":"5.4.4","fr":30.0000305175781,"ip":0,"op":300.000305175781,"w":800,"h":400,"nm":"DyanamicGradient","ddd":0,"assets":[],"layers":[{"ddd":0,"ind":1,"ty":4,"nm":"Radial","sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[836,202,0],"ix":2},"a":{"a":0,"k":[0,0,0],"ix":1},"s":{"a":0,"k":[100,100,100],"ix":6}},"ao":0,"shapes":[{"ty":"gr","it":[{"ty":"rc","d":1,"s":{"a":0,"k":[250,250],"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":"gs","o":{"a":0,"k":100,"ix":9},"w":{"a":0,"k":45,"ix":10},"g":{"p":2,"k":{"a":0,"k":[0,1,1,1,1,0,0,0],"ix":8}},"s":{"a":0,"k":[0,0],"ix":4},"e":{"a":0,"k":[300,0],"ix":5},"t":2,"h":{"a":0,"k":0,"ix":6},"a":{"a":0,"k":0,"ix":7},"lc":1,"lj":1,"ml":4,"ml2":{"a":0,"k":4,"ix":13},"bm":0,"nm":"Gradient Stroke","mn":"ADBE Vector Graphic - G-Stroke","hd":false},{"ty":"gf","o":{"a":0,"k":100,"ix":10},"r":1,"bm":0,"g":{"p":2,"k":{"a":0,"k":[0,1,1,1,1,0,0,0],"ix":9}},"s":{"a":0,"k":[0,0],"ix":5},"e":{"a":0,"k":[101,0],"ix":6},"t":2,"h":{"a":0,"k":0,"ix":7},"a":{"a":0,"k":0,"ix":8},"nm":"Gradient Fill","mn":"ADBE Vector Graphic - G-Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[-218,0],"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","np":3,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":300.000305175781,"st":0,"bm":0},{"ddd":0,"ind":2,"ty":4,"nm":"Linear","sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[400,202,0],"ix":2},"a":{"a":0,"k":[0,0,0],"ix":1},"s":{"a":0,"k":[100,100,100],"ix":6}},"ao":0,"shapes":[{"ty":"gr","it":[{"ty":"rc","d":1,"s":{"a":0,"k":[250,250],"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":"gs","o":{"a":0,"k":100,"ix":9},"w":{"a":0,"k":45,"ix":10},"g":{"p":2,"k":{"a":0,"k":[0,1,1,1,1,0,0,0],"ix":8}},"s":{"a":0,"k":[0,0],"ix":4},"e":{"a":0,"k":[100,0],"ix":5},"t":1,"lc":1,"lj":1,"ml":4,"ml2":{"a":0,"k":4,"ix":13},"bm":0,"nm":"Gradient Stroke","mn":"ADBE Vector Graphic - G-Stroke","hd":false},{"ty":"gf","o":{"a":0,"k":100,"ix":10},"r":1,"bm":0,"g":{"p":2,"k":{"a":0,"k":[0,1,1,1,1,0,0,0],"ix":9}},"s":{"a":0,"k":[-75,0],"ix":5},"e":{"a":0,"k":[75,0],"ix":6},"t":1,"nm":"Gradient Fill","mn":"ADBE Vector Graphic - G-Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[-218,0],"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","np":3,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":300.000305175781,"st":0,"bm":0}],"markers":[]}
\ No newline at end of file
diff --git a/lottie/src/main/java/com/airbnb/lottie/LottieProperty.java b/lottie/src/main/java/com/airbnb/lottie/LottieProperty.java
index e5c8b82..87f29e8 100644
--- a/lottie/src/main/java/com/airbnb/lottie/LottieProperty.java
+++ b/lottie/src/main/java/com/airbnb/lottie/LottieProperty.java
@@ -108,4 +108,6 @@
   Float TIME_REMAP = 13f;
 
   ColorFilter COLOR_FILTER = new ColorFilter();
+
+  Integer[] GRADIENT_COLOR = new Integer[0];
 }
diff --git a/lottie/src/main/java/com/airbnb/lottie/animation/content/BaseStrokeContent.java b/lottie/src/main/java/com/airbnb/lottie/animation/content/BaseStrokeContent.java
index 9306ddf..676ce78 100644
--- a/lottie/src/main/java/com/airbnb/lottie/animation/content/BaseStrokeContent.java
+++ b/lottie/src/main/java/com/airbnb/lottie/animation/content/BaseStrokeContent.java
@@ -41,7 +41,7 @@
   private final Path trimPathPath = new Path();
   private final RectF rect = new RectF();
   private final LottieDrawable lottieDrawable;
-  private final BaseLayer layer;
+  protected final BaseLayer layer;
   private final List<PathGroup> pathGroups = new ArrayList<>();
   private final float[] dashPatternValues;
   final Paint paint = new LPaint(Paint.ANTI_ALIAS_FLAG);
diff --git a/lottie/src/main/java/com/airbnb/lottie/animation/content/GradientFillContent.java b/lottie/src/main/java/com/airbnb/lottie/animation/content/GradientFillContent.java
index 7175e10..b9c63c7 100644
--- a/lottie/src/main/java/com/airbnb/lottie/animation/content/GradientFillContent.java
+++ b/lottie/src/main/java/com/airbnb/lottie/animation/content/GradientFillContent.java
@@ -55,6 +55,7 @@
   private final BaseKeyframeAnimation<PointF, PointF> startPointAnimation;
   private final BaseKeyframeAnimation<PointF, PointF> endPointAnimation;
   @Nullable private BaseKeyframeAnimation<ColorFilter, ColorFilter> colorFilterAnimation;
+  @Nullable private ValueCallbackKeyframeAnimation colorCallbackAnimation;
   private final LottieDrawable lottieDrawable;
   private final int cacheSteps;
 
@@ -159,7 +160,7 @@
     PointF startPoint = startPointAnimation.getValue();
     PointF endPoint = endPointAnimation.getValue();
     GradientColor gradientColor = colorAnimation.getValue();
-    int[] colors = gradientColor.getColors();
+    int[] colors = applyDynamicColorsIfNeeded(gradientColor.getColors());
     float[] positions = gradientColor.getPositions();
     gradient = new LinearGradient(startPoint.x, startPoint.y, endPoint.x, endPoint.y, colors,
         positions, Shader.TileMode.CLAMP);
@@ -176,7 +177,7 @@
     PointF startPoint = startPointAnimation.getValue();
     PointF endPoint = endPointAnimation.getValue();
     GradientColor gradientColor = colorAnimation.getValue();
-    int[] colors = gradientColor.getColors();
+    int[] colors = applyDynamicColorsIfNeeded(gradientColor.getColors());
     float[] positions = gradientColor.getPositions();
     float x0 = startPoint.x;
     float y0 = startPoint.y;
@@ -208,6 +209,23 @@
     return hash;
   }
 
+  private int[] applyDynamicColorsIfNeeded(int[] colors) {
+    if (colorCallbackAnimation != null) {
+      Integer[] dynamicColors = (Integer[]) colorCallbackAnimation.getValue();
+      if (colors.length == dynamicColors.length) {
+        for (int i = 0; i < colors.length; i++) {
+          colors[i] = dynamicColors[i];
+        }
+      } else {
+        colors = new int[dynamicColors.length];
+        for (int i = 0; i < dynamicColors.length; i++) {
+          colors[i] = dynamicColors[i];
+        }
+      }
+    }
+    return colors;
+  }
+
   @Override public void resolveKeyPath(
       KeyPath keyPath, int depth, List<KeyPath> accumulator, KeyPath currentPartialKeyPath) {
     MiscUtils.resolveKeyPath(keyPath, depth, accumulator, currentPartialKeyPath, this);
@@ -225,6 +243,17 @@
          colorFilterAnimation.addUpdateListener(this);
          layer.addAnimation(colorFilterAnimation);
        }
+     } else if (property == LottieProperty.GRADIENT_COLOR) {
+       if (callback == null) {
+         if (colorCallbackAnimation != null) {
+           layer.removeAnimation(colorCallbackAnimation);
+         }
+         colorCallbackAnimation = null;
+       } else {
+         colorCallbackAnimation = new ValueCallbackKeyframeAnimation<>(callback);
+         colorCallbackAnimation.addUpdateListener(this);
+         layer.addAnimation(colorCallbackAnimation);
+       }
     }
   }
 }
diff --git a/lottie/src/main/java/com/airbnb/lottie/animation/content/GradientStrokeContent.java b/lottie/src/main/java/com/airbnb/lottie/animation/content/GradientStrokeContent.java
index 137ee31..5f2cc56 100644
--- a/lottie/src/main/java/com/airbnb/lottie/animation/content/GradientStrokeContent.java
+++ b/lottie/src/main/java/com/airbnb/lottie/animation/content/GradientStrokeContent.java
@@ -7,14 +7,18 @@
 import android.graphics.RadialGradient;
 import android.graphics.RectF;
 import android.graphics.Shader;
+import androidx.annotation.Nullable;
 import androidx.collection.LongSparseArray;
 
 import com.airbnb.lottie.LottieDrawable;
+import com.airbnb.lottie.LottieProperty;
 import com.airbnb.lottie.animation.keyframe.BaseKeyframeAnimation;
+import com.airbnb.lottie.animation.keyframe.ValueCallbackKeyframeAnimation;
 import com.airbnb.lottie.model.content.GradientColor;
 import com.airbnb.lottie.model.content.GradientStroke;
 import com.airbnb.lottie.model.content.GradientType;
 import com.airbnb.lottie.model.layer.BaseLayer;
+import com.airbnb.lottie.value.LottieValueCallback;
 
 public class GradientStrokeContent extends BaseStrokeContent {
   /**
@@ -33,6 +37,7 @@
   private final BaseKeyframeAnimation<GradientColor, GradientColor> colorAnimation;
   private final BaseKeyframeAnimation<PointF, PointF> startPointAnimation;
   private final BaseKeyframeAnimation<PointF, PointF> endPointAnimation;
+  @Nullable private ValueCallbackKeyframeAnimation colorCallbackAnimation;
 
   public GradientStrokeContent(
       final LottieDrawable lottieDrawable, BaseLayer layer, GradientStroke stroke) {
@@ -63,11 +68,14 @@
       return;
     }
     getBounds(boundsRect, parentMatrix, false);
+
+    Shader shader;
     if (type == GradientType.LINEAR) {
-      paint.setShader(getLinearGradient());
+      shader = getLinearGradient();
     } else {
-      paint.setShader(getRadialGradient());
+      shader = getRadialGradient();
     }
+    paint.setShader(shader);
 
     super.draw(canvas, parentMatrix, parentAlpha);
   }
@@ -85,7 +93,7 @@
     PointF startPoint = startPointAnimation.getValue();
     PointF endPoint = endPointAnimation.getValue();
     GradientColor gradientColor = colorAnimation.getValue();
-    int[] colors = gradientColor.getColors();
+    int[] colors = applyDynamicColorsIfNeeded(gradientColor.getColors());
     float[] positions = gradientColor.getPositions();
     int x0 = (int) (boundsRect.left + boundsRect.width() / 2 + startPoint.x);
     int y0 = (int) (boundsRect.top + boundsRect.height() / 2 + startPoint.y);
@@ -105,7 +113,7 @@
     PointF startPoint = startPointAnimation.getValue();
     PointF endPoint = endPointAnimation.getValue();
     GradientColor gradientColor = colorAnimation.getValue();
-    int[] colors = gradientColor.getColors();
+    int[] colors = applyDynamicColorsIfNeeded(gradientColor.getColors());
     float[] positions = gradientColor.getPositions();
     int x0 = (int) (boundsRect.left + boundsRect.width() / 2 + startPoint.x);
     int y0 = (int) (boundsRect.top + boundsRect.height() / 2 + startPoint.y);
@@ -133,4 +141,38 @@
     }
     return hash;
   }
+
+  private int[] applyDynamicColorsIfNeeded(int[] colors) {
+    if (colorCallbackAnimation != null) {
+      Integer[] dynamicColors = (Integer[]) colorCallbackAnimation.getValue();
+      if (colors.length == dynamicColors.length) {
+        for (int i = 0; i < colors.length; i++) {
+          colors[i] = dynamicColors[i];
+        }
+      } else {
+        colors = new int[dynamicColors.length];
+        for (int i = 0; i < dynamicColors.length; i++) {
+          colors[i] = dynamicColors[i];
+        }
+      }
+    }
+    return colors;
+  }
+
+  @Override
+  public <T> void addValueCallback(T property, @Nullable LottieValueCallback<T> callback) {
+    super.addValueCallback(property, callback);
+    if (property == LottieProperty.GRADIENT_COLOR) {
+      if (callback == null) {
+        if (colorCallbackAnimation != null) {
+          layer.removeAnimation(colorCallbackAnimation);
+        }
+        colorCallbackAnimation = null;
+      } else {
+        colorCallbackAnimation = new ValueCallbackKeyframeAnimation<>(callback);
+        colorCallbackAnimation.addUpdateListener(this);
+        layer.addAnimation(colorCallbackAnimation);
+      }
+    }
+  }
 }
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 ddb5329..fc63529 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
@@ -183,6 +183,10 @@
     animations.add(newAnimation);
   }
 
+  public void removeAnimation(BaseKeyframeAnimation<?, ?> animation) {
+    animations.remove(animation);
+  }
+
   @CallSuper
   @Override
   public void getBounds(