From 5e19eeaafece3763bf48bec408cac08cbdcf70d0 Mon Sep 17 00:00:00 2001 From: Janic Duplessis Date: Wed, 3 Aug 2016 16:03:25 -0400 Subject: [PATCH] [NativeAnimated][iOS] Add support for value listener --- .../UIExplorer/js/NativeAnimationsExample.js | 1 - .../Animated/src/AnimatedImplementation.js | 20 +++++------ .../Animated/src/NativeAnimatedHelper.js | 26 +++++++-------- .../Animated/src/__tests__/Animated-test.js | 3 +- .../Nodes/RCTValueAnimatedNode.h | 9 +++++ .../Nodes/RCTValueAnimatedNode.m | 9 +++++ .../NativeAnimation/RCTNativeAnimatedModule.h | 4 ++- .../NativeAnimation/RCTNativeAnimatedModule.m | 33 +++++++++++++++++-- 8 files changed, 75 insertions(+), 30 deletions(-) diff --git a/Examples/UIExplorer/js/NativeAnimationsExample.js b/Examples/UIExplorer/js/NativeAnimationsExample.js index bd7add201d258d..0c26a46fd3b580 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 3d78bfed4484c2..d7403ef638bae7 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'); @@ -724,23 +723,24 @@ class AnimatedValue extends AnimatedWithChildren { } _startListeningToNativeValueUpdates() { - if (this.__nativeAnimatedValueListener || - !NativeAnimatedHelper.supportsNativeListener()) { + if (this.__nativeAnimatedValueListener) { return; } NativeAnimatedAPI.startListeningToAnimatedNodeValue(this.__getNativeTag()); - this.__nativeAnimatedValueListener = DeviceEventEmitter.addListener('onAnimatedValueUpdate', (data) => { - if (data.tag !== this.__getNativeTag()) { - return; + this.__nativeAnimatedValueListener = NativeAnimatedHelper.nativeEventEmitter.addListener( + 'onAnimatedValueUpdate', + (data) => { + if (data.tag !== this.__getNativeTag()) { + return; + } + this._updateValue(data.value, false /* flush */); } - this._updateValue(data.value, false /* flush */); - }); + ); } _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..769b2228d69091 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; +const nativeEventEmitter = new NativeEventEmitter(NativeAnimatedModule); + /** - * 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,5 @@ module.exports = { generateNewNodeTag, generateNewAnimationId, assertNativeAnimatedModule, - supportsNativeListener, + nativeEventEmitter, }; diff --git a/Libraries/Animated/src/__tests__/Animated-test.js b/Libraries/Animated/src/__tests__/Animated-test.js index 02cb505755dab4..d2ea92f3fa9d4c 100644 --- a/Libraries/Animated/src/__tests__/Animated-test.js +++ b/Libraries/Animated/src/__tests__/Animated-test.js @@ -13,7 +13,8 @@ jest .setMock('Text', {}) .setMock('View', {}) .setMock('Image', {}) - .setMock('React', {Component: class {}}); + .setMock('React', {Component: class {}}) + .setMock('NativeEventEmitter', class {}); var Animated = require('Animated'); 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 74e157fc17fb6b..a8694460c3bb25 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,6 +46,11 @@ - (void)setBridge:(RCTBridge *)bridge _propAnimationNodes = [NSMutableSet new]; } +- (NSArray *)supportedEvents +{ + return @[@"onAnimatedValueUpdate"]; +} + RCT_EXPORT_METHOD(createAnimatedNode:(nonnull NSNumber *)tag config:(NSDictionary *)config) { @@ -193,6 +197,29 @@ - (void)setBridge:(RCTBridge *)bridge } } +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