From ec5dfbf8c7391b343c654362241673b862c2cdbb Mon Sep 17 00:00:00 2001 From: Krzysztof Magiera Date: Tue, 19 Apr 2016 13:19:27 -0700 Subject: [PATCH] Support for Animated.multiply node Summary:This change adds native animated support for Animated.multiply nodes. Animated.multiply allows for defining nodes that would output a product of values of the input nodes. **Test Plan** Run JS tests: `npm test Libraries/Animated/src/__tests__/AnimatedNative-test.js` Run java tests: `buck test ReactAndroid/src/test/java/com/facebook/react/animated` Closes https://github.com/facebook/react-native/pull/7071 Differential Revision: D3197663 fb-gh-sync-id: 35f64244a2482c487a81e5e7cd08f3c0e56d9b78 fbshipit-source-id: 35f64244a2482c487a81e5e7cd08f3c0e56d9b78 --- .../Animated/src/AnimatedImplementation.js | 14 +++++ .../src/__tests__/AnimatedNative-test.js | 34 +++++++++++ .../animated/MultiplicationAnimatedNode.java | 40 +++++++++++++ .../animated/NativeAnimatedNodesManager.java | 2 + .../NativeAnimatedNodeTraversalTest.java | 60 +++++++++++++++++++ 5 files changed, 150 insertions(+) create mode 100644 ReactAndroid/src/main/java/com/facebook/react/animated/MultiplicationAnimatedNode.java diff --git a/Libraries/Animated/src/AnimatedImplementation.js b/Libraries/Animated/src/AnimatedImplementation.js index 047a9361f..f153251a5 100644 --- a/Libraries/Animated/src/AnimatedImplementation.js +++ b/Libraries/Animated/src/AnimatedImplementation.js @@ -995,6 +995,12 @@ class AnimatedMultiplication extends AnimatedWithChildren { this._b = typeof b === 'number' ? new AnimatedValue(b) : b; } + __makeNative() { + super.__makeNative(); + this._a.__makeNative(); + this._b.__makeNative(); + } + __getValue(): number { return this._a.__getValue() * this._b.__getValue(); } @@ -1011,6 +1017,14 @@ class AnimatedMultiplication extends AnimatedWithChildren { __detach(): void { this._a.__removeChild(this); this._b.__removeChild(this); + super.__detach(); + } + + __getNativeConfig(): any { + return { + type: 'multiplication', + input: [this._a.__getNativeTag(), this._b.__getNativeTag()], + }; } } diff --git a/Libraries/Animated/src/__tests__/AnimatedNative-test.js b/Libraries/Animated/src/__tests__/AnimatedNative-test.js index d38d52abf..4e17ef207 100644 --- a/Libraries/Animated/src/__tests__/AnimatedNative-test.js +++ b/Libraries/Animated/src/__tests__/AnimatedNative-test.js @@ -124,6 +124,40 @@ describe('Animated', () => { .toBeCalledWith(additionCall[1].input[1], { type: 'value', value: 2 }); }); + it('sends a valid graph description for Animated.multiply nodes', () => { + var first = new Animated.Value(2); + var second = new Animated.Value(1); + + var c = new Animated.View(); + c.props = { + style: { + opacity: Animated.multiply(first, second), + }, + }; + c.componentWillMount(); + + Animated.timing(first, {toValue: 5, duration: 1000, useNativeDriver: true}).start(); + Animated.timing(second, {toValue: -1, duration: 1000, useNativeDriver: true}).start(); + + var nativeAnimatedModule = require('NativeModules').NativeAnimatedModule; + expect(nativeAnimatedModule.createAnimatedNode) + .toBeCalledWith(jasmine.any(Number), { type: 'multiplication', input: jasmine.any(Array) }); + var multiplicationCalls = nativeAnimatedModule.createAnimatedNode.mock.calls.filter( + (call) => call[1].type === 'multiplication' + ); + expect(multiplicationCalls.length).toBe(1); + var multiplicationCall = multiplicationCalls[0]; + var multiplicationNodeTag = multiplicationCall[0]; + var multiplicationConnectionCalls = nativeAnimatedModule.connectAnimatedNodes.mock.calls.filter( + (call) => call[1] === multiplicationNodeTag + ); + expect(multiplicationConnectionCalls.length).toBe(2); + expect(nativeAnimatedModule.createAnimatedNode) + .toBeCalledWith(multiplicationCall[1].input[0], { type: 'value', value: 2 }); + expect(nativeAnimatedModule.createAnimatedNode) + .toBeCalledWith(multiplicationCall[1].input[1], { type: 'value', value: 1 }); + }); + it('sends a valid timing animation description', () => { var anim = new Animated.Value(0); Animated.timing(anim, {toValue: 10, duration: 1000, useNativeDriver: true}).start(); diff --git a/ReactAndroid/src/main/java/com/facebook/react/animated/MultiplicationAnimatedNode.java b/ReactAndroid/src/main/java/com/facebook/react/animated/MultiplicationAnimatedNode.java new file mode 100644 index 000000000..9fe8d7a81 --- /dev/null +++ b/ReactAndroid/src/main/java/com/facebook/react/animated/MultiplicationAnimatedNode.java @@ -0,0 +1,40 @@ +package com.facebook.react.animated; + +import com.facebook.react.bridge.JSApplicationCausedNativeException; +import com.facebook.react.bridge.ReadableArray; +import com.facebook.react.bridge.ReadableMap; + +/** + * Animated node which takes two or more value node as an input and outputs a product of their + * values + */ +/*package*/ class MultiplicationAnimatedNode extends ValueAnimatedNode { + + private final NativeAnimatedNodesManager mNativeAnimatedNodesManager; + private final int[] mInputNodes; + + public MultiplicationAnimatedNode( + ReadableMap config, + NativeAnimatedNodesManager nativeAnimatedNodesManager) { + mNativeAnimatedNodesManager = nativeAnimatedNodesManager; + ReadableArray inputNodes = config.getArray("input"); + mInputNodes = new int[inputNodes.size()]; + for (int i = 0; i < mInputNodes.length; i++) { + mInputNodes[i] = inputNodes.getInt(i); + } + } + + @Override + public void update() { + mValue = 1; + for (int i = 0; i < mInputNodes.length; i++) { + AnimatedNode animatedNode = mNativeAnimatedNodesManager.getNodeById(mInputNodes[i]); + if (animatedNode != null && animatedNode instanceof ValueAnimatedNode) { + mValue *= ((ValueAnimatedNode) animatedNode).mValue; + } 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/NativeAnimatedNodesManager.java b/ReactAndroid/src/main/java/com/facebook/react/animated/NativeAnimatedNodesManager.java index 5e88676be..c833a75d3 100644 --- a/ReactAndroid/src/main/java/com/facebook/react/animated/NativeAnimatedNodesManager.java +++ b/ReactAndroid/src/main/java/com/facebook/react/animated/NativeAnimatedNodesManager.java @@ -74,6 +74,8 @@ import javax.annotation.Nullable; node = new PropsAnimatedNode(config, this); } else if ("addition".equals(type)) { node = new AdditionAnimatedNode(config, this); + } else if ("multiplication".equals(type)) { + node = new MultiplicationAnimatedNode(config, this); } else { throw new JSApplicationIllegalArgumentException("Unsupported node type: " + type); } 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 2ea909cbf..9e713ee0d 100644 --- a/ReactAndroid/src/test/java/com/facebook/react/animated/NativeAnimatedNodeTraversalTest.java +++ b/ReactAndroid/src/test/java/com/facebook/react/animated/NativeAnimatedNodeTraversalTest.java @@ -341,4 +341,64 @@ public class NativeAnimatedNodeTraversalTest { mNativeAnimatedNodesManager.runUpdates(nextFrameTime()); verifyNoMoreInteractions(mUIImplementationMock); } + + @Test + public void testMultiplicationNode() { + mNativeAnimatedNodesManager.createAnimatedNode( + 1, + JavaOnlyMap.of("type", "value", "value", 1d)); + mNativeAnimatedNodesManager.createAnimatedNode( + 2, + JavaOnlyMap.of("type", "value", "value", 5d)); + + mNativeAnimatedNodesManager.createAnimatedNode( + 3, + JavaOnlyMap.of("type", "multiplication", "input", JavaOnlyArray.of(1, 2))); + + mNativeAnimatedNodesManager.createAnimatedNode( + 4, + JavaOnlyMap.of("type", "style", "style", JavaOnlyMap.of("translateX", 3))); + mNativeAnimatedNodesManager.createAnimatedNode( + 5, + JavaOnlyMap.of("type", "props", "props", JavaOnlyMap.of("style", 4))); + mNativeAnimatedNodesManager.connectAnimatedNodes(1, 3); + mNativeAnimatedNodesManager.connectAnimatedNodes(2, 3); + mNativeAnimatedNodesManager.connectAnimatedNodes(3, 4); + mNativeAnimatedNodesManager.connectAnimatedNodes(4, 5); + mNativeAnimatedNodesManager.connectAnimatedNodeToView(5, 50); + + Callback animationCallback = mock(Callback.class); + JavaOnlyArray frames = JavaOnlyArray.of(0d, 1d); + mNativeAnimatedNodesManager.startAnimatingNode( + 1, + JavaOnlyMap.of("type", "frames", "frames", frames, "toValue", 2d), + animationCallback); + + mNativeAnimatedNodesManager.startAnimatingNode( + 2, + JavaOnlyMap.of("type", "frames", "frames", frames, "toValue", 10d), + animationCallback); + + ArgumentCaptor stylesCaptor = + ArgumentCaptor.forClass(ReactStylesDiffMap.class); + + reset(mUIImplementationMock); + mNativeAnimatedNodesManager.runUpdates(nextFrameTime()); + verify(mUIImplementationMock).synchronouslyUpdateViewOnUIThread(eq(50), stylesCaptor.capture()); + assertThat(stylesCaptor.getValue().getDouble("translateX", Double.NaN)).isEqualTo(5d); + + reset(mUIImplementationMock); + mNativeAnimatedNodesManager.runUpdates(nextFrameTime()); + verify(mUIImplementationMock).synchronouslyUpdateViewOnUIThread(eq(50), stylesCaptor.capture()); + assertThat(stylesCaptor.getValue().getDouble("translateX", Double.NaN)).isEqualTo(5d); + + reset(mUIImplementationMock); + mNativeAnimatedNodesManager.runUpdates(nextFrameTime()); + verify(mUIImplementationMock).synchronouslyUpdateViewOnUIThread(eq(50), stylesCaptor.capture()); + assertThat(stylesCaptor.getValue().getDouble("translateX", Double.NaN)).isEqualTo(20d); + + reset(mUIImplementationMock); + mNativeAnimatedNodesManager.runUpdates(nextFrameTime()); + verifyNoMoreInteractions(mUIImplementationMock); + } }