Allow controlling split dimension transform positions with dynamic properties
diff --git a/lottie/src/main/java/com/airbnb/lottie/LottieProperty.java b/lottie/src/main/java/com/airbnb/lottie/LottieProperty.java
index aac65d1..2982d65 100644
--- a/lottie/src/main/java/com/airbnb/lottie/LottieProperty.java
+++ b/lottie/src/main/java/com/airbnb/lottie/LottieProperty.java
@@ -69,6 +69,10 @@
   PointF TRANSFORM_ANCHOR_POINT = new PointF();
   /** In Px */
   PointF TRANSFORM_POSITION = new PointF();
+  /** When split dimensions is enabled. In Px */
+  Float TRANSFORM_POSITION_X = 15f;
+  /** When split dimensions is enabled. In Px */
+  Float TRANSFORM_POSITION_Y = 16f;
   /** In Px */
   PointF ELLIPSE_SIZE = new PointF();
   /** In Px */
diff --git a/lottie/src/main/java/com/airbnb/lottie/animation/keyframe/SplitDimensionPathKeyframeAnimation.java b/lottie/src/main/java/com/airbnb/lottie/animation/keyframe/SplitDimensionPathKeyframeAnimation.java
index 8fd0300..fd0afe8 100644
--- a/lottie/src/main/java/com/airbnb/lottie/animation/keyframe/SplitDimensionPathKeyframeAnimation.java
+++ b/lottie/src/main/java/com/airbnb/lottie/animation/keyframe/SplitDimensionPathKeyframeAnimation.java
@@ -3,14 +3,22 @@
 import android.graphics.PointF;
 
 import com.airbnb.lottie.value.Keyframe;
+import com.airbnb.lottie.value.LottieValueCallback;
 
 import java.util.Collections;
 
+import androidx.annotation.Nullable;
+
 public class SplitDimensionPathKeyframeAnimation extends BaseKeyframeAnimation<PointF, PointF> {
   private final PointF point = new PointF();
+  private final PointF pointWithCallbackValues = new PointF();
   private final BaseKeyframeAnimation<Float, Float> xAnimation;
   private final BaseKeyframeAnimation<Float, Float> yAnimation;
 
+  @Nullable protected LottieValueCallback<Float> xValueCallback;
+  @Nullable protected LottieValueCallback<Float> yValueCallback;
+
+
   public SplitDimensionPathKeyframeAnimation(
       BaseKeyframeAnimation<Float, Float> xAnimation,
       BaseKeyframeAnimation<Float, Float> yAnimation) {
@@ -22,6 +30,26 @@
     setProgress(getProgress());
   }
 
+  public void setXValueCallback(@Nullable LottieValueCallback<Float> xValueCallback) {
+    if (this.xValueCallback != null) {
+      this.xValueCallback.setAnimation(null);
+    }
+    this.xValueCallback = xValueCallback;
+    if (xValueCallback != null) {
+      xValueCallback.setAnimation(this);
+    }
+  }
+
+  public void setYValueCallback(@Nullable LottieValueCallback<Float> yValueCallback) {
+    if (this.yValueCallback != null) {
+      this.yValueCallback.setAnimation(null);
+    }
+    this.yValueCallback = yValueCallback;
+    if (yValueCallback != null) {
+      yValueCallback.setAnimation(this);
+    }
+  }
+
   @Override public void setProgress(float progress) {
     xAnimation.setProgress(progress);
     yAnimation.setProgress(progress);
@@ -36,6 +64,40 @@
   }
 
   @Override PointF getValue(Keyframe<PointF> keyframe, float keyframeProgress) {
-    return point;
+    Float xCallbackValue = null;
+    Float yCallbackValue = null;
+
+    if (xValueCallback != null) {
+      Keyframe<Float> xKeyframe = xAnimation.getCurrentKeyframe();
+      if (xKeyframe != null) {
+          float progress = xAnimation.getInterpolatedCurrentKeyframeProgress();
+          Float endFrame = xKeyframe.endFrame;
+          xCallbackValue = xValueCallback.getValueInternal(xKeyframe.startFrame, endFrame == null ? xKeyframe.startFrame : endFrame, xKeyframe.startValue,
+              xKeyframe.endValue, keyframeProgress, keyframeProgress, progress);
+      }
+    }
+    if (yValueCallback != null) {
+      Keyframe<Float> yKeyframe = yAnimation.getCurrentKeyframe();
+      if (yKeyframe != null) {
+        float progress = yAnimation.getInterpolatedCurrentKeyframeProgress();
+        Float endFrame = yKeyframe.endFrame;
+        yCallbackValue = yValueCallback.getValueInternal(yKeyframe.startFrame, endFrame == null ? yKeyframe.startFrame : endFrame, yKeyframe.startValue,
+            yKeyframe.endValue, keyframeProgress, keyframeProgress, progress);
+      }
+    }
+
+    if (xCallbackValue == null) {
+      pointWithCallbackValues.set(point.x, 0f);
+    } else {
+      pointWithCallbackValues.set(xCallbackValue, 0f);
+    }
+
+    if (yCallbackValue == null) {
+      pointWithCallbackValues.set(pointWithCallbackValues.x, point.y);
+    } else {
+      pointWithCallbackValues.set(pointWithCallbackValues.x, yCallbackValue);
+    }
+
+    return pointWithCallbackValues;
   }
 }
diff --git a/lottie/src/main/java/com/airbnb/lottie/animation/keyframe/TransformKeyframeAnimation.java b/lottie/src/main/java/com/airbnb/lottie/animation/keyframe/TransformKeyframeAnimation.java
index a422cb4..a05354c 100644
--- a/lottie/src/main/java/com/airbnb/lottie/animation/keyframe/TransformKeyframeAnimation.java
+++ b/lottie/src/main/java/com/airbnb/lottie/animation/keyframe/TransformKeyframeAnimation.java
@@ -17,6 +17,8 @@
 import static com.airbnb.lottie.LottieProperty.TRANSFORM_END_OPACITY;
 import static com.airbnb.lottie.LottieProperty.TRANSFORM_OPACITY;
 import static com.airbnb.lottie.LottieProperty.TRANSFORM_POSITION;
+import static com.airbnb.lottie.LottieProperty.TRANSFORM_POSITION_X;
+import static com.airbnb.lottie.LottieProperty.TRANSFORM_POSITION_Y;
 import static com.airbnb.lottie.LottieProperty.TRANSFORM_ROTATION;
 import static com.airbnb.lottie.LottieProperty.TRANSFORM_SCALE;
 import static com.airbnb.lottie.LottieProperty.TRANSFORM_SKEW;
@@ -278,6 +280,10 @@
       } else {
         position.setValueCallback((LottieValueCallback<PointF>) callback);
       }
+    } else if (property == TRANSFORM_POSITION_X && position instanceof SplitDimensionPathKeyframeAnimation) {
+      ((SplitDimensionPathKeyframeAnimation) position).setXValueCallback((LottieValueCallback<Float>) callback);
+    } else if (property == TRANSFORM_POSITION_Y && position instanceof SplitDimensionPathKeyframeAnimation) {
+      ((SplitDimensionPathKeyframeAnimation) position).setYValueCallback((LottieValueCallback<Float>) callback);
     } else if (property == TRANSFORM_SCALE) {
       if (scale == null) {
         scale = new ValueCallbackKeyframeAnimation(callback, new ScaleXY());
diff --git a/sample/src/androidTest/java/com/airbnb/lottie/samples/LottieTest.kt b/sample/src/androidTest/java/com/airbnb/lottie/samples/LottieTest.kt
index 4782009..4b6ccb6 100644
--- a/sample/src/androidTest/java/com/airbnb/lottie/samples/LottieTest.kt
+++ b/sample/src/androidTest/java/com/airbnb/lottie/samples/LottieTest.kt
@@ -497,6 +497,27 @@
                 LottieProperty.TRANSFORM_POSITION,
                 LottieRelativePointValueCallback(PointF(20f, 20f)))
 
+
+        testDynamicProperty(
+            "Transform position X",
+            KeyPath("Shape Layer 1"),
+            LottieProperty.TRANSFORM_POSITION_X,
+            object : LottieValueCallback<Float>() {
+                override fun getValue(frameInfo: LottieFrameInfo<Float>) =  frameInfo.startValue
+            },
+            progress = 1f,
+            assetName = "Tests/SplitPathTransform.json")
+
+        testDynamicProperty(
+            "Transform position Y",
+            KeyPath("Shape Layer 1"),
+            LottieProperty.TRANSFORM_POSITION_Y,
+            object : LottieValueCallback<Float>() {
+                override fun getValue(frameInfo: LottieFrameInfo<Float>) =  frameInfo.startValue
+            },
+            progress = 1f,
+            assetName = "Tests/SplitPathTransform.json")
+
         testDynamicProperty(
                 "Transform position (relative)",
                 KeyPath("Shape Layer 1", "Rectangle"),
@@ -545,8 +566,6 @@
                 LottieProperty.POSITION,
                 LottieRelativePointValueCallback(PointF(20f, 20f)))
 
-
-
         testDynamicProperty(
                 "Ellipse size",
                 KeyPath("Shape Layer 1", "Ellipse", "Ellipse Path 1"),
@@ -786,8 +805,15 @@
         }
     }
 
-    private suspend fun <T> testDynamicProperty(name: String, keyPath: KeyPath, property: T, callback: LottieValueCallback<T>, progress: Float = 0f) {
-        withDrawable("Tests/Shapes.json", "Dynamic Properties", name) { drawable ->
+    private suspend fun <T> testDynamicProperty(
+        name: String,
+        keyPath: KeyPath,
+        property: T,
+        callback: LottieValueCallback<T>,
+        progress: Float = 0f,
+        assetName: String = "Tests/Shapes.json",
+    ) {
+        withDrawable(assetName, "Dynamic Properties", name) { drawable ->
             drawable.addValueCallback(keyPath, property, callback)
             drawable.progress = progress
         }
diff --git a/sample/src/main/assets/Tests/SplitPathTransform.json b/sample/src/main/assets/Tests/SplitPathTransform.json
new file mode 100644
index 0000000..2cf1f4a
--- /dev/null
+++ b/sample/src/main/assets/Tests/SplitPathTransform.json
@@ -0,0 +1 @@
+{"v":"5.7.4","fr":60,"ip":0,"op":63,"w":400,"h":400,"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":{"s":true,"x":{"a":1,"k":[{"i":{"x":[0.984],"y":[0.777]},"o":{"x":[0.411],"y":[0.047]},"t":0,"s":[200]},{"t":62,"s":[460]}],"ix":3},"y":{"a":1,"k":[{"i":{"x":[0.926],"y":[0.943]},"o":{"x":[0.028],"y":[1.152]},"t":0,"s":[200]},{"t":62,"s":[460]}],"ix":4}},"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":[119.332,119.332],"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":0,"k":[1,0,0,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":[-130.404,-126.932],"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":63,"st":0,"bm":0}],"markers":[]}
\ No newline at end of file