Skip to content

Commit

Permalink
Add an implementation of Animated.subtract
Browse files Browse the repository at this point in the history
Summary:
Fixes facebook#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!
-->

facebook/react-native-website#276

[GENERAL] [ENHANCEMENT] [Animated] - Implemented Animated.subtract
Closes facebook#18630

Differential Revision: D7462867

Pulled By: hramos

fbshipit-source-id: 4cb0b8af08bb0c841e44ea2099889b8c02a22a4a
  • Loading branch information
avendiart authored and bunnyc1986 committed May 11, 2018
1 parent f7d3249 commit b158190
Show file tree
Hide file tree
Showing 10 changed files with 254 additions and 0 deletions.
16 changes: 16 additions & 0 deletions Libraries/Animated/src/AnimatedImplementation.js
Original file line number Diff line number Diff line change
Expand Up @@ -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');
Expand Down Expand Up @@ -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,
Expand Down Expand Up @@ -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.
Expand Down
32 changes: 32 additions & 0 deletions Libraries/Animated/src/__tests__/AnimatedNative-test.js
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down
63 changes: 63 additions & 0 deletions Libraries/Animated/src/nodes/AnimatedSubtraction.js
Original file line number Diff line number Diff line change
@@ -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;
13 changes: 13 additions & 0 deletions Libraries/NativeAnimation/Nodes/RCTSubtractionAnimatedNode.h
Original file line number Diff line number Diff line change
@@ -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

27 changes: 27 additions & 0 deletions Libraries/NativeAnimation/Nodes/RCTSubtractionAnimatedNode.m
Original file line number Diff line number Diff line change
@@ -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

Original file line number Diff line number Diff line change
Expand Up @@ -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 */; };
Expand Down Expand Up @@ -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; };
Expand Down Expand Up @@ -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 */,
Expand Down Expand Up @@ -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;
};
Expand Down
2 changes: 2 additions & 0 deletions Libraries/NativeAnimation/RCTNativeAnimatedNodesManager.m
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down Expand Up @@ -66,6 +67,7 @@ - (void)createAnimatedNode:(nonnull NSNumber *)tag
@"division" : [RCTDivisionAnimatedNode class],
@"multiplication" : [RCTMultiplicationAnimatedNode class],
@"modulus" : [RCTModuloAnimatedNode class],
@"subtraction" : [RCTSubtractionAnimatedNode class],
@"transform" : [RCTTransformAnimatedNode class],
@"tracking" : [RCTTrackingAnimatedNode class]};
});
Expand Down
42 changes: 42 additions & 0 deletions RNTester/js/NativeAnimationsExample.js
Original file line number Diff line number Diff line change
Expand Up @@ -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() {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -93,6 +93,8 @@ public void createAnimatedNode(int tag, ReadableMap config) {
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)) {
Expand Down
Original file line number Diff line number Diff line change
@@ -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");
}
}
}
}

0 comments on commit b158190

Please sign in to comment.