From 0a0dd30c6a3761bb7e48637a1a9cbb47d277f7f2 Mon Sep 17 00:00:00 2001 From: Kevin Gozali Date: Mon, 26 Sep 2016 16:32:27 -0700 Subject: [PATCH] Introduced AnimatedDivision Summary: Combining 2 animated values via addition, multiplication, and modulo are already supported, and this adds another one: division. There are some cases where an animated value needs to invert (1 / x) another animated value for calculation. An example is inverting a scale (2x --> 0.5x), e.g.: ``` const a = Animated.Value(1); const b = Animated.divide(1, a); Animated.spring(a, { toValue: 2, }).start(); ``` `b` will then follow `a`'s spring animation and produce the value of `1 / a`. The basic usage is like this: ``` ``` In this example, the inner image won't get stretched at all because the parent's scaling gets cancelled out. Also added this to native animated implementation. Reviewed By: foghina, mmmulani Differential Revision: D3922891 fbshipit-source-id: 32508956c4b65b2deb7574d50a10c85b4809b961 --- .../Animated/src/AnimatedImplementation.js | 70 +++++++++++++++++-- .../Nodes/RCTDivisionAnimatedNode.h | 14 ++++ .../Nodes/RCTDivisionAnimatedNode.m | 34 +++++++++ .../RCTAnimation.xcodeproj/project.pbxproj | 12 +++- .../NativeAnimation/RCTNativeAnimatedModule.m | 2 + .../react/animated/DivisionAnimatedNode.java | 57 +++++++++++++++ .../animated/NativeAnimatedNodesManager.java | 2 + 7 files changed, 184 insertions(+), 7 deletions(-) create mode 100644 Libraries/NativeAnimation/Nodes/RCTDivisionAnimatedNode.h create mode 100644 Libraries/NativeAnimation/Nodes/RCTDivisionAnimatedNode.m create mode 100644 ReactAndroid/src/main/java/com/facebook/react/animated/DivisionAnimatedNode.java diff --git a/Libraries/Animated/src/AnimatedImplementation.js b/Libraries/Animated/src/AnimatedImplementation.js index 1af0af58b..4182a8aac 100644 --- a/Libraries/Animated/src/AnimatedImplementation.js +++ b/Libraries/Animated/src/AnimatedImplementation.js @@ -1136,6 +1136,54 @@ class AnimatedAddition extends AnimatedWithChildren { } } +class AnimatedDivision extends AnimatedWithChildren { + _a: Animated; + _b: Animated; + + constructor(a: Animated | number, b: Animated | number) { + super(); + this._a = typeof a === 'number' ? new AnimatedValue(a) : a; + this._b = typeof b === 'number' ? new AnimatedValue(b) : b; + } + + __makeNative() { + super.__makeNative(); + this._a.__makeNative(); + this._b.__makeNative(); + } + + __getValue(): number { + const a = this._a.__getValue(); + const b = this._b.__getValue(); + if (b === 0) { + console.error('Detected division by zero in AnimatedDivision'); + } + return a / b; + } + + 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: 'division', + input: [this._a.__getNativeTag(), this._b.__getNativeTag()], + }; + } +} + class AnimatedMultiplication extends AnimatedWithChildren { _a: Animated; _b: Animated; @@ -1795,15 +1843,22 @@ type CompositeAnimation = { }; var add = function( - a: Animated, - b: Animated + a: Animated | number, + b: Animated | number, ): AnimatedAddition { return new AnimatedAddition(a, b); }; +var divide = function( + a: Animated | number, + b: Animated | number, +): AnimatedDivision { + return new AnimatedDivision(a, b); +}; + var multiply = function( - a: Animated, - b: Animated + a: Animated | number, + b: Animated | number, ): AnimatedMultiplication { return new AnimatedMultiplication(a, b); }; @@ -2295,6 +2350,13 @@ module.exports = { * together. */ add, + + /** + * Creates a new Animated value composed by dividing the first Animated value + * by the second Animated value. + */ + divide, + /** * Creates a new Animated value composed from two Animated values multiplied * together. diff --git a/Libraries/NativeAnimation/Nodes/RCTDivisionAnimatedNode.h b/Libraries/NativeAnimation/Nodes/RCTDivisionAnimatedNode.h new file mode 100644 index 000000000..83fac043b --- /dev/null +++ b/Libraries/NativeAnimation/Nodes/RCTDivisionAnimatedNode.h @@ -0,0 +1,14 @@ +/** + * Copyright (c) 2015-present, Facebook, Inc. + * All rights reserved. + * + * This source code is licensed under the BSD-style license found in the + * LICENSE file in the root directory of this source tree. An additional grant + * of patent rights can be found in the PATENTS file in the same directory. + */ + +#import "RCTValueAnimatedNode.h" + +@interface RCTDivisionAnimatedNode : RCTValueAnimatedNode + +@end diff --git a/Libraries/NativeAnimation/Nodes/RCTDivisionAnimatedNode.m b/Libraries/NativeAnimation/Nodes/RCTDivisionAnimatedNode.m new file mode 100644 index 000000000..ae2423e54 --- /dev/null +++ b/Libraries/NativeAnimation/Nodes/RCTDivisionAnimatedNode.m @@ -0,0 +1,34 @@ +/** + * Copyright (c) 2015-present, Facebook, Inc. + * All rights reserved. + * + * This source code is licensed under the BSD-style license found in the + * LICENSE file in the root directory of this source tree. An additional grant + * of patent rights can be found in the PATENTS file in the same directory. + */ + +#import "RCTDivisionAnimatedNode.h" +#import "RCTLog.h" + +@implementation RCTDivisionAnimatedNode + +- (void)performUpdate +{ + [super performUpdate]; + + NSArray *inputNodes = self.config[@"input"]; + if (inputNodes.count > 1) { + RCTValueAnimatedNode *parent1 = (RCTValueAnimatedNode *)self.parentNodes[inputNodes[0]]; + RCTValueAnimatedNode *parent2 = (RCTValueAnimatedNode *)self.parentNodes[inputNodes[1]]; + if ([parent1 isKindOfClass:[RCTValueAnimatedNode class]] && + [parent2 isKindOfClass:[RCTValueAnimatedNode class]]) { + if (parent2.value == 0) { + RCTLogError(@"Detected a division by zero in Animated.divide node"); + return; + } + self.value = parent1.value / parent2.value; + } + } +} + +@end diff --git a/Libraries/NativeAnimation/RCTAnimation.xcodeproj/project.pbxproj b/Libraries/NativeAnimation/RCTAnimation.xcodeproj/project.pbxproj index 8e1598423..7f6287106 100644 --- a/Libraries/NativeAnimation/RCTAnimation.xcodeproj/project.pbxproj +++ b/Libraries/NativeAnimation/RCTAnimation.xcodeproj/project.pbxproj @@ -19,8 +19,9 @@ 13E501EE1D07A6C9005F35D8 /* RCTStyleAnimatedNode.m in Sources */ = {isa = PBXBuildFile; fileRef = 13E501E31D07A6C9005F35D8 /* RCTStyleAnimatedNode.m */; }; 13E501EF1D07A6C9005F35D8 /* RCTTransformAnimatedNode.m in Sources */ = {isa = PBXBuildFile; fileRef = 13E501E51D07A6C9005F35D8 /* RCTTransformAnimatedNode.m */; }; 13E501F01D07A6C9005F35D8 /* RCTValueAnimatedNode.m in Sources */ = {isa = PBXBuildFile; fileRef = 13E501E71D07A6C9005F35D8 /* RCTValueAnimatedNode.m */; }; - 94DAE3F91D7334A70059942F /* RCTModuloAnimatedNode.m in Sources */ = {isa = PBXBuildFile; fileRef = 94DAE3F81D7334A70059942F /* RCTModuloAnimatedNode.m */; }; 193F64F41D776EC6004D1CAA /* RCTDiffClampAnimatedNode.m in Sources */ = {isa = PBXBuildFile; fileRef = 193F64F31D776EC6004D1CAA /* RCTDiffClampAnimatedNode.m */; }; + 5C9894951D999639008027DB /* RCTDivisionAnimatedNode.m in Sources */ = {isa = PBXBuildFile; fileRef = 5C9894941D999639008027DB /* RCTDivisionAnimatedNode.m */; }; + 94DAE3F91D7334A70059942F /* RCTModuloAnimatedNode.m in Sources */ = {isa = PBXBuildFile; fileRef = 94DAE3F81D7334A70059942F /* RCTModuloAnimatedNode.m */; }; /* End PBXBuildFile section */ /* Begin PBXCopyFilesBuildPhase section */ @@ -61,10 +62,12 @@ 13E501E51D07A6C9005F35D8 /* RCTTransformAnimatedNode.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RCTTransformAnimatedNode.m; sourceTree = ""; }; 13E501E61D07A6C9005F35D8 /* RCTValueAnimatedNode.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RCTValueAnimatedNode.h; sourceTree = ""; }; 13E501E71D07A6C9005F35D8 /* RCTValueAnimatedNode.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RCTValueAnimatedNode.m; sourceTree = ""; }; - 94DAE3F71D7334A70059942F /* RCTModuloAnimatedNode.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RCTModuloAnimatedNode.h; sourceTree = ""; }; - 94DAE3F81D7334A70059942F /* RCTModuloAnimatedNode.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RCTModuloAnimatedNode.m; sourceTree = ""; }; 193F64F21D776EC6004D1CAA /* RCTDiffClampAnimatedNode.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RCTDiffClampAnimatedNode.h; sourceTree = ""; }; 193F64F31D776EC6004D1CAA /* RCTDiffClampAnimatedNode.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RCTDiffClampAnimatedNode.m; sourceTree = ""; }; + 5C9894931D999639008027DB /* RCTDivisionAnimatedNode.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RCTDivisionAnimatedNode.h; sourceTree = ""; }; + 5C9894941D999639008027DB /* RCTDivisionAnimatedNode.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RCTDivisionAnimatedNode.m; sourceTree = ""; }; + 94DAE3F71D7334A70059942F /* RCTModuloAnimatedNode.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RCTModuloAnimatedNode.h; sourceTree = ""; }; + 94DAE3F81D7334A70059942F /* RCTModuloAnimatedNode.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RCTModuloAnimatedNode.m; sourceTree = ""; }; /* End PBXFileReference section */ /* Begin PBXFrameworksBuildPhase section */ @@ -89,6 +92,8 @@ 13E501D51D07A6C9005F35D8 /* Nodes */ = { isa = PBXGroup; children = ( + 5C9894931D999639008027DB /* RCTDivisionAnimatedNode.h */, + 5C9894941D999639008027DB /* RCTDivisionAnimatedNode.m */, 193F64F21D776EC6004D1CAA /* RCTDiffClampAnimatedNode.h */, 193F64F31D776EC6004D1CAA /* RCTDiffClampAnimatedNode.m */, 13E501D61D07A6C9005F35D8 /* RCTAdditionAnimatedNode.h */, @@ -196,6 +201,7 @@ 13E501E91D07A6C9005F35D8 /* RCTAnimatedNode.m in Sources */, 13E501EB1D07A6C9005F35D8 /* RCTInterpolationAnimatedNode.m in Sources */, 13E501E81D07A6C9005F35D8 /* RCTAdditionAnimatedNode.m in Sources */, + 5C9894951D999639008027DB /* RCTDivisionAnimatedNode.m in Sources */, 13E501EA1D07A6C9005F35D8 /* RCTAnimationDriverNode.m in Sources */, 13E501EF1D07A6C9005F35D8 /* RCTTransformAnimatedNode.m in Sources */, 13E501D41D07A644005F35D8 /* RCTViewPropertyMapper.m in Sources */, diff --git a/Libraries/NativeAnimation/RCTNativeAnimatedModule.m b/Libraries/NativeAnimation/RCTNativeAnimatedModule.m index 38d0f34f2..e88db8ade 100644 --- a/Libraries/NativeAnimation/RCTNativeAnimatedModule.m +++ b/Libraries/NativeAnimation/RCTNativeAnimatedModule.m @@ -16,6 +16,7 @@ #import "RCTInterpolationAnimatedNode.h" #import "RCTLog.h" #import "RCTDiffClampAnimatedNode.h" +#import "RCTDivisionAnimatedNode.h" #import "RCTModuloAnimatedNode.h" #import "RCTMultiplicationAnimatedNode.h" #import "RCTPropsAnimatedNode.h" @@ -71,6 +72,7 @@ RCT_EXPORT_METHOD(createAnimatedNode:(nonnull NSNumber *)tag @"interpolation" : [RCTInterpolationAnimatedNode class], @"addition" : [RCTAdditionAnimatedNode class], @"diffclamp": [RCTDiffClampAnimatedNode class], + @"division" : [RCTDivisionAnimatedNode class], @"multiplication" : [RCTMultiplicationAnimatedNode class], @"modulus" : [RCTModuloAnimatedNode class], @"transform" : [RCTTransformAnimatedNode class]}; diff --git a/ReactAndroid/src/main/java/com/facebook/react/animated/DivisionAnimatedNode.java b/ReactAndroid/src/main/java/com/facebook/react/animated/DivisionAnimatedNode.java new file mode 100644 index 000000000..ccb38890d --- /dev/null +++ b/ReactAndroid/src/main/java/com/facebook/react/animated/DivisionAnimatedNode.java @@ -0,0 +1,57 @@ +/** + * Copyright (c) 2015-present, Facebook, Inc. + * All rights reserved. + * + * This source code is licensed under the BSD-style license found in the + * LICENSE file in the root directory of this source tree. An additional grant + * of patent rights can be found in the PATENTS file in the same directory. + */ + +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 an in-order + * division of their values. + */ +/*package*/ class DivisionAnimatedNode extends ValueAnimatedNode { + + private final NativeAnimatedNodesManager mNativeAnimatedNodesManager; + private final int[] mInputNodes; + + public DivisionAnimatedNode( + 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).mValue; + if (i == 0) { + mValue = value; + continue; + } + if (value == 0) { + throw new JSApplicationCausedNativeException("Detected a division by zero in " + + "Animated.divide node"); + } + mValue /= value; + } else { + throw new JSApplicationCausedNativeException("Illegal node ID set as an input for " + + "Animated.divide 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 e11db2835..d9d7f449b 100644 --- a/ReactAndroid/src/main/java/com/facebook/react/animated/NativeAnimatedNodesManager.java +++ b/ReactAndroid/src/main/java/com/facebook/react/animated/NativeAnimatedNodesManager.java @@ -89,6 +89,8 @@ import javax.annotation.Nullable; node = new InterpolationAnimatedNode(config); } else if ("addition".equals(type)) { node = new AdditionAnimatedNode(config, this); + } else if ("division".equals(type)) { + node = new DivisionAnimatedNode(config, this); } else if ("multiplication".equals(type)) { node = new MultiplicationAnimatedNode(config, this); } else if ("diffclamp".equals(type)) {