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