diff --git a/Examples/UIExplorer/js/NativeAnimationsExample.js b/Examples/UIExplorer/js/NativeAnimationsExample.js
index 62f7100f8..208f27a64 100644
--- a/Examples/UIExplorer/js/NativeAnimationsExample.js
+++ b/Examples/UIExplorer/js/NativeAnimationsExample.js
@@ -191,6 +191,7 @@ class EventExample extends React.Component {
+ );
+ },
+ },
{
title: 'Internal Settings',
render: function() {
@@ -470,13 +479,4 @@ exports.examples = [
);
},
},
- {
- title: 'Animated events',
- platform: 'android',
- render: function() {
- return (
-
- );
- },
- },
];
diff --git a/Libraries/NativeAnimation/Drivers/RCTEventAnimation.h b/Libraries/NativeAnimation/Drivers/RCTEventAnimation.h
new file mode 100644
index 000000000..4b8eefd1f
--- /dev/null
+++ b/Libraries/NativeAnimation/Drivers/RCTEventAnimation.h
@@ -0,0 +1,24 @@
+/**
+ * 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
+
+#import "RCTValueAnimatedNode.h"
+#import "RCTEventDispatcher.h"
+
+@interface RCTEventAnimation : NSObject
+
+@property (nonatomic, readonly, weak) RCTValueAnimatedNode *valueNode;
+
+- (instancetype)initWithEventPath:(NSArray *)eventPath
+ valueNode:(RCTValueAnimatedNode *)valueNode;
+
+- (void)updateWithEvent:(id)event;
+
+@end
diff --git a/Libraries/NativeAnimation/Drivers/RCTEventAnimation.m b/Libraries/NativeAnimation/Drivers/RCTEventAnimation.m
new file mode 100644
index 000000000..dd7dfc9cf
--- /dev/null
+++ b/Libraries/NativeAnimation/Drivers/RCTEventAnimation.m
@@ -0,0 +1,40 @@
+/**
+ * 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 "RCTEventAnimation.h"
+
+@implementation RCTEventAnimation
+{
+ NSArray *_eventPath;
+}
+
+- (instancetype)initWithEventPath:(NSArray *)eventPath
+ valueNode:(RCTValueAnimatedNode *)valueNode
+{
+ if ((self = [super init])) {
+ _eventPath = eventPath;
+ _valueNode = valueNode;
+ }
+ return self;
+}
+
+- (void)updateWithEvent:(id)event
+{
+ NSArray *args = event.arguments;
+ // Supported events args are in the following order: viewTag, eventName, eventData.
+ id currentValue = args[2];
+ for (NSString *key in _eventPath) {
+ currentValue = [currentValue valueForKey:key];
+ }
+
+ _valueNode.value = ((NSNumber *)currentValue).doubleValue;
+ [_valueNode setNeedsUpdate];
+}
+
+@end
diff --git a/Libraries/NativeAnimation/RCTAnimation.xcodeproj/project.pbxproj b/Libraries/NativeAnimation/RCTAnimation.xcodeproj/project.pbxproj
index 657150b28..aec9ef048 100644
--- a/Libraries/NativeAnimation/RCTAnimation.xcodeproj/project.pbxproj
+++ b/Libraries/NativeAnimation/RCTAnimation.xcodeproj/project.pbxproj
@@ -19,6 +19,8 @@
13E501EF1D07A6C9005F35D8 /* RCTTransformAnimatedNode.m in Sources */ = {isa = PBXBuildFile; fileRef = 13E501E51D07A6C9005F35D8 /* RCTTransformAnimatedNode.m */; };
13E501F01D07A6C9005F35D8 /* RCTValueAnimatedNode.m in Sources */ = {isa = PBXBuildFile; fileRef = 13E501E71D07A6C9005F35D8 /* RCTValueAnimatedNode.m */; };
193F64F41D776EC6004D1CAA /* RCTDiffClampAnimatedNode.m in Sources */ = {isa = PBXBuildFile; fileRef = 193F64F31D776EC6004D1CAA /* RCTDiffClampAnimatedNode.m */; };
+ 19F00F221DC8847500113FEE /* RCTEventAnimation.m in Sources */ = {isa = PBXBuildFile; fileRef = 19F00F211DC8847500113FEE /* RCTEventAnimation.m */; };
+ 19F00F231DC8848E00113FEE /* RCTEventAnimation.m in Sources */ = {isa = PBXBuildFile; fileRef = 19F00F211DC8847500113FEE /* RCTEventAnimation.m */; };
2D3B5EF21D9B0B3100451313 /* RCTAnimationUtils.m in Sources */ = {isa = PBXBuildFile; fileRef = 13E501B81D07A644005F35D8 /* RCTAnimationUtils.m */; };
2D3B5EF31D9B0B3400451313 /* RCTViewPropertyMapper.m in Sources */ = {isa = PBXBuildFile; fileRef = 13E501C81D07A644005F35D8 /* RCTViewPropertyMapper.m */; };
2D3B5EF41D9B0B3700451313 /* RCTNativeAnimatedModule.m in Sources */ = {isa = PBXBuildFile; fileRef = 13E501BE1D07A644005F35D8 /* RCTNativeAnimatedModule.m */; };
@@ -88,6 +90,8 @@
13E501E71D07A6C9005F35D8 /* RCTValueAnimatedNode.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RCTValueAnimatedNode.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 = ""; };
+ 19F00F201DC8847500113FEE /* RCTEventAnimation.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RCTEventAnimation.h; sourceTree = ""; };
+ 19F00F211DC8847500113FEE /* RCTEventAnimation.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RCTEventAnimation.m; sourceTree = ""; };
2D2A28201D9B03D100D4039D /* libRCTAnimation-tvOS.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = "libRCTAnimation-tvOS.a"; sourceTree = BUILT_PRODUCTS_DIR; };
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 = ""; };
@@ -175,6 +179,8 @@
isa = PBXGroup;
children = (
94C1294A1D4069170025F25C /* RCTAnimationDriver.h */,
+ 19F00F201DC8847500113FEE /* RCTEventAnimation.h */,
+ 19F00F211DC8847500113FEE /* RCTEventAnimation.m */,
94C1294C1D4069170025F25C /* RCTFrameAnimation.h */,
94C1294D1D4069170025F25C /* RCTFrameAnimation.m */,
94C1294E1D4069170025F25C /* RCTSpringAnimation.h */,
@@ -266,6 +272,7 @@
2D3B5EF21D9B0B3100451313 /* RCTAnimationUtils.m in Sources */,
2D3B5EF51D9B0B4800451313 /* RCTDivisionAnimatedNode.m in Sources */,
2D3B5EF71D9B0B4800451313 /* RCTAdditionAnimatedNode.m in Sources */,
+ 19F00F231DC8848E00113FEE /* RCTEventAnimation.m in Sources */,
2D3B5EF41D9B0B3700451313 /* RCTNativeAnimatedModule.m in Sources */,
2D3B5EF61D9B0B4800451313 /* RCTDiffClampAnimatedNode.m in Sources */,
2D3B5EF81D9B0B4800451313 /* RCTAnimatedNode.m in Sources */,
@@ -289,6 +296,7 @@
13E501F01D07A6C9005F35D8 /* RCTValueAnimatedNode.m in Sources */,
94DAE3F91D7334A70059942F /* RCTModuloAnimatedNode.m in Sources */,
193F64F41D776EC6004D1CAA /* RCTDiffClampAnimatedNode.m in Sources */,
+ 19F00F221DC8847500113FEE /* RCTEventAnimation.m in Sources */,
13E501EE1D07A6C9005F35D8 /* RCTStyleAnimatedNode.m in Sources */,
13E501CC1D07A644005F35D8 /* RCTAnimationUtils.m in Sources */,
13E501CF1D07A644005F35D8 /* RCTNativeAnimatedModule.m in Sources */,
diff --git a/Libraries/NativeAnimation/RCTNativeAnimatedModule.h b/Libraries/NativeAnimation/RCTNativeAnimatedModule.h
index da7831769..55cdf705f 100644
--- a/Libraries/NativeAnimation/RCTNativeAnimatedModule.h
+++ b/Libraries/NativeAnimation/RCTNativeAnimatedModule.h
@@ -9,7 +9,8 @@
#import "RCTBridgeModule.h"
#import "RCTValueAnimatedNode.h"
#import "RCTEventEmitter.h"
+#import "RCTEventDispatcher.h"
-@interface RCTNativeAnimatedModule : RCTEventEmitter
+@interface RCTNativeAnimatedModule : RCTEventEmitter
@end
diff --git a/Libraries/NativeAnimation/RCTNativeAnimatedModule.m b/Libraries/NativeAnimation/RCTNativeAnimatedModule.m
index f08824395..8426d8bb8 100644
--- a/Libraries/NativeAnimation/RCTNativeAnimatedModule.m
+++ b/Libraries/NativeAnimation/RCTNativeAnimatedModule.m
@@ -15,6 +15,7 @@
#import "RCTAnimationUtils.h"
#import "RCTBridge.h"
#import "RCTConvert.h"
+#import "RCTEventAnimation.h"
#import "RCTInterpolationAnimatedNode.h"
#import "RCTLog.h"
#import "RCTDiffClampAnimatedNode.h"
@@ -30,6 +31,7 @@
{
NSMutableDictionary *_animationNodes;
NSMutableDictionary> *_animationDrivers;
+ NSMutableDictionary *_eventAnimationDrivers;
NSMutableSet> *_activeAnimations;
NSMutableSet> *_finishedAnimations;
NSMutableSet *_updatedValueNodes;
@@ -45,10 +47,18 @@ RCT_EXPORT_MODULE()
_animationNodes = [NSMutableDictionary new];
_animationDrivers = [NSMutableDictionary new];
+ _eventAnimationDrivers = [NSMutableDictionary new];
_activeAnimations = [NSMutableSet new];
_finishedAnimations = [NSMutableSet new];
_updatedValueNodes = [NSMutableSet new];
_propAnimationNodes = [NSMutableSet new];
+
+ [bridge.eventDispatcher addDispatchObserver:self];
+}
+
+- (void)dealloc
+{
+ [self.bridge.eventDispatcher removeDispatchObserver:self];
}
@@ -157,7 +167,7 @@ RCT_EXPORT_METHOD(startAnimatingNode:(nonnull NSNumber *)animationId
[_activeAnimations addObject:animationDriver];
_animationDrivers[animationId] = animationDriver;
[animationDriver startAnimation];
- [self startAnimation];
+ [self startAnimationLoopIfNeeded];
}
RCT_EXPORT_METHOD(stopAnimation:(nonnull NSNumber *)animationId)
@@ -263,12 +273,64 @@ RCT_EXPORT_METHOD(stopListeningToAnimatedNodeValue:(nonnull NSNumber *)tag)
}
}
+RCT_EXPORT_METHOD(addAnimatedEventToView:(nonnull NSNumber *)viewTag
+ eventName:(nonnull NSString *)eventName
+ eventMapping:(NSDictionary *)eventMapping)
+{
+ NSNumber *nodeTag = [RCTConvert NSNumber:eventMapping[@"animatedValueTag"]];
+ RCTAnimatedNode *node = _animationNodes[nodeTag];
+
+ if (!node) {
+ RCTLogError(@"Animated node with tag %@ does not exists", nodeTag);
+ return;
+ }
+
+ if (![node isKindOfClass:[RCTValueAnimatedNode class]]) {
+ RCTLogError(@"Animated node connected to event should be of type RCTValueAnimatedNode");
+ return;
+ }
+
+ NSArray *eventPath = [RCTConvert NSStringArray:eventMapping[@"nativeEventPath"]];
+
+ RCTEventAnimation *driver =
+ [[RCTEventAnimation alloc] initWithEventPath:eventPath valueNode:(RCTValueAnimatedNode *)node];
+
+ _eventAnimationDrivers[[NSString stringWithFormat:@"%@%@", viewTag, eventName]] = driver;
+}
+
+RCT_EXPORT_METHOD(removeAnimatedEventFromView:(nonnull NSNumber *)viewTag
+ eventName:(nonnull NSString *)eventName)
+{
+ [_eventAnimationDrivers removeObjectForKey:[NSString stringWithFormat:@"%@%@", viewTag, eventName]];
+}
+
- (void)animatedNode:(RCTValueAnimatedNode *)node didUpdateValue:(CGFloat)value
{
[self sendEventWithName:@"onAnimatedValueUpdate"
body:@{@"tag": node.nodeTag, @"value": @(value)}];
}
+- (BOOL)eventDispatcherWillDispatchEvent:(id)event
+{
+ // Native animated events only work for events dispatched from the main queue.
+ if (!RCTIsMainQueue() || _eventAnimationDrivers.count == 0) {
+ return NO;
+ }
+
+ NSString *key = [NSString stringWithFormat:@"%@%@", event.viewTag, event.eventName];
+ RCTEventAnimation *driver = _eventAnimationDrivers[key];
+
+ if (driver) {
+ [driver updateWithEvent:event];
+ [self updateViewsProps];
+ [driver.valueNode cleanupAnimationUpdate];
+
+ return YES;
+ }
+
+ return NO;
+}
+
- (void)updateViewsProps
{
for (RCTPropsAnimatedNode *propsNode in _propAnimationNodes) {
@@ -278,14 +340,22 @@ RCT_EXPORT_METHOD(stopListeningToAnimatedNodeValue:(nonnull NSNumber *)tag)
#pragma mark -- Animation Loop
-- (void)startAnimation
+- (void)startAnimationLoopIfNeeded
{
- if (!_displayLink && _activeAnimations.count > 0) {
+ if (!_displayLink && (_activeAnimations.count > 0 || _updatedValueNodes.count > 0)) {
_displayLink = [CADisplayLink displayLinkWithTarget:self selector:@selector(updateAnimations)];
[_displayLink addToRunLoop:[NSRunLoop mainRunLoop] forMode:NSRunLoopCommonModes];
}
}
+- (void)stopAnimationLoopIfNeeded
+{
+ if (_displayLink && _activeAnimations.count == 0 && _updatedValueNodes.count == 0) {
+ [_displayLink invalidate];
+ _displayLink = nil;
+ }
+}
+
- (void)updateAnimations
{
// Step Current active animations
@@ -321,10 +391,7 @@ RCT_EXPORT_METHOD(stopListeningToAnimatedNodeValue:(nonnull NSNumber *)tag)
}
[_finishedAnimations removeAllObjects];
- if (_activeAnimations.count == 0) {
- [_displayLink invalidate];
- _displayLink = nil;
- }
+ [self stopAnimationLoopIfNeeded];
}
@end
diff --git a/React.podspec b/React.podspec
index 70e3f1a9d..8cd94c058 100644
--- a/React.podspec
+++ b/React.podspec
@@ -56,7 +56,7 @@ Pod::Spec.new do |s|
s.subspec 'RCTAnimation' do |ss|
ss.dependency 'React/Core'
- ss.source_files = "Libraries/NativeAnimation/{Nodes/*,*}.{h,m}"
+ ss.source_files = "Libraries/NativeAnimation/{Drivers/*,Nodes/*,*}.{h,m}"
end
s.subspec 'RCTCameraRoll' do |ss|
diff --git a/React/Base/RCTEventDispatcher.h b/React/Base/RCTEventDispatcher.h
index 6b5b448c1..b6a4d41bf 100644
--- a/React/Base/RCTEventDispatcher.h
+++ b/React/Base/RCTEventDispatcher.h
@@ -51,6 +51,20 @@ RCT_EXTERN NSString *RCTNormalizeInputEventName(NSString *eventName);
@end
+/**
+ * This protocol allows observing events dispatched by RCTEventDispatcher.
+ */
+@protocol RCTEventDispatcherObserver
+
+/**
+ * Called before dispatching an event, on the same thread the event was
+ * dispatched from. Return YES if the event was handled and must not be
+ * sent to JS.
+ */
+- (BOOL)eventDispatcherWillDispatchEvent:(id)event;
+
+@end
+
/**
* This class wraps the -[RCTBridge enqueueJSCall:args:] method, and
@@ -93,6 +107,16 @@ __deprecated_msg("Use RCTDirectEventBlock or RCTBubblingEventBlock instead");
*/
- (void)sendEvent:(id)event;
+/**
+ * Add an event dispatcher observer.
+ */
+- (void)addDispatchObserver:(id)observer;
+
+/**
+ * Remove an event dispatcher observer.
+ */
+- (void)removeDispatchObserver:(id)observer;
+
@end
@interface RCTBridge (RCTEventDispatcher)
diff --git a/React/Base/RCTEventDispatcher.m b/React/Base/RCTEventDispatcher.m
index 6fe1d4e33..381aa265c 100644
--- a/React/Base/RCTEventDispatcher.m
+++ b/React/Base/RCTEventDispatcher.m
@@ -46,6 +46,8 @@ static NSNumber *RCTGetEventID(id event)
// This array contains ids of events in order they come in, so we can emit them to JS in the exact same order.
NSMutableArray *_eventQueue;
BOOL _eventsDispatchScheduled;
+ NSMutableArray> *_observers;
+ NSLock *_observersLock;
}
@synthesize bridge = _bridge;
@@ -59,6 +61,8 @@ RCT_EXPORT_MODULE()
_eventQueue = [NSMutableArray new];
_eventQueueLock = [NSLock new];
_eventsDispatchScheduled = NO;
+ _observers = [NSMutableArray new];
+ _observersLock = [NSLock new];
}
- (void)sendAppEventWithName:(NSString *)name body:(id)body
@@ -140,6 +144,21 @@ RCT_EXPORT_MODULE()
- (void)sendEvent:(id)event
{
+ [_observersLock lock];
+
+ BOOL eventHandled = NO;
+ for (id observer in _observers) {
+ if ([observer eventDispatcherWillDispatchEvent:event]) {
+ eventHandled = YES;
+ }
+ }
+
+ [_observersLock unlock];
+
+ if (eventHandled) {
+ return;
+ }
+
[_eventQueueLock lock];
NSNumber *eventID = RCTGetEventID(event);
@@ -171,6 +190,20 @@ RCT_EXPORT_MODULE()
}
}
+- (void)addDispatchObserver:(id)observer
+{
+ [_observersLock lock];
+ [_observers addObject:observer];
+ [_observersLock unlock];
+}
+
+- (void)removeDispatchObserver:(id)observer
+{
+ [_observersLock lock];
+ [_observers removeObject:observer];
+ [_observersLock unlock];
+}
+
- (void)dispatchEvent:(id)event
{
[_bridge enqueueJSCall:[[event class] moduleDotMethod] args:[event arguments]];