Add an implementation of Animated.subtract

Summary:
Fixes #18451

I've added another example to NativeAnimationsExample, which makes use of `Animated.substract()`, let me know if the example is not desired / doesn't add much value. Below two GIFs of the new method working on iOS and Android:

<img width="320" src="https://user-images.githubusercontent.com/1437605/38154748-165cc5f8-3474-11e8-8b31-504444271896.gif" />
<img width="320" src="https://user-images.githubusercontent.com/1437605/38154749-1679bff0-3474-11e8-80b1-b558d44e0494.gif" />

<!--
  Required: Write your test plan here. If you changed any code, please provide us with
  clear instructions on how you verified your changes work. Bonus points for screenshots and videos!
-->

https://github.com/facebook/react-native-website/pull/276

[GENERAL] [ENHANCEMENT] [Animated] - Implemented Animated.subtract
Closes https://github.com/facebook/react-native/pull/18630

Differential Revision: D7462867

Pulled By: hramos

fbshipit-source-id: 4cb0b8af08bb0c841e44ea2099889b8c02a22a4a
This commit is contained in:
Vladislav Pilgasov 2018-03-30 20:57:42 -07:00 committed by Facebook Github Bot
parent 1f27098a1a
commit 4906f8d28c
10 changed files with 254 additions and 0 deletions

View File

@ -20,6 +20,7 @@ const AnimatedModulo = require('./nodes/AnimatedModulo');
const AnimatedMultiplication = require('./nodes/AnimatedMultiplication');
const AnimatedNode = require('./nodes/AnimatedNode');
const AnimatedProps = require('./nodes/AnimatedProps');
const AnimatedSubtraction = require('./nodes/AnimatedSubtraction');
const AnimatedTracking = require('./nodes/AnimatedTracking');
const AnimatedValue = require('./nodes/AnimatedValue');
const AnimatedValueXY = require('./nodes/AnimatedValueXY');
@ -54,6 +55,13 @@ const add = function(
return new AnimatedAddition(a, b);
};
const subtract = function(
a: AnimatedNode | number,
b: AnimatedNode | number,
): AnimatedSubtraction {
return new AnimatedSubtraction(a, b);
};
const divide = function(
a: AnimatedNode | number,
b: AnimatedNode | number,
@ -568,6 +576,14 @@ module.exports = {
*/
add,
/**
* Creates a new Animated value composed by subtracting the second Animated
* value from the first Animated value.
*
* See http://facebook.github.io/react-native/docs/animated.html#subtract
*/
subtract,
/**
* Creates a new Animated value composed by dividing the first Animated value
* by the second Animated value.

View File

@ -333,6 +333,38 @@ describe('Native Animated', () => {
.toBeCalledWith(additionCall[1].input[1], {type: 'value', value: 2, offset: 0});
});
it('sends a valid graph description for Animated.subtract nodes', () => {
const first = new Animated.Value(2);
const second = new Animated.Value(1);
first.__makeNative();
second.__makeNative();
createAndMountComponent(Animated.View, {
style: {
opacity: Animated.subtract(first, second),
},
});
expect(nativeAnimatedModule.createAnimatedNode).toBeCalledWith(
expect.any(Number),
{type: 'subtraction', input: expect.any(Array)},
);
const subtractionCalls = nativeAnimatedModule.createAnimatedNode.mock.calls.filter(
(call) => call[1].type === 'subtraction'
);
expect(subtractionCalls.length).toBe(1);
const subtractionCall = subtractionCalls[0];
const subtractionNodeTag = subtractionCall[0];
const subtractionConnectionCalls = nativeAnimatedModule.connectAnimatedNodes.mock.calls.filter(
(call) => call[1] === subtractionNodeTag
);
expect(subtractionConnectionCalls.length).toBe(2);
expect(nativeAnimatedModule.createAnimatedNode)
.toBeCalledWith(subtractionCall[1].input[0], {type: 'value', value: 2, offset: 0});
expect(nativeAnimatedModule.createAnimatedNode)
.toBeCalledWith(subtractionCall[1].input[1], {type: 'value', value: 1, offset: 0});
});
it('sends a valid graph description for Animated.multiply nodes', () => {
const first = new Animated.Value(2);
const second = new Animated.Value(1);

View File

@ -0,0 +1,63 @@
/**
* Copyright (c) 2015-present, Facebook, Inc.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*
* @providesModule AnimatedSubtraction
* @flow
* @format
*/
'use strict';
const AnimatedInterpolation = require('./AnimatedInterpolation');
const AnimatedNode = require('./AnimatedNode');
const AnimatedValue = require('./AnimatedValue');
const AnimatedWithChildren = require('./AnimatedWithChildren');
import type {InterpolationConfigType} from './AnimatedInterpolation';
class AnimatedSubtraction extends AnimatedWithChildren {
_a: AnimatedNode;
_b: AnimatedNode;
constructor(a: AnimatedNode | number, b: AnimatedNode | number) {
super();
this._a = typeof a === 'number' ? new AnimatedValue(a) : a;
this._b = typeof b === 'number' ? new AnimatedValue(b) : b;
}
__makeNative() {
this._a.__makeNative();
this._b.__makeNative();
super.__makeNative();
}
__getValue(): number {
return this._a.__getValue() - this._b.__getValue();
}
interpolate(config: InterpolationConfigType): AnimatedInterpolation {
return new AnimatedInterpolation(this, config);
}
__attach(): void {
this._a.__addChild(this);
this._b.__addChild(this);
}
__detach(): void {
this._a.__removeChild(this);
this._b.__removeChild(this);
super.__detach();
}
__getNativeConfig(): any {
return {
type: 'subtraction',
input: [this._a.__getNativeTag(), this._b.__getNativeTag()],
};
}
}
module.exports = AnimatedSubtraction;

View File

@ -0,0 +1,13 @@
/**
* Copyright (c) 2015-present, Facebook, Inc.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
#import "RCTValueAnimatedNode.h"
@interface RCTSubtractionAnimatedNode : RCTValueAnimatedNode
@end

View File

@ -0,0 +1,27 @@
/**
* Copyright (c) 2015-present, Facebook, Inc.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
#import "RCTSubtractionAnimatedNode.h"
@implementation RCTSubtractionAnimatedNode
- (void)performUpdate
{
[super performUpdate];
NSArray<NSNumber *> *inputNodes = self.config[@"input"];
if (inputNodes.count > 1) {
RCTValueAnimatedNode *parent1 = (RCTValueAnimatedNode *)[self.parentNodes objectForKey:inputNodes[0]];
RCTValueAnimatedNode *parent2 = (RCTValueAnimatedNode *)[self.parentNodes objectForKey:inputNodes[1]];
if ([parent1 isKindOfClass:[RCTValueAnimatedNode class]] &&
[parent2 isKindOfClass:[RCTValueAnimatedNode class]]) {
self.value = parent1.value - parent2.value;
}
}
}
@end

View File

@ -111,6 +111,7 @@
2D3B5EFE1D9B0B4800451313 /* RCTStyleAnimatedNode.m in Sources */ = {isa = PBXBuildFile; fileRef = 13E501E31D07A6C9005F35D8 /* RCTStyleAnimatedNode.m */; };
2D3B5EFF1D9B0B4800451313 /* RCTTransformAnimatedNode.m in Sources */ = {isa = PBXBuildFile; fileRef = 13E501E51D07A6C9005F35D8 /* RCTTransformAnimatedNode.m */; };
2D3B5F001D9B0B4800451313 /* RCTValueAnimatedNode.m in Sources */ = {isa = PBXBuildFile; fileRef = 13E501E71D07A6C9005F35D8 /* RCTValueAnimatedNode.m */; };
2EC00631206EA19300586E91 /* RCTSubtractionAnimatedNode.m in Sources */ = {isa = PBXBuildFile; fileRef = 2EC00630206EA19300586E91 /* RCTSubtractionAnimatedNode.m */; };
44DB7D942024F74200588FCD /* RCTTrackingAnimatedNode.h in Headers */ = {isa = PBXBuildFile; fileRef = 44DB7D932024F74200588FCD /* RCTTrackingAnimatedNode.h */; };
44DB7D952024F74200588FCD /* RCTTrackingAnimatedNode.h in Headers */ = {isa = PBXBuildFile; fileRef = 44DB7D932024F74200588FCD /* RCTTrackingAnimatedNode.h */; };
44DB7D972024F75100588FCD /* RCTTrackingAnimatedNode.m in Sources */ = {isa = PBXBuildFile; fileRef = 44DB7D962024F75100588FCD /* RCTTrackingAnimatedNode.m */; };
@ -213,6 +214,8 @@
19F00F201DC8847500113FEE /* RCTEventAnimation.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; lineEnding = 0; path = RCTEventAnimation.h; sourceTree = "<group>"; xcLanguageSpecificationIdentifier = xcode.lang.objcpp; };
19F00F211DC8847500113FEE /* RCTEventAnimation.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RCTEventAnimation.m; sourceTree = "<group>"; };
2D2A28201D9B03D100D4039D /* libRCTAnimation.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = libRCTAnimation.a; sourceTree = BUILT_PRODUCTS_DIR; };
2EC0062F206EA15F00586E91 /* RCTSubtractionAnimatedNode.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = RCTSubtractionAnimatedNode.h; sourceTree = "<group>"; };
2EC00630206EA19300586E91 /* RCTSubtractionAnimatedNode.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = RCTSubtractionAnimatedNode.m; sourceTree = "<group>"; };
44DB7D932024F74200588FCD /* RCTTrackingAnimatedNode.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = RCTTrackingAnimatedNode.h; sourceTree = "<group>"; };
44DB7D962024F75100588FCD /* RCTTrackingAnimatedNode.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = RCTTrackingAnimatedNode.m; sourceTree = "<group>"; };
5C9894931D999639008027DB /* RCTDivisionAnimatedNode.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; lineEnding = 0; path = RCTDivisionAnimatedNode.h; sourceTree = "<group>"; xcLanguageSpecificationIdentifier = xcode.lang.objcpp; };
@ -246,6 +249,8 @@
193F64F31D776EC6004D1CAA /* RCTDiffClampAnimatedNode.m */,
13E501D61D07A6C9005F35D8 /* RCTAdditionAnimatedNode.h */,
13E501D71D07A6C9005F35D8 /* RCTAdditionAnimatedNode.m */,
2EC0062F206EA15F00586E91 /* RCTSubtractionAnimatedNode.h */,
2EC00630206EA19300586E91 /* RCTSubtractionAnimatedNode.m */,
13E501D81D07A6C9005F35D8 /* RCTAnimatedNode.h */,
13E501D91D07A6C9005F35D8 /* RCTAnimatedNode.m */,
13E501DC1D07A6C9005F35D8 /* RCTInterpolationAnimatedNode.h */,
@ -483,6 +488,7 @@
5C9894951D999639008027DB /* RCTDivisionAnimatedNode.m in Sources */,
13E501EF1D07A6C9005F35D8 /* RCTTransformAnimatedNode.m in Sources */,
194804EE1E975D8E00623005 /* RCTDecayAnimation.m in Sources */,
2EC00631206EA19300586E91 /* RCTSubtractionAnimatedNode.m in Sources */,
);
runOnlyForDeploymentPostprocessing = 0;
};

View File

@ -23,6 +23,7 @@
#import "RCTPropsAnimatedNode.h"
#import "RCTSpringAnimation.h"
#import "RCTStyleAnimatedNode.h"
#import "RCTSubtractionAnimatedNode.h"
#import "RCTTransformAnimatedNode.h"
#import "RCTValueAnimatedNode.h"
#import "RCTTrackingAnimatedNode.h"
@ -66,6 +67,7 @@
@"division" : [RCTDivisionAnimatedNode class],
@"multiplication" : [RCTMultiplicationAnimatedNode class],
@"modulus" : [RCTModuloAnimatedNode class],
@"subtraction" : [RCTSubtractionAnimatedNode class],
@"transform" : [RCTTransformAnimatedNode class],
@"tracking" : [RCTTrackingAnimatedNode class]};
});

View File

@ -429,6 +429,48 @@ exports.examples = [
);
},
},
{
title: 'Multistage With Subtract',
render: function() {
return (
<Tester type="timing" config={{duration: 1000}}>
{anim => (
<Animated.View
style={[
styles.block,
{
transform: [
{
translateX: anim.interpolate({
inputRange: [0, 1],
outputRange: [0, 200],
}),
},
{
translateY: anim.interpolate({
inputRange: [0, 0.5, 1],
outputRange: [0, 50, 0],
}),
},
],
opacity: Animated.subtract(
anim.interpolate({
inputRange: [0, 1],
outputRange: [1, 1],
}),
anim.interpolate({
inputRange: [0, 0.5, 1],
outputRange: [0, 0.5, 0],
}),
),
},
]}
/>
)}
</Tester>
);
},
},
{
title: 'Scale interpolation with clamping',
render: function() {

View File

@ -93,6 +93,8 @@ import javax.annotation.Nullable;
node = new InterpolationAnimatedNode(config);
} else if ("addition".equals(type)) {
node = new AdditionAnimatedNode(config, this);
} else if ("subtraction".equals(type)) {
node = new SubtractionAnimatedNode(config, this);
} else if ("division".equals(type)) {
node = new DivisionAnimatedNode(config, this);
} else if ("multiplication".equals(type)) {

View File

@ -0,0 +1,51 @@
/**
* Copyright (c) 2015-present, Facebook, Inc.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
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 that plays a role of value aggregator. It takes two or more value nodes as an input
* and outputs a difference of values outputted by those nodes.
*/
/*package*/ class SubtractionAnimatedNode extends ValueAnimatedNode {
private final NativeAnimatedNodesManager mNativeAnimatedNodesManager;
private final int[] mInputNodes;
public SubtractionAnimatedNode(
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() {
for (int i = 0; i < mInputNodes.length; i++) {
AnimatedNode animatedNode = mNativeAnimatedNodesManager.getNodeById(mInputNodes[i]);
if (animatedNode != null && animatedNode instanceof ValueAnimatedNode) {
double value = ((ValueAnimatedNode) animatedNode).getValue();
if (i == 0) {
mValue = value;
continue;
}
mValue -= ((ValueAnimatedNode) animatedNode).getValue();
} else {
throw new JSApplicationCausedNativeException("Illegal node ID set as an input for " +
"Animated.subtract node");
}
}
}
}