From 8e81644f64b96ab4726ce37a6a328acbe4a32783 Mon Sep 17 00:00:00 2001 From: Ryan Gomba Date: Wed, 2 Nov 2016 13:46:47 -0700 Subject: [PATCH] Implement NativeAnimated offsets on Android Summary: This diff implements NativeAnimation offsets on Android. Running the examples should show no change; however, calling `setOffset()` should offset the final value for any value node by that amount. This brings Android up to date with JS and iOS animation APIs. Closes https://github.com/facebook/react-native/pull/10680 Differential Revision: D4119609 fbshipit-source-id: 96dccdf25f67c64c6787fd9ac762ec841cefc46a --- .../react/animated/AdditionAnimatedNode.java | 2 +- .../react/animated/DiffClampAnimatedNode.java | 2 +- .../react/animated/DivisionAnimatedNode.java | 2 +- .../animated/InterpolationAnimatedNode.java | 2 +- .../animated/MultiplicationAnimatedNode.java | 2 +- .../react/animated/NativeAnimatedModule.java | 20 +++++++++++++++++++ .../animated/NativeAnimatedNodesManager.java | 19 ++++++++++++++++++ .../react/animated/PropsAnimatedNode.java | 2 +- .../react/animated/StyleAnimatedNode.java | 2 +- .../react/animated/TransformAnimatedNode.java | 2 +- .../react/animated/ValueAnimatedNode.java | 11 ++++++++++ .../NativeAnimatedNodeTraversalTest.java | 12 +++++------ 12 files changed, 64 insertions(+), 14 deletions(-) diff --git a/ReactAndroid/src/main/java/com/facebook/react/animated/AdditionAnimatedNode.java b/ReactAndroid/src/main/java/com/facebook/react/animated/AdditionAnimatedNode.java index 34b96be57..0fba5f90b 100644 --- a/ReactAndroid/src/main/java/com/facebook/react/animated/AdditionAnimatedNode.java +++ b/ReactAndroid/src/main/java/com/facebook/react/animated/AdditionAnimatedNode.java @@ -39,7 +39,7 @@ import com.facebook.react.bridge.ReadableMap; for (int i = 0; i < mInputNodes.length; i++) { AnimatedNode animatedNode = mNativeAnimatedNodesManager.getNodeById(mInputNodes[i]); if (animatedNode != null && animatedNode instanceof ValueAnimatedNode) { - mValue += ((ValueAnimatedNode) animatedNode).mValue; + mValue += ((ValueAnimatedNode) animatedNode).getValue(); } else { throw new JSApplicationCausedNativeException("Illegal node ID set as an input for " + "Animated.Add node"); diff --git a/ReactAndroid/src/main/java/com/facebook/react/animated/DiffClampAnimatedNode.java b/ReactAndroid/src/main/java/com/facebook/react/animated/DiffClampAnimatedNode.java index 2dc25d954..3d11792b3 100644 --- a/ReactAndroid/src/main/java/com/facebook/react/animated/DiffClampAnimatedNode.java +++ b/ReactAndroid/src/main/java/com/facebook/react/animated/DiffClampAnimatedNode.java @@ -48,6 +48,6 @@ import com.facebook.react.bridge.ReadableMap; } - return ((ValueAnimatedNode) animatedNode).mValue; + return ((ValueAnimatedNode) animatedNode).getValue(); } } diff --git a/ReactAndroid/src/main/java/com/facebook/react/animated/DivisionAnimatedNode.java b/ReactAndroid/src/main/java/com/facebook/react/animated/DivisionAnimatedNode.java index ccb38890d..152bc049a 100644 --- a/ReactAndroid/src/main/java/com/facebook/react/animated/DivisionAnimatedNode.java +++ b/ReactAndroid/src/main/java/com/facebook/react/animated/DivisionAnimatedNode.java @@ -38,7 +38,7 @@ import com.facebook.react.bridge.ReadableMap; for (int i = 0; i < mInputNodes.length; i++) { AnimatedNode animatedNode = mNativeAnimatedNodesManager.getNodeById(mInputNodes[i]); if (animatedNode != null && animatedNode instanceof ValueAnimatedNode) { - double value = ((ValueAnimatedNode) animatedNode).mValue; + double value = ((ValueAnimatedNode) animatedNode).getValue(); if (i == 0) { mValue = value; continue; diff --git a/ReactAndroid/src/main/java/com/facebook/react/animated/InterpolationAnimatedNode.java b/ReactAndroid/src/main/java/com/facebook/react/animated/InterpolationAnimatedNode.java index a673c0705..4f9e5fbc7 100644 --- a/ReactAndroid/src/main/java/com/facebook/react/animated/InterpolationAnimatedNode.java +++ b/ReactAndroid/src/main/java/com/facebook/react/animated/InterpolationAnimatedNode.java @@ -136,6 +136,6 @@ import javax.annotation.Nullable; throw new IllegalStateException("Trying to update interpolation node that has not been " + "attached to the parent"); } - mValue = interpolate(mParent.mValue, mInputRange, mOutputRange, mExtrapolateLeft, mExtrapolateRight); + mValue = interpolate(mParent.getValue(), mInputRange, mOutputRange, mExtrapolateLeft, mExtrapolateRight); } } diff --git a/ReactAndroid/src/main/java/com/facebook/react/animated/MultiplicationAnimatedNode.java b/ReactAndroid/src/main/java/com/facebook/react/animated/MultiplicationAnimatedNode.java index 5838ed0c5..2f79e1a91 100644 --- a/ReactAndroid/src/main/java/com/facebook/react/animated/MultiplicationAnimatedNode.java +++ b/ReactAndroid/src/main/java/com/facebook/react/animated/MultiplicationAnimatedNode.java @@ -39,7 +39,7 @@ import com.facebook.react.bridge.ReadableMap; for (int i = 0; i < mInputNodes.length; i++) { AnimatedNode animatedNode = mNativeAnimatedNodesManager.getNodeById(mInputNodes[i]); if (animatedNode != null && animatedNode instanceof ValueAnimatedNode) { - mValue *= ((ValueAnimatedNode) animatedNode).mValue; + mValue *= ((ValueAnimatedNode) animatedNode).getValue(); } else { throw new JSApplicationCausedNativeException("Illegal node ID set as an input for " + "Animated.multiply node"); diff --git a/ReactAndroid/src/main/java/com/facebook/react/animated/NativeAnimatedModule.java b/ReactAndroid/src/main/java/com/facebook/react/animated/NativeAnimatedModule.java index 4ef8f3e3a..5ae7294ee 100644 --- a/ReactAndroid/src/main/java/com/facebook/react/animated/NativeAnimatedModule.java +++ b/ReactAndroid/src/main/java/com/facebook/react/animated/NativeAnimatedModule.java @@ -242,6 +242,26 @@ public class NativeAnimatedModule extends ReactContextBaseJavaModule implements }); } + @ReactMethod + public void setAnimatedNodeOffset(final int tag, final double value) { + mOperations.add(new UIThreadOperation() { + @Override + public void execute(NativeAnimatedNodesManager animatedNodesManager) { + animatedNodesManager.setAnimatedNodeOffset(tag, value); + } + }); + } + + @ReactMethod + public void flattenAnimatedNodeOffset(final int tag) { + mOperations.add(new UIThreadOperation() { + @Override + public void execute(NativeAnimatedNodesManager animatedNodesManager) { + animatedNodesManager.flattenAnimatedNodeOffset(tag); + } + }); + } + @ReactMethod public void startAnimatingNode( final int animationId, diff --git a/ReactAndroid/src/main/java/com/facebook/react/animated/NativeAnimatedNodesManager.java b/ReactAndroid/src/main/java/com/facebook/react/animated/NativeAnimatedNodesManager.java index d9d7f449b..ecd54345f 100644 --- a/ReactAndroid/src/main/java/com/facebook/react/animated/NativeAnimatedNodesManager.java +++ b/ReactAndroid/src/main/java/com/facebook/react/animated/NativeAnimatedNodesManager.java @@ -136,6 +136,25 @@ import javax.annotation.Nullable; mUpdatedNodes.add(node); } + public void setAnimatedNodeOffset(int tag, double offset) { + AnimatedNode node = mAnimatedNodes.get(tag); + if (node == null || !(node instanceof ValueAnimatedNode)) { + throw new JSApplicationIllegalArgumentException("Animated node with tag " + tag + + " does not exists or is not a 'value' node"); + } + ((ValueAnimatedNode) node).mOffset = offset; + mUpdatedNodes.add(node); + } + + public void flattenAnimatedNodeOffset(int tag) { + AnimatedNode node = mAnimatedNodes.get(tag); + if (node == null || !(node instanceof ValueAnimatedNode)) { + throw new JSApplicationIllegalArgumentException("Animated node with tag " + tag + + " does not exists or is not a 'value' node"); + } + ((ValueAnimatedNode) node).flattenOffset(); + } + public void startAnimatingNode( int animationId, int animatedNodeTag, diff --git a/ReactAndroid/src/main/java/com/facebook/react/animated/PropsAnimatedNode.java b/ReactAndroid/src/main/java/com/facebook/react/animated/PropsAnimatedNode.java index c96e9af59..de5c3e6f0 100644 --- a/ReactAndroid/src/main/java/com/facebook/react/animated/PropsAnimatedNode.java +++ b/ReactAndroid/src/main/java/com/facebook/react/animated/PropsAnimatedNode.java @@ -56,7 +56,7 @@ import javax.annotation.Nullable; } else if (node instanceof StyleAnimatedNode) { ((StyleAnimatedNode) node).collectViewUpdates(propsMap); } else if (node instanceof ValueAnimatedNode) { - propsMap.putDouble(entry.getKey(), ((ValueAnimatedNode) node).mValue); + propsMap.putDouble(entry.getKey(), ((ValueAnimatedNode) node).getValue()); } else { throw new IllegalArgumentException("Unsupported type of node used in property node " + node.getClass()); diff --git a/ReactAndroid/src/main/java/com/facebook/react/animated/StyleAnimatedNode.java b/ReactAndroid/src/main/java/com/facebook/react/animated/StyleAnimatedNode.java index f23cf248a..be68582f8 100644 --- a/ReactAndroid/src/main/java/com/facebook/react/animated/StyleAnimatedNode.java +++ b/ReactAndroid/src/main/java/com/facebook/react/animated/StyleAnimatedNode.java @@ -46,7 +46,7 @@ import javax.annotation.Nullable; } else if (node instanceof TransformAnimatedNode) { ((TransformAnimatedNode) node).collectViewUpdates(propsMap); } else if (node instanceof ValueAnimatedNode) { - propsMap.putDouble(entry.getKey(), ((ValueAnimatedNode) node).mValue); + propsMap.putDouble(entry.getKey(), ((ValueAnimatedNode) node).getValue()); } else { throw new IllegalArgumentException("Unsupported type of node used in property node " + node.getClass()); diff --git a/ReactAndroid/src/main/java/com/facebook/react/animated/TransformAnimatedNode.java b/ReactAndroid/src/main/java/com/facebook/react/animated/TransformAnimatedNode.java index 69213dcde..10dc12a4a 100644 --- a/ReactAndroid/src/main/java/com/facebook/react/animated/TransformAnimatedNode.java +++ b/ReactAndroid/src/main/java/com/facebook/react/animated/TransformAnimatedNode.java @@ -70,7 +70,7 @@ import java.util.List; if (node == null) { throw new IllegalArgumentException("Mapped style node does not exists"); } else if (node instanceof ValueAnimatedNode) { - value = ((ValueAnimatedNode) node).mValue; + value = ((ValueAnimatedNode) node).getValue(); } else { throw new IllegalArgumentException("Unsupported type of node used as a transform child " + "node " + node.getClass()); diff --git a/ReactAndroid/src/main/java/com/facebook/react/animated/ValueAnimatedNode.java b/ReactAndroid/src/main/java/com/facebook/react/animated/ValueAnimatedNode.java index 4d8a42d5f..dfaa8330a 100644 --- a/ReactAndroid/src/main/java/com/facebook/react/animated/ValueAnimatedNode.java +++ b/ReactAndroid/src/main/java/com/facebook/react/animated/ValueAnimatedNode.java @@ -19,6 +19,7 @@ import javax.annotation.Nullable; */ /*package*/ class ValueAnimatedNode extends AnimatedNode { /*package*/ double mValue = Double.NaN; + /*package*/ double mOffset = 0; private @Nullable AnimatedNodeValueListener mValueListener; public ValueAnimatedNode() { @@ -27,6 +28,16 @@ import javax.annotation.Nullable; public ValueAnimatedNode(ReadableMap config) { mValue = config.getDouble("value"); + mOffset = config.getDouble("offset"); + } + + public double getValue() { + return mOffset + mValue; + } + + public void flattenOffset() { + mValue += mOffset; + mOffset = 0; } public void onValueUpdate() { diff --git a/ReactAndroid/src/test/java/com/facebook/react/animated/NativeAnimatedNodeTraversalTest.java b/ReactAndroid/src/test/java/com/facebook/react/animated/NativeAnimatedNodeTraversalTest.java index 9f4db9d6f..5476ab5ea 100644 --- a/ReactAndroid/src/test/java/com/facebook/react/animated/NativeAnimatedNodeTraversalTest.java +++ b/ReactAndroid/src/test/java/com/facebook/react/animated/NativeAnimatedNodeTraversalTest.java @@ -121,7 +121,7 @@ public class NativeAnimatedNodeTraversalTest { private void createSimpleAnimatedViewWithOpacity(int viewTag, double opacity) { mNativeAnimatedNodesManager.createAnimatedNode( 1, - JavaOnlyMap.of("type", "value", "value", opacity)); + JavaOnlyMap.of("type", "value", "value", opacity, "offset", 0d)); mNativeAnimatedNodesManager.createAnimatedNode( 2, JavaOnlyMap.of("type", "style", "style", JavaOnlyMap.of("opacity", 1))); @@ -387,10 +387,10 @@ public class NativeAnimatedNodeTraversalTest { double secondValue) { mNativeAnimatedNodesManager.createAnimatedNode( 1, - JavaOnlyMap.of("type", "value", "value", 100d)); + JavaOnlyMap.of("type", "value", "value", 100d, "offset", 0d)); mNativeAnimatedNodesManager.createAnimatedNode( 2, - JavaOnlyMap.of("type", "value", "value", 1000d)); + JavaOnlyMap.of("type", "value", "value", 1000d, "offset", 0d)); mNativeAnimatedNodesManager.createAnimatedNode( 3, @@ -558,10 +558,10 @@ public class NativeAnimatedNodeTraversalTest { public void testMultiplicationNode() { mNativeAnimatedNodesManager.createAnimatedNode( 1, - JavaOnlyMap.of("type", "value", "value", 1d)); + JavaOnlyMap.of("type", "value", "value", 1d, "offset", 0d)); mNativeAnimatedNodesManager.createAnimatedNode( 2, - JavaOnlyMap.of("type", "value", "value", 5d)); + JavaOnlyMap.of("type", "value", "value", 5d, "offset", 0d)); mNativeAnimatedNodesManager.createAnimatedNode( 3, @@ -669,7 +669,7 @@ public class NativeAnimatedNodeTraversalTest { public void testInterpolationNode() { mNativeAnimatedNodesManager.createAnimatedNode( 1, - JavaOnlyMap.of("type", "value", "value", 10d)); + JavaOnlyMap.of("type", "value", "value", 10d, "offset", 0d)); mNativeAnimatedNodesManager.createAnimatedNode( 2,