Skip to content

Commit

Permalink
Support for Animated.multiply node
Browse files Browse the repository at this point in the history
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 facebook#7071

Differential Revision: D3197663

fb-gh-sync-id: 35f64244a2482c487a81e5e7cd08f3c0e56d9b78
fbshipit-source-id: 35f64244a2482c487a81e5e7cd08f3c0e56d9b78
  • Loading branch information
kmagiera authored and Facebook Github Bot 0 committed Apr 19, 2016
1 parent 3e2a3ca commit ec5dfbf
Show file tree
Hide file tree
Showing 5 changed files with 150 additions and 0 deletions.
14 changes: 14 additions & 0 deletions Libraries/Animated/src/AnimatedImplementation.js
Original file line number Diff line number Diff line change
Expand Up @@ -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();
}
Expand All @@ -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()],
};
}
}

Expand Down
34 changes: 34 additions & 0 deletions Libraries/Animated/src/__tests__/AnimatedNative-test.js
Original file line number Diff line number Diff line change
Expand Up @@ -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();
Expand Down
Original file line number Diff line number Diff line change
@@ -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");
}
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -74,6 +74,8 @@ public void createAnimatedNode(int tag, ReadableMap config) {
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);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -341,4 +341,64 @@ public void testViewReceiveUpdatesWhenOneOfAnimationHasFinished() {
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<ReactStylesDiffMap> 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);
}
}

0 comments on commit ec5dfbf

Please sign in to comment.