diff --git a/Examples/UIExplorer/js/NativeAnimationsProblemExample.js b/Examples/UIExplorer/js/NativeAnimationsProblemExample.js new file mode 100644 index 00000000000000..5cdf0977cdc17d --- /dev/null +++ b/Examples/UIExplorer/js/NativeAnimationsProblemExample.js @@ -0,0 +1,120 @@ +/** + * Copyright (c) 2013-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. + * + * The examples provided by Facebook are for non-commercial testing and + * evaluation purposes only. + * + * Facebook reserves all rights not expressly granted. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS + * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NON INFRINGEMENT. IN NO EVENT SHALL + * FACEBOOK BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN + * AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + * + * @flow + */ +'use strict'; + +var React = require('react'); +var ReactNative = require('react-native'); +var { + View, + Text, + Animated, + StyleSheet, + TouchableWithoutFeedback, +} = ReactNative; + +class Tester extends React.Component { + + static title = 'Native Animated Problem'; + static description = 'Test out Native Animations'; + + state = { + opacity: new Animated.Value(0.25, {useNativeDriver: true}), + showBlock2: false, + }; + + visible = false; + + runAnimation = () => { + this.visible = !this.visible; + Animated.timing(this.state.opacity, { + toValue: this.visible ? 1 : 0.25, + duration: 500, + useNativeDriver: true, + }).start(); + }; + + render() { + return ( + + + + + {'Run animation'} + + + + + { + this.setState({ + showBlock2: !this.state.showBlock2, + }); + }}> + + {'Toggle block 2'} + + + + + {this._renderBlock(1)} + + + {this.state.showBlock2 && this._renderBlock(2)} + + + ); + } + + _renderBlock(key: number) { + return ( + + ); + } +} + +const styles = StyleSheet.create({ + container: { + padding: 10, + }, + button: { + padding: 10, + backgroundColor: 'green', + }, + section: { + marginBottom: 20, + }, + block: { + width: 50, + height: 50, + backgroundColor: 'blue', + }, +}); + +module.exports = Tester; diff --git a/Examples/UIExplorer/js/UIExplorerList.android.js b/Examples/UIExplorer/js/UIExplorerList.android.js index de1f23b35d19f9..818e26deeca18e 100644 --- a/Examples/UIExplorer/js/UIExplorerList.android.js +++ b/Examples/UIExplorer/js/UIExplorerList.android.js @@ -179,6 +179,10 @@ const APIExamples: Array = [ key: 'NativeAnimationsExample', module: require('./NativeAnimationsExample'), }, + { + key: 'NativeAnimationsProblemExample', + module: require('./NativeAnimationsProblemExample'), + }, { key: 'NavigationExperimentalExample', module: require('./NavigationExperimental/NavigationExperimentalExample'), diff --git a/Examples/UIExplorer/js/UIExplorerList.ios.js b/Examples/UIExplorer/js/UIExplorerList.ios.js index 71fc56312a220e..951b269e3bd4ac 100644 --- a/Examples/UIExplorer/js/UIExplorerList.ios.js +++ b/Examples/UIExplorer/js/UIExplorerList.ios.js @@ -231,6 +231,10 @@ const APIExamples: Array = [ key: 'NativeAnimationsExample', module: require('./NativeAnimationsExample'), }, + { + key: 'NativeAnimationsProblemExample', + module: require('./NativeAnimationsProblemExample'), + }, { key: 'NavigationExperimentalExample', module: require('./NavigationExperimental/NavigationExperimentalExample'), diff --git a/Libraries/Animated/src/AnimatedImplementation.js b/Libraries/Animated/src/AnimatedImplementation.js index 7fb99069c559b8..424e137ecffd3e 100644 --- a/Libraries/Animated/src/AnimatedImplementation.js +++ b/Libraries/Animated/src/AnimatedImplementation.js @@ -659,6 +659,10 @@ class SpringAnimation extends Animation { } } +type AnimatedValueConfig = { + useNativeDriver?: bool; +}; + type ValueListenerCallback = (state: {value: number}) => void; var _uniqueId = 1; @@ -678,12 +682,15 @@ class AnimatedValue extends AnimatedWithChildren { _listeners: {[key: string]: ValueListenerCallback}; __nativeAnimatedValueListener: ?any; - constructor(value: number) { + constructor(value: number, config?: AnimatedValueConfig) { super(); this._startingValue = this._value = value; this._offset = 0; this._animation = null; this._listeners = {}; + if (config && config.useNativeDriver) { + this.__makeNative(); + } } __detach() { @@ -929,7 +936,7 @@ class AnimatedValueXY extends AnimatedWithChildren { y: AnimatedValue; _listeners: {[key: string]: {x: string, y: string}}; - constructor(valueIn?: ?{x: number | AnimatedValue, y: number | AnimatedValue}) { + constructor(valueIn?: ?{x: number | AnimatedValue; y: number | AnimatedValue}, config?: AnimatedValueConfig) { super(); var value: any = valueIn || {x: 0, y: 0}; // @flowfixme: shouldn't need `: any` if (typeof value.x === 'number' && typeof value.y === 'number') { @@ -946,6 +953,9 @@ class AnimatedValueXY extends AnimatedWithChildren { this.y = value.y; } this._listeners = {}; + if (config && config.useNativeDriver) { + this.__makeNative(); + } } setValue(value: {x: number, y: number}) { diff --git a/Libraries/NativeAnimation/Nodes/RCTAnimatedNode.h b/Libraries/NativeAnimation/Nodes/RCTAnimatedNode.h index 8e06e90629fdd6..07e5a85555306e 100644 --- a/Libraries/NativeAnimation/Nodes/RCTAnimatedNode.h +++ b/Libraries/NativeAnimation/Nodes/RCTAnimatedNode.h @@ -21,7 +21,6 @@ @property (nonatomic, copy, readonly) NSDictionary *parentNodes; @property (nonatomic, readonly) BOOL needsUpdate; -@property (nonatomic, readonly) BOOL hasUpdated; /** * Marks a node and its children as needing update. diff --git a/Libraries/NativeAnimation/Nodes/RCTAnimatedNode.m b/Libraries/NativeAnimation/Nodes/RCTAnimatedNode.m index 9f5075b6222140..0a1c35fa77351c 100644 --- a/Libraries/NativeAnimation/Nodes/RCTAnimatedNode.m +++ b/Libraries/NativeAnimation/Nodes/RCTAnimatedNode.m @@ -69,6 +69,7 @@ - (void)onAttachedToNode:(RCTAnimatedNode *)parent if (parent) { _parentNodes[parent.nodeTag] = parent; } + [self setNeedsUpdate]; } - (void)onDetachedFromNode:(RCTAnimatedNode *)parent @@ -79,6 +80,7 @@ - (void)onDetachedFromNode:(RCTAnimatedNode *)parent if (parent) { [_parentNodes removeObjectForKey:parent.nodeTag]; } + [self setNeedsUpdate]; } - (void)detachNode @@ -93,10 +95,6 @@ - (void)detachNode - (void)setNeedsUpdate { - if (_needsUpdate) { - // Has already been marked. Stop branch. - return; - } _needsUpdate = YES; for (RCTAnimatedNode *child in _childNodes.allValues) { [child setNeedsUpdate]; @@ -105,9 +103,7 @@ - (void)setNeedsUpdate - (void)cleanupAnimationUpdate { - if (_hasUpdated) { - _needsUpdate = NO; - _hasUpdated = NO; + if (!_needsUpdate) { for (RCTAnimatedNode *child in _childNodes.allValues) { [child cleanupAnimationUpdate]; } @@ -116,7 +112,7 @@ - (void)cleanupAnimationUpdate - (void)updateNodeIfNecessary { - if (_needsUpdate && !_hasUpdated) { + if (_needsUpdate) { for (RCTAnimatedNode *parent in _parentNodes.allValues) { [parent updateNodeIfNecessary]; } @@ -126,7 +122,7 @@ - (void)updateNodeIfNecessary - (void)performUpdate { - _hasUpdated = YES; + _needsUpdate = NO; // To be overidden by subclasses // This method is called on a node only if it has been marked for update // during the current update loop diff --git a/Libraries/NativeAnimation/Nodes/RCTStyleAnimatedNode.m b/Libraries/NativeAnimation/Nodes/RCTStyleAnimatedNode.m index 2c349bd22e81d7..feb7d1ad7f5acf 100644 --- a/Libraries/NativeAnimation/Nodes/RCTStyleAnimatedNode.m +++ b/Libraries/NativeAnimation/Nodes/RCTStyleAnimatedNode.m @@ -38,7 +38,7 @@ - (void)performUpdate NSDictionary *style = self.config[@"style"]; [style enumerateKeysAndObjectsUsingBlock:^(NSString *property, NSNumber *nodeTag, __unused BOOL *stop) { RCTAnimatedNode *node = self.parentNodes[nodeTag]; - if (node && node.hasUpdated) { + if (node) { if ([node isKindOfClass:[RCTValueAnimatedNode class]]) { RCTValueAnimatedNode *parentNode = (RCTValueAnimatedNode *)node; [self->_updatedPropsDictionary setObject:@(parentNode.value) forKey:property]; diff --git a/Libraries/NativeAnimation/Nodes/RCTTransformAnimatedNode.m b/Libraries/NativeAnimation/Nodes/RCTTransformAnimatedNode.m index d44fe4d7c356fe..778413e0725fc2 100644 --- a/Libraries/NativeAnimation/Nodes/RCTTransformAnimatedNode.m +++ b/Libraries/NativeAnimation/Nodes/RCTTransformAnimatedNode.m @@ -46,7 +46,7 @@ - (void)performUpdate NSNumber *nodeTag = transformConfig[@"nodeTag"]; RCTAnimatedNode *node = self.parentNodes[nodeTag]; - if (node.hasUpdated && [node isKindOfClass:[RCTValueAnimatedNode class]]) { + if ([node isKindOfClass:[RCTValueAnimatedNode class]]) { RCTValueAnimatedNode *parentNode = (RCTValueAnimatedNode *)node; NSString *property = transformConfig[@"property"]; diff --git a/Libraries/NativeAnimation/Nodes/RCTValueAnimatedNode.m b/Libraries/NativeAnimation/Nodes/RCTValueAnimatedNode.m index 29d2a6c14a7a7c..b79a0857f76880 100644 --- a/Libraries/NativeAnimation/Nodes/RCTValueAnimatedNode.m +++ b/Libraries/NativeAnimation/Nodes/RCTValueAnimatedNode.m @@ -27,6 +27,7 @@ - (instancetype)initWithTag:(NSNumber *)tag _value = [self.config[@"value"] floatValue]; } return self; + } - (void)flattenOffset diff --git a/Libraries/NativeAnimation/RCTNativeAnimatedModule.m b/Libraries/NativeAnimation/RCTNativeAnimatedModule.m index 5c76c10e4623bd..8b16bfe232ecfd 100644 --- a/Libraries/NativeAnimation/RCTNativeAnimatedModule.m +++ b/Libraries/NativeAnimation/RCTNativeAnimatedModule.m @@ -51,7 +51,6 @@ - (void)setBridge:(RCTBridge *)bridge _propAnimationNodes = [NSMutableSet new]; } - - (dispatch_queue_t)methodQueue { return dispatch_get_main_queue(); @@ -218,6 +217,8 @@ - (dispatch_queue_t)methodQueue if (viewTag && [node isKindOfClass:[RCTPropsAnimatedNode class]]) { [(RCTPropsAnimatedNode *)node connectToView:viewTag animatedModule:self]; } + [node setNeedsUpdate]; + [node updateNodeIfNecessary]; } RCT_EXPORT_METHOD(disconnectAnimatedNodeFromView:(nonnull NSNumber *)nodeTag