From 0e204e1141663182f6a8305fec1816f2ecda2d30 Mon Sep 17 00:00:00 2001 From: Janic Duplessis Date: Thu, 11 Aug 2016 18:10:16 -0700 Subject: [PATCH] Add support for value listener Summary: Adds support for `Animated.Value#addListener` for native driven animated values. Same as #8844 but for iOS. This depends on some JS code in #8844 so only review the 2nd commit and let's wait for #8844 to land first. **Test plan** Tested using the UIExplorer example. Closes https://github.com/facebook/react-native/pull/9194 Differential Revision: D3681749 fbshipit-source-id: 521a61e2221c1ad1f6f40c75dd2dc957361d0271 --- .../UIExplorer/js/NativeAnimationsExample.js | 1 - .../Animated/src/AnimatedImplementation.js | 9 ++--- .../Animated/src/NativeAnimatedHelper.js | 31 +++++++++-------- .../Nodes/RCTValueAnimatedNode.h | 9 +++++ .../Nodes/RCTValueAnimatedNode.m | 9 +++++ .../NativeAnimation/RCTNativeAnimatedModule.h | 4 ++- .../NativeAnimation/RCTNativeAnimatedModule.m | 34 +++++++++++++++++-- 7 files changed, 72 insertions(+), 25 deletions(-) diff --git a/Examples/UIExplorer/js/NativeAnimationsExample.js b/Examples/UIExplorer/js/NativeAnimationsExample.js index b4c151c53df63e..12f317c72143cc 100644 --- a/Examples/UIExplorer/js/NativeAnimationsExample.js +++ b/Examples/UIExplorer/js/NativeAnimationsExample.js @@ -347,7 +347,6 @@ exports.examples = [ }, { title: 'Animated value listener', - platform: 'android', render: function() { return ( diff --git a/Libraries/Animated/src/AnimatedImplementation.js b/Libraries/Animated/src/AnimatedImplementation.js index 8ad81d2de058f8..f7d8b8e163f85d 100644 --- a/Libraries/Animated/src/AnimatedImplementation.js +++ b/Libraries/Animated/src/AnimatedImplementation.js @@ -11,7 +11,6 @@ */ 'use strict'; -var DeviceEventEmitter = require('RCTDeviceEventEmitter'); var InteractionManager = require('InteractionManager'); var Interpolation = require('Interpolation'); var React = require('React'); @@ -750,13 +749,12 @@ class AnimatedValue extends AnimatedWithChildren { } _startListeningToNativeValueUpdates() { - if (this.__nativeAnimatedValueListener || - !NativeAnimatedHelper.supportsNativeListener()) { + if (this.__nativeAnimatedValueListener) { return; } NativeAnimatedAPI.startListeningToAnimatedNodeValue(this.__getNativeTag()); - this.__nativeAnimatedValueListener = DeviceEventEmitter.addListener( + this.__nativeAnimatedValueListener = NativeAnimatedHelper.nativeEventEmitter.addListener( 'onAnimatedValueUpdate', (data) => { if (data.tag !== this.__getNativeTag()) { @@ -768,8 +766,7 @@ class AnimatedValue extends AnimatedWithChildren { } _stopListeningForNativeValueUpdates() { - if (!this.__nativeAnimatedValueListener || - !NativeAnimatedHelper.supportsNativeListener()) { + if (!this.__nativeAnimatedValueListener) { return; } diff --git a/Libraries/Animated/src/NativeAnimatedHelper.js b/Libraries/Animated/src/NativeAnimatedHelper.js index 9138a4308e351b..668a1e079ab9f7 100644 --- a/Libraries/Animated/src/NativeAnimatedHelper.js +++ b/Libraries/Animated/src/NativeAnimatedHelper.js @@ -11,21 +11,24 @@ */ 'use strict'; -var NativeAnimatedModule = require('NativeModules').NativeAnimatedModule; +const NativeAnimatedModule = require('NativeModules').NativeAnimatedModule; +const NativeEventEmitter = require('NativeEventEmitter'); -var invariant = require('fbjs/lib/invariant'); +const invariant = require('fbjs/lib/invariant'); -var __nativeAnimatedNodeTagCount = 1; /* used for animated nodes */ -var __nativeAnimationIdCount = 1; /* used for started animations */ +let __nativeAnimatedNodeTagCount = 1; /* used for animated nodes */ +let __nativeAnimationIdCount = 1; /* used for started animations */ type EndResult = {finished: bool}; type EndCallback = (result: EndResult) => void; +let nativeEventEmitter; + /** - * Simple wrappers around NativeANimatedModule to provide flow and autocmplete support for + * Simple wrappers around NativeAnimatedModule to provide flow and autocmplete support for * the native module methods */ -var API = { +const API = { createAnimatedNode: function(tag: number, config: Object): void { assertNativeAnimatedModule(); NativeAnimatedModule.createAnimatedNode(tag, config); @@ -79,7 +82,7 @@ var API = { * to be updated through the shadow view hierarchy (all non-layout properties). This list is limited * to the properties that will perform best when animated off the JS thread. */ -var PROPS_WHITELIST = { +const PROPS_WHITELIST = { style: { opacity: true, transform: true, @@ -91,7 +94,7 @@ var PROPS_WHITELIST = { }, }; -var TRANSFORM_WHITELIST = { +const TRANSFORM_WHITELIST = { translateX: true, translateY: true, scale: true, @@ -152,11 +155,6 @@ function assertNativeAnimatedModule(): void { invariant(NativeAnimatedModule, 'Native animated module is not available'); } -// TODO: remove this when iOS supports native listeners. -function supportsNativeListener(): bool { - return !!NativeAnimatedModule.startListeningToAnimatedNodeValue; -} - module.exports = { API, validateProps, @@ -166,5 +164,10 @@ module.exports = { generateNewNodeTag, generateNewAnimationId, assertNativeAnimatedModule, - supportsNativeListener, + get nativeEventEmitter() { + if (!nativeEventEmitter) { + nativeEventEmitter = new NativeEventEmitter(NativeAnimatedModule); + } + return nativeEventEmitter; + }, }; diff --git a/Libraries/NativeAnimation/Nodes/RCTValueAnimatedNode.h b/Libraries/NativeAnimation/Nodes/RCTValueAnimatedNode.h index af183243534cdd..5029129ec27db8 100644 --- a/Libraries/NativeAnimation/Nodes/RCTValueAnimatedNode.h +++ b/Libraries/NativeAnimation/Nodes/RCTValueAnimatedNode.h @@ -10,8 +10,17 @@ #import "RCTAnimatedNode.h" #import +@class RCTValueAnimatedNode; + +@protocol RCTValueAnimatedNodeObserver + +- (void)animatedNode:(RCTValueAnimatedNode *)node didUpdateValue:(CGFloat)value; + +@end + @interface RCTValueAnimatedNode : RCTAnimatedNode @property (nonatomic, assign) CGFloat value; +@property (nonatomic, weak) id valueObserver; @end diff --git a/Libraries/NativeAnimation/Nodes/RCTValueAnimatedNode.m b/Libraries/NativeAnimation/Nodes/RCTValueAnimatedNode.m index cffa66681d7b17..40ca5b9eafdabd 100644 --- a/Libraries/NativeAnimation/Nodes/RCTValueAnimatedNode.m +++ b/Libraries/NativeAnimation/Nodes/RCTValueAnimatedNode.m @@ -11,4 +11,13 @@ @implementation RCTValueAnimatedNode +- (void)setValue:(CGFloat)value +{ + _value = value; + + if (_valueObserver) { + [_valueObserver animatedNode:self didUpdateValue:_value]; + } +} + @end diff --git a/Libraries/NativeAnimation/RCTNativeAnimatedModule.h b/Libraries/NativeAnimation/RCTNativeAnimatedModule.h index d099ac53bfacf9..da7831769194f6 100644 --- a/Libraries/NativeAnimation/RCTNativeAnimatedModule.h +++ b/Libraries/NativeAnimation/RCTNativeAnimatedModule.h @@ -7,7 +7,9 @@ * of patent rights can be found in the PATENTS file in the same directory. */ #import "RCTBridgeModule.h" +#import "RCTValueAnimatedNode.h" +#import "RCTEventEmitter.h" -@interface RCTNativeAnimatedModule : NSObject +@interface RCTNativeAnimatedModule : RCTEventEmitter @end diff --git a/Libraries/NativeAnimation/RCTNativeAnimatedModule.m b/Libraries/NativeAnimation/RCTNativeAnimatedModule.m index 5ba4f322fb3dfa..80c263f7e42a28 100644 --- a/Libraries/NativeAnimation/RCTNativeAnimatedModule.m +++ b/Libraries/NativeAnimation/RCTNativeAnimatedModule.m @@ -32,13 +32,12 @@ @implementation RCTNativeAnimatedModule CADisplayLink *_displayLink; } -@synthesize bridge = _bridge; - RCT_EXPORT_MODULE() - (void)setBridge:(RCTBridge *)bridge { - _bridge = bridge; + [super setBridge:bridge]; + _animationNodes = [NSMutableDictionary new]; _animationDrivers = [NSMutableDictionary new]; _activeAnimations = [NSMutableSet new]; @@ -47,11 +46,17 @@ - (void)setBridge:(RCTBridge *)bridge _propAnimationNodes = [NSMutableSet new]; } + - (dispatch_queue_t)methodQueue { return dispatch_get_main_queue(); } +- (NSArray *)supportedEvents +{ + return @[@"onAnimatedValueUpdate"]; +} + RCT_EXPORT_METHOD(createAnimatedNode:(nonnull NSNumber *)tag config:(NSDictionary *)config) { @@ -198,6 +203,29 @@ - (dispatch_queue_t)methodQueue } } +RCT_EXPORT_METHOD(startListeningToAnimatedNodeValue:(nonnull NSNumber *)tag) +{ + RCTAnimatedNode *node = _animationNodes[tag]; + if (node && [node isKindOfClass:[RCTValueAnimatedNode class]]) { + ((RCTValueAnimatedNode *)node).valueObserver = self; + } +} + +RCT_EXPORT_METHOD(stopListeningToAnimatedNodeValue:(nonnull NSNumber *)tag) +{ + RCTAnimatedNode *node = _animationNodes[tag]; + if (node && [node isKindOfClass:[RCTValueAnimatedNode class]]) { + ((RCTValueAnimatedNode *)node).valueObserver = nil; + } +} + +- (void)animatedNode:(RCTValueAnimatedNode *)node didUpdateValue:(CGFloat)value +{ + [self sendEventWithName:@"onAnimatedValueUpdate" + body:@{@"tag": node.nodeTag, @"value": @(value)}]; +} + + #pragma mark -- Animation Loop - (void)startAnimation