Allow dynamic properties on text without animators
diff --git a/LottieSample/src/androidTest/java/com/airbnb/lottie/samples/LottieTest.kt b/LottieSample/src/androidTest/java/com/airbnb/lottie/samples/LottieTest.kt
index fe1baef..051252b 100644
--- a/LottieSample/src/androidTest/java/com/airbnb/lottie/samples/LottieTest.kt
+++ b/LottieSample/src/androidTest/java/com/airbnb/lottie/samples/LottieTest.kt
@@ -633,6 +633,27 @@
             }
             drawable.addValueCallback(KeyPath("Linear", "Rectangle", "Gradient Fill"), LottieProperty.OPACITY, value)
         }
+
+        withDrawable("Tests/Text.json", "Text", "Text Fill (Blue -> Green)") { drawable ->
+            val value = object : LottieValueCallback<Int>() {
+                override fun getValue(frameInfo: LottieFrameInfo<Int>?) = Color.GREEN
+            }
+            drawable.addValueCallback(KeyPath("Text"), LottieProperty.COLOR, value)
+        }
+
+        withDrawable("Tests/Text.json", "Text", "Text Stroke (Red -> Yellow)") { drawable ->
+            val value = object : LottieValueCallback<Int>() {
+                override fun getValue(frameInfo: LottieFrameInfo<Int>?) = Color.YELLOW
+            }
+            drawable.addValueCallback(KeyPath("Text"), LottieProperty.STROKE_COLOR, value)
+        }
+
+        withDrawable("Tests/Text.json", "Text", "Text Stroke Width") { drawable ->
+            val value = object : LottieValueCallback<Float>() {
+                override fun getValue(frameInfo: LottieFrameInfo<Float>?) = 200f
+            }
+            drawable.addValueCallback(KeyPath("Text"), LottieProperty.STROKE_WIDTH, 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/Tests/Text.json b/LottieSample/src/main/assets/Tests/Text.json
new file mode 100644
index 0000000..5e1c4b4
--- /dev/null
+++ b/LottieSample/src/main/assets/Tests/Text.json
@@ -0,0 +1 @@
+{"v":"5.4.4","fr":30.0000305175781,"ip":0,"op":91.000092569987,"w":300,"h":300,"nm":"Comp 1","ddd":0,"assets":[],"fonts":{"list":[{"fName":"Helvetica","fFamily":"Helvetica","fStyle":"Regular","ascent":76.8994140625}]},"layers":[{"ddd":0,"ind":1,"ty":5,"nm":"Text","sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[156,199,0],"ix":2},"a":{"a":0,"k":[0,0,0],"ix":1},"s":{"a":0,"k":[100,100,100],"ix":6}},"ao":0,"t":{"d":{"k":[{"s":{"s":120,"f":"Helvetica","t":"ABC","j":2,"tr":0,"lh":144,"ls":0,"fc":[0,0,1],"sc":[1,0,0.115],"sw":5,"of":true},"t":0}]},"p":{},"m":{"g":1,"a":{"a":0,"k":[0,0],"ix":2}},"a":[]},"ip":0,"op":91.000092569987,"st":0,"bm":0}],"markers":[],"chars":[{"ch":"A","size":120,"style":"Regular","w":66.7,"data":{"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,0],[0,0],[0,0]],"o":[[0,0],[0,0],[0,0]],"v":[[22.143,-29.395],[33.618,-61.084],[44.662,-29.395]],"c":true},"ix":2},"nm":"A","mn":"ADBE Vector Shape - Group","hd":false},{"ind":1,"ty":"sh","ix":2,"ks":{"a":0,"k":{"i":[[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0]],"o":[[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0]],"v":[[1.538,0],[11.499,0],[19.278,-21.484],[47.419,-21.484],[54.907,0],[65.552,0],[39.518,-71.729],[28.549,-71.729]],"c":true},"ix":2},"nm":"A","mn":"ADBE Vector Shape - Group","hd":false}],"nm":"A","np":5,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}]},"fFamily":"Helvetica"},{"ch":"B","size":120,"style":"Regular","w":66.7,"data":{"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,0],[0,0],[0,0],[-2.171,-0.911],[0,-4.688],[3.618,-1.79],[4.144,0]],"o":[[0,0],[0,0],[4.276,0],[3.848,1.628],[0,4.655],[-2.303,1.14],[0,0]],"v":[[16.895,-41.406],[16.895,-63.623],[34.41,-63.623],[44.081,-62.256],[49.854,-52.783],[44.426,-43.115],[34.756,-41.406]],"c":true},"ix":2},"nm":"B","mn":"ADBE Vector Shape - Group","hd":false},{"ind":1,"ty":"sh","ix":2,"ks":{"a":0,"k":{"i":[[0,0],[0,0],[0,0],[-2.498,-1.009],[0,-5.208],[1.61,-2.18],[6.013,0]],"o":[[0,0],[0,0],[4.108,0],[4.699,1.888],[0,3.093],[-2.563,3.451],[0,0]],"v":[[16.895,-8.301],[16.895,-33.545],[36.364,-33.545],[46.272,-32.031],[53.32,-21.387],[50.905,-13.477],[38.04,-8.301]],"c":true},"ix":2},"nm":"B","mn":"ADBE Vector Shape - Group","hd":false},{"ind":2,"ty":"sh","ix":3,"ks":{"a":0,"k":{"i":[[0,0],[0,0],[-4.356,5.599],[0,4.623],[3.223,3.125],[3.678,1.4],[-1.335,1.53],[0,4.525],[2.097,2.962],[8.453,0],[0,0]],"o":[[0,0],[9.498,0],[2.914,-3.743],[0,-5.501],[-1.823,-1.758],[2.506,-1.27],[2.571,-2.897],[0,-3.873],[-3.572,-5.013],[0,0],[0,0]],"v":[[7.373,0],[37.883,0],[58.665,-8.398],[63.037,-20.947],[58.203,-33.887],[49.951,-38.623],[55.713,-42.822],[59.57,-53.955],[56.425,-64.209],[38.387,-71.729],[7.373,-71.729]],"c":true},"ix":2},"nm":"B","mn":"ADBE Vector Shape - Group","hd":false}],"nm":"B","np":6,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}]},"fFamily":"Helvetica"},{"ch":"C","size":120,"style":"Regular","w":72.22,"data":{"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[8.976,0],[5.984,-7.361],[0,-10.586],[-7.368,-6.677],[-8.237,0],[-5.631,6.152],[-0.837,7.552],[0,0],[1.95,-2.897],[7.035,0],[3.853,5.229],[0,8.406],[-4.268,4.968],[-6.937,0],[-3.149,-2.702],[-1.055,-4.622],[0,0],[4.955,4.785]],"o":[[-10.521,0],[-5.534,6.776],[0,13.942],[5.598,5.049],[9.557,0],[4.697,-5.11],[0,0],[-0.96,4.72],[-3.677,5.502],[-7.642,0],[-3.854,-5.229],[0,-10.262],[4.268,-4.968],[5.69,0],[3.149,2.702],[0,0],[-0.547,-6.087],[-4.955,-4.785]],"v":[[37.453,-73.682],[12.695,-62.64],[4.395,-36.597],[15.447,-5.669],[36.201,1.904],[58.984,-7.324],[67.285,-26.318],[57.812,-26.318],[53.448,-14.893],[37.379,-6.641],[20.135,-14.484],[14.355,-34.936],[20.757,-57.782],[37.564,-65.234],[50.823,-61.182],[57.129,-50.195],[66.602,-50.195],[58.349,-66.504]],"c":true},"ix":2},"nm":"C","mn":"ADBE Vector Shape - Group","hd":false}],"nm":"C","np":3,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}]},"fFamily":"Helvetica"}]}
\ No newline at end of file
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 ab45531..7526052 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
@@ -2,6 +2,7 @@
 
 import android.graphics.Canvas;
 import android.graphics.Color;
+import android.graphics.ColorFilter;
 import android.graphics.Matrix;
 import android.graphics.Paint;
 import android.graphics.Path;
@@ -16,6 +17,7 @@
 import com.airbnb.lottie.animation.content.ContentGroup;
 import com.airbnb.lottie.animation.keyframe.BaseKeyframeAnimation;
 import com.airbnb.lottie.animation.keyframe.TextKeyframeAnimation;
+import com.airbnb.lottie.animation.keyframe.ValueCallbackKeyframeAnimation;
 import com.airbnb.lottie.model.DocumentData;
 import com.airbnb.lottie.model.DocumentData.Justification;
 import com.airbnb.lottie.model.Font;
@@ -411,14 +413,66 @@
   @Override
   public <T> void addValueCallback(T property, @Nullable LottieValueCallback<T> callback) {
     super.addValueCallback(property, callback);
-    if (property == LottieProperty.COLOR && colorAnimation != null) {
-      colorAnimation.setValueCallback((LottieValueCallback<Integer>) callback);
-    } else if (property == LottieProperty.STROKE_COLOR && strokeColorAnimation != null) {
-      strokeColorAnimation.setValueCallback((LottieValueCallback<Integer>) callback);
-    } else if (property == LottieProperty.STROKE_WIDTH && strokeWidthAnimation != null) {
-      strokeWidthAnimation.setValueCallback((LottieValueCallback<Float>) callback);
-    } else if (property == LottieProperty.TEXT_TRACKING && trackingAnimation != null) {
-      trackingAnimation.setValueCallback((LottieValueCallback<Float>) callback);
+    if (property == LottieProperty.COLOR) {
+      if (colorAnimation != null) {
+        colorAnimation.setValueCallback((LottieValueCallback<Integer>) callback);
+      } else {
+        if (callback == null) {
+          if (colorAnimation != null) {
+            removeAnimation(colorAnimation);
+          }
+          colorAnimation = null;
+        } else {
+          colorAnimation = new ValueCallbackKeyframeAnimation<>((LottieValueCallback<Integer>) callback);
+          colorAnimation.addUpdateListener(this);
+          addAnimation(colorAnimation);
+        }
+      }
+    } else if (property == LottieProperty.STROKE_COLOR) {
+      if (strokeColorAnimation != null) {
+        strokeColorAnimation.setValueCallback((LottieValueCallback<Integer>) callback);
+      } else {
+        if (callback == null) {
+          if (strokeColorAnimation != null) {
+            removeAnimation(strokeColorAnimation);
+          }
+          strokeColorAnimation = null;
+        } else {
+          strokeColorAnimation = new ValueCallbackKeyframeAnimation<>((LottieValueCallback<Integer>) callback);
+          strokeColorAnimation.addUpdateListener(this);
+          addAnimation(strokeColorAnimation);
+        }
+      }
+    } else if (property == LottieProperty.STROKE_WIDTH) {
+      if (strokeWidthAnimation != null) {
+        strokeWidthAnimation.setValueCallback((LottieValueCallback<Float>) callback);
+      } else {
+        if (callback == null) {
+          if (strokeWidthAnimation != null) {
+            removeAnimation(strokeWidthAnimation);
+          }
+          strokeWidthAnimation = null;
+        } else {
+          strokeWidthAnimation = new ValueCallbackKeyframeAnimation<>((LottieValueCallback<Float>) callback);
+          strokeWidthAnimation.addUpdateListener(this);
+          addAnimation(strokeWidthAnimation);
+        }
+      }
+    } else if (property == LottieProperty.TEXT_TRACKING) {
+      if (trackingAnimation != null) {
+        trackingAnimation.setValueCallback((LottieValueCallback<Float>) callback);
+      } else {
+        if (callback == null) {
+          if (trackingAnimation != null) {
+            removeAnimation(trackingAnimation);
+          }
+          trackingAnimation = null;
+        } else {
+          trackingAnimation = new ValueCallbackKeyframeAnimation<>((LottieValueCallback<Float>) callback);
+          trackingAnimation.addUpdateListener(this);
+          addAnimation(trackingAnimation);
+        }
+      }
     }
   }
 }