mirror of
https://github.com/zhigang1992/react-native.git
synced 2026-05-03 23:35:06 +08:00
Fix NativeAnimation invalidation & races on iOS
Summary: This diff attempts to fix a number of iOS native animation bugs related to improper node invalidation and a race with view creation. The major issues were presented in #9120 as problems 3 and 3b, but I'll recap here: The invalidation model we use is overly complicated and incomplete. The proper combination of `_needsUpdate` and `_hasUpdated` will result in nodes values being recomputed. However, we do not invalidate nodes in all the places we should, e.g. if we create a new view and attach it to an existing value node (see example in #9120). This diff chooses to remove the `_hasUpdated` flag, and simply relies on the `_needsUpdate` flag to mark a node as dirty. We mark nodes as dirty when they are: - created - updated - attached to new parents - detached from old parents - attached to a view Calling `updateNodeIfNecessary` will, if necessary, compute all invalidated parent values before recomputing the node value. It will then apply the update, and mark the no Closes https://github.com/facebook/react-native/pull/10663 Differential Revision: D4120301 Pulled By: mkonicek fbshipit-source-id: e247afcb5d8c15999b8328c664b9f7e764d76a75
This commit is contained in:
committed by
Facebook Github Bot
parent
bf901d926e
commit
c858420b2d
@@ -8,134 +8,62 @@
|
||||
*/
|
||||
#import "RCTNativeAnimatedModule.h"
|
||||
|
||||
#import <React/RCTBridge.h>
|
||||
#import <React/RCTConvert.h>
|
||||
#import <React/RCTLog.h>
|
||||
#import "RCTNativeAnimatedNodesManager.h"
|
||||
|
||||
#import "RCTAdditionAnimatedNode.h"
|
||||
#import "RCTAnimationDriver.h"
|
||||
#import "RCTAnimationUtils.h"
|
||||
#import "RCTDiffClampAnimatedNode.h"
|
||||
#import "RCTDivisionAnimatedNode.h"
|
||||
#import "RCTEventAnimation.h"
|
||||
#import "RCTFrameAnimation.h"
|
||||
#import "RCTInterpolationAnimatedNode.h"
|
||||
#import "RCTModuloAnimatedNode.h"
|
||||
#import "RCTMultiplicationAnimatedNode.h"
|
||||
#import "RCTPropsAnimatedNode.h"
|
||||
#import "RCTSpringAnimation.h"
|
||||
#import "RCTStyleAnimatedNode.h"
|
||||
#import "RCTTransformAnimatedNode.h"
|
||||
#import "RCTValueAnimatedNode.h"
|
||||
typedef void (^AnimatedOperation)(RCTNativeAnimatedNodesManager *nodesManager);
|
||||
|
||||
@implementation RCTNativeAnimatedModule
|
||||
{
|
||||
NSMutableDictionary<NSNumber *, RCTAnimatedNode *> *_animationNodes;
|
||||
NSMutableDictionary<NSNumber *, id<RCTAnimationDriver>> *_animationDrivers;
|
||||
NSMutableDictionary<NSString *, RCTEventAnimation *> *_eventAnimationDrivers;
|
||||
NSMutableSet<id<RCTAnimationDriver>> *_activeAnimations;
|
||||
NSMutableSet<id<RCTAnimationDriver>> *_finishedAnimations;
|
||||
NSMutableSet<RCTValueAnimatedNode *> *_updatedValueNodes;
|
||||
NSMutableSet<RCTPropsAnimatedNode *> *_propAnimationNodes;
|
||||
CADisplayLink *_displayLink;
|
||||
RCTNativeAnimatedNodesManager *_nodesManager;
|
||||
NSMutableArray<AnimatedOperation> *_operations;
|
||||
}
|
||||
|
||||
RCT_EXPORT_MODULE()
|
||||
|
||||
- (void)setBridge:(RCTBridge *)bridge
|
||||
{
|
||||
[super setBridge:bridge];
|
||||
|
||||
_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];
|
||||
}
|
||||
RCT_EXPORT_MODULE();
|
||||
|
||||
- (void)dealloc
|
||||
{
|
||||
[self.bridge.eventDispatcher removeDispatchObserver:self];
|
||||
}
|
||||
|
||||
|
||||
- (dispatch_queue_t)methodQueue
|
||||
{
|
||||
return dispatch_get_main_queue();
|
||||
return RCTGetUIManagerQueue();
|
||||
}
|
||||
|
||||
- (NSArray<NSString *> *)supportedEvents
|
||||
- (void)setBridge:(RCTBridge *)bridge
|
||||
{
|
||||
return @[@"onAnimatedValueUpdate"];
|
||||
[super setBridge:bridge];
|
||||
|
||||
_nodesManager = [[RCTNativeAnimatedNodesManager alloc] initWithUIManager:self.bridge.uiManager];
|
||||
_operations = [NSMutableArray new];
|
||||
|
||||
[bridge.eventDispatcher addDispatchObserver:self];
|
||||
}
|
||||
|
||||
#pragma mark -- API
|
||||
|
||||
RCT_EXPORT_METHOD(createAnimatedNode:(nonnull NSNumber *)tag
|
||||
config:(NSDictionary<NSString *, id> *)config)
|
||||
{
|
||||
static NSDictionary *map;
|
||||
static dispatch_once_t mapToken;
|
||||
dispatch_once(&mapToken, ^{
|
||||
map = @{@"style" : [RCTStyleAnimatedNode class],
|
||||
@"value" : [RCTValueAnimatedNode class],
|
||||
@"props" : [RCTPropsAnimatedNode class],
|
||||
@"interpolation" : [RCTInterpolationAnimatedNode class],
|
||||
@"addition" : [RCTAdditionAnimatedNode class],
|
||||
@"diffclamp": [RCTDiffClampAnimatedNode class],
|
||||
@"division" : [RCTDivisionAnimatedNode class],
|
||||
@"multiplication" : [RCTMultiplicationAnimatedNode class],
|
||||
@"modulus" : [RCTModuloAnimatedNode class],
|
||||
@"transform" : [RCTTransformAnimatedNode class]};
|
||||
});
|
||||
|
||||
NSString *nodeType = [RCTConvert NSString:config[@"type"]];
|
||||
|
||||
Class nodeClass = map[nodeType];
|
||||
if (!nodeClass) {
|
||||
RCTLogError(@"Animated node type %@ not supported natively", nodeType);
|
||||
return;
|
||||
}
|
||||
|
||||
RCTAnimatedNode *node = [[nodeClass alloc] initWithTag:tag config:config];
|
||||
|
||||
_animationNodes[tag] = node;
|
||||
|
||||
if ([node isKindOfClass:[RCTPropsAnimatedNode class]]) {
|
||||
[_propAnimationNodes addObject:(RCTPropsAnimatedNode *)node];
|
||||
}
|
||||
[_operations addObject:^(RCTNativeAnimatedNodesManager *nodesManager) {
|
||||
[nodesManager createAnimatedNode:tag config:config];
|
||||
}];
|
||||
}
|
||||
|
||||
RCT_EXPORT_METHOD(connectAnimatedNodes:(nonnull NSNumber *)parentTag
|
||||
childTag:(nonnull NSNumber *)childTag)
|
||||
{
|
||||
RCTAssertParam(parentTag);
|
||||
RCTAssertParam(childTag);
|
||||
|
||||
RCTAnimatedNode *parentNode = _animationNodes[parentTag];
|
||||
RCTAnimatedNode *childNode = _animationNodes[childTag];
|
||||
|
||||
RCTAssertParam(parentNode);
|
||||
RCTAssertParam(childNode);
|
||||
|
||||
[parentNode addChild:childNode];
|
||||
[_operations addObject:^(RCTNativeAnimatedNodesManager *nodesManager) {
|
||||
[nodesManager connectAnimatedNodes:parentTag childTag:childTag];
|
||||
}];
|
||||
}
|
||||
|
||||
RCT_EXPORT_METHOD(disconnectAnimatedNodes:(nonnull NSNumber *)parentTag
|
||||
childTag:(nonnull NSNumber *)childTag)
|
||||
{
|
||||
RCTAssertParam(parentTag);
|
||||
RCTAssertParam(childTag);
|
||||
|
||||
RCTAnimatedNode *parentNode = _animationNodes[parentTag];
|
||||
RCTAnimatedNode *childNode = _animationNodes[childTag];
|
||||
|
||||
RCTAssertParam(parentNode);
|
||||
RCTAssertParam(childNode);
|
||||
|
||||
[parentNode removeChild:childNode];
|
||||
[_operations addObject:^(RCTNativeAnimatedNodesManager *nodesManager) {
|
||||
[nodesManager disconnectAnimatedNodes:parentTag childTag:childTag];
|
||||
}];
|
||||
}
|
||||
|
||||
RCT_EXPORT_METHOD(startAnimatingNode:(nonnull NSNumber *)animationId
|
||||
@@ -143,178 +71,124 @@ RCT_EXPORT_METHOD(startAnimatingNode:(nonnull NSNumber *)animationId
|
||||
config:(NSDictionary<NSString *, id> *)config
|
||||
endCallback:(RCTResponseSenderBlock)callBack)
|
||||
{
|
||||
RCTValueAnimatedNode *valueNode = (RCTValueAnimatedNode *)_animationNodes[nodeTag];
|
||||
|
||||
NSString *type = config[@"type"];
|
||||
id<RCTAnimationDriver> animationDriver;
|
||||
|
||||
if ([type isEqual:@"frames"]) {
|
||||
animationDriver = [[RCTFrameAnimation alloc] initWithId:animationId
|
||||
config:config
|
||||
forNode:valueNode
|
||||
callBack:callBack];
|
||||
|
||||
} else if ([type isEqual:@"spring"]) {
|
||||
animationDriver = [[RCTSpringAnimation alloc] initWithId:animationId
|
||||
config:config
|
||||
forNode:valueNode
|
||||
callBack:callBack];
|
||||
|
||||
} else {
|
||||
RCTLogError(@"Unsupported animation type: %@", config[@"type"]);
|
||||
return;
|
||||
}
|
||||
|
||||
[_activeAnimations addObject:animationDriver];
|
||||
_animationDrivers[animationId] = animationDriver;
|
||||
[animationDriver startAnimation];
|
||||
[self startAnimationLoopIfNeeded];
|
||||
[_operations addObject:^(RCTNativeAnimatedNodesManager *nodesManager) {
|
||||
[nodesManager startAnimatingNode:animationId nodeTag:nodeTag config:config endCallback:callBack];
|
||||
}];
|
||||
}
|
||||
|
||||
RCT_EXPORT_METHOD(stopAnimation:(nonnull NSNumber *)animationId)
|
||||
{
|
||||
id<RCTAnimationDriver> driver = _animationDrivers[animationId];
|
||||
if (driver) {
|
||||
[driver removeAnimation];
|
||||
[_animationDrivers removeObjectForKey:animationId];
|
||||
[_activeAnimations removeObject:driver];
|
||||
[_finishedAnimations removeObject:driver];
|
||||
}
|
||||
[_operations addObject:^(RCTNativeAnimatedNodesManager *nodesManager) {
|
||||
[nodesManager stopAnimation:animationId];
|
||||
}];
|
||||
}
|
||||
|
||||
RCT_EXPORT_METHOD(setAnimatedNodeValue:(nonnull NSNumber *)nodeTag
|
||||
value:(nonnull NSNumber *)value)
|
||||
{
|
||||
RCTAnimatedNode *node = _animationNodes[nodeTag];
|
||||
if (![node isKindOfClass:[RCTValueAnimatedNode class]]) {
|
||||
RCTLogError(@"Not a value node.");
|
||||
return;
|
||||
}
|
||||
|
||||
RCTValueAnimatedNode *valueNode = (RCTValueAnimatedNode *)node;
|
||||
valueNode.value = value.floatValue;
|
||||
[valueNode setNeedsUpdate];
|
||||
|
||||
[self updateViewsProps];
|
||||
|
||||
[valueNode cleanupAnimationUpdate];
|
||||
[_operations addObject:^(RCTNativeAnimatedNodesManager *nodesManager) {
|
||||
[nodesManager setAnimatedNodeValue:nodeTag value:value];
|
||||
}];
|
||||
}
|
||||
|
||||
RCT_EXPORT_METHOD(setAnimatedNodeOffset:(nonnull NSNumber *)nodeTag
|
||||
offset:(nonnull NSNumber *)offset)
|
||||
{
|
||||
RCTAnimatedNode *node = _animationNodes[nodeTag];
|
||||
if (![node isKindOfClass:[RCTValueAnimatedNode class]]) {
|
||||
RCTLogError(@"Not a value node.");
|
||||
return;
|
||||
}
|
||||
|
||||
RCTValueAnimatedNode *valueNode = (RCTValueAnimatedNode *)node;
|
||||
[valueNode setOffset:offset.floatValue];
|
||||
[_updatedValueNodes addObject:valueNode];
|
||||
[_operations addObject:^(RCTNativeAnimatedNodesManager *nodesManager) {
|
||||
[nodesManager setAnimatedNodeOffset:nodeTag offset:offset];
|
||||
}];
|
||||
}
|
||||
|
||||
RCT_EXPORT_METHOD(flattenAnimatedNodeOffset:(nonnull NSNumber *)nodeTag)
|
||||
{
|
||||
RCTAnimatedNode *node = _animationNodes[nodeTag];
|
||||
if (![node isKindOfClass:[RCTValueAnimatedNode class]]) {
|
||||
RCTLogError(@"Not a value node.");
|
||||
return;
|
||||
}
|
||||
|
||||
RCTValueAnimatedNode *valueNode = (RCTValueAnimatedNode *)node;
|
||||
[valueNode flattenOffset];
|
||||
[_operations addObject:^(RCTNativeAnimatedNodesManager *nodesManager) {
|
||||
[nodesManager flattenAnimatedNodeOffset:nodeTag];
|
||||
}];
|
||||
}
|
||||
|
||||
RCT_EXPORT_METHOD(extractAnimatedNodeOffset:(nonnull NSNumber *)nodeTag)
|
||||
{
|
||||
RCTAnimatedNode *node = _animationNodes[nodeTag];
|
||||
if (![node isKindOfClass:[RCTValueAnimatedNode class]]) {
|
||||
RCTLogError(@"Not a value node.");
|
||||
return;
|
||||
}
|
||||
|
||||
RCTValueAnimatedNode *valueNode = (RCTValueAnimatedNode *)node;
|
||||
[valueNode extractOffset];
|
||||
[_operations addObject:^(RCTNativeAnimatedNodesManager *nodesManager) {
|
||||
[nodesManager extractAnimatedNodeOffset:nodeTag];
|
||||
}];
|
||||
}
|
||||
|
||||
RCT_EXPORT_METHOD(connectAnimatedNodeToView:(nonnull NSNumber *)nodeTag
|
||||
viewTag:(nonnull NSNumber *)viewTag)
|
||||
{
|
||||
RCTAnimatedNode *node = _animationNodes[nodeTag];
|
||||
if (viewTag && [node isKindOfClass:[RCTPropsAnimatedNode class]]) {
|
||||
[(RCTPropsAnimatedNode *)node connectToView:viewTag animatedModule:self];
|
||||
}
|
||||
[_operations addObject:^(RCTNativeAnimatedNodesManager *nodesManager) {
|
||||
[nodesManager connectAnimatedNodeToView:nodeTag viewTag:viewTag];
|
||||
}];
|
||||
}
|
||||
|
||||
RCT_EXPORT_METHOD(disconnectAnimatedNodeFromView:(nonnull NSNumber *)nodeTag
|
||||
viewTag:(nonnull NSNumber *)viewTag)
|
||||
{
|
||||
RCTAnimatedNode *node = _animationNodes[nodeTag];
|
||||
if (viewTag && node && [node isKindOfClass:[RCTPropsAnimatedNode class]]) {
|
||||
[(RCTPropsAnimatedNode *)node disconnectFromView:viewTag];
|
||||
}
|
||||
[_operations addObject:^(RCTNativeAnimatedNodesManager *nodesManager) {
|
||||
[nodesManager disconnectAnimatedNodeFromView:nodeTag viewTag:viewTag];
|
||||
}];
|
||||
}
|
||||
|
||||
RCT_EXPORT_METHOD(dropAnimatedNode:(nonnull NSNumber *)tag)
|
||||
{
|
||||
RCTAnimatedNode *node = _animationNodes[tag];
|
||||
if (node) {
|
||||
[node detachNode];
|
||||
[_animationNodes removeObjectForKey:tag];
|
||||
if ([node isKindOfClass:[RCTValueAnimatedNode class]]) {
|
||||
[_updatedValueNodes removeObject:(RCTValueAnimatedNode *)node];
|
||||
} else if ([node isKindOfClass:[RCTPropsAnimatedNode class]]) {
|
||||
[_propAnimationNodes removeObject:(RCTPropsAnimatedNode *)node];
|
||||
}
|
||||
}
|
||||
[_operations addObject:^(RCTNativeAnimatedNodesManager *nodesManager) {
|
||||
[nodesManager dropAnimatedNode:tag];
|
||||
}];
|
||||
}
|
||||
|
||||
RCT_EXPORT_METHOD(startListeningToAnimatedNodeValue:(nonnull NSNumber *)tag)
|
||||
{
|
||||
RCTAnimatedNode *node = _animationNodes[tag];
|
||||
if (node && [node isKindOfClass:[RCTValueAnimatedNode class]]) {
|
||||
((RCTValueAnimatedNode *)node).valueObserver = self;
|
||||
}
|
||||
__weak id<RCTValueAnimatedNodeObserver> valueObserver = self;
|
||||
[_operations addObject:^(RCTNativeAnimatedNodesManager *nodesManager) {
|
||||
[nodesManager startListeningToAnimatedNodeValue:tag valueObserver:valueObserver];
|
||||
}];
|
||||
}
|
||||
|
||||
RCT_EXPORT_METHOD(stopListeningToAnimatedNodeValue:(nonnull NSNumber *)tag)
|
||||
{
|
||||
RCTAnimatedNode *node = _animationNodes[tag];
|
||||
if (node && [node isKindOfClass:[RCTValueAnimatedNode class]]) {
|
||||
((RCTValueAnimatedNode *)node).valueObserver = nil;
|
||||
}
|
||||
__weak id<RCTValueAnimatedNodeObserver> valueObserver = self;
|
||||
[_operations addObject:^(RCTNativeAnimatedNodesManager *nodesManager) {
|
||||
[nodesManager stopListeningToAnimatedNodeValue:tag valueObserver:valueObserver];
|
||||
}];
|
||||
}
|
||||
|
||||
RCT_EXPORT_METHOD(addAnimatedEventToView:(nonnull NSNumber *)viewTag
|
||||
eventName:(nonnull NSString *)eventName
|
||||
eventMapping:(NSDictionary<NSString *, id> *)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<NSString *> *eventPath = [RCTConvert NSStringArray:eventMapping[@"nativeEventPath"]];
|
||||
|
||||
RCTEventAnimation *driver =
|
||||
[[RCTEventAnimation alloc] initWithEventPath:eventPath valueNode:(RCTValueAnimatedNode *)node];
|
||||
|
||||
_eventAnimationDrivers[[NSString stringWithFormat:@"%@%@", viewTag, eventName]] = driver;
|
||||
[_operations addObject:^(RCTNativeAnimatedNodesManager *nodesManager) {
|
||||
[nodesManager addAnimatedEventToView:viewTag eventName:eventName eventMapping:eventMapping];
|
||||
}];
|
||||
}
|
||||
|
||||
RCT_EXPORT_METHOD(removeAnimatedEventFromView:(nonnull NSNumber *)viewTag
|
||||
eventName:(nonnull NSString *)eventName)
|
||||
{
|
||||
[_eventAnimationDrivers removeObjectForKey:[NSString stringWithFormat:@"%@%@", viewTag, eventName]];
|
||||
[_operations addObject:^(RCTNativeAnimatedNodesManager *nodesManager) {
|
||||
[nodesManager removeAnimatedEventFromView:viewTag eventName:eventName];
|
||||
}];
|
||||
}
|
||||
|
||||
#pragma mark -- Batch handling
|
||||
|
||||
- (void)batchDidComplete
|
||||
{
|
||||
NSArray *operations = _operations;
|
||||
_operations = [NSMutableArray new];
|
||||
|
||||
dispatch_async(dispatch_get_main_queue(), ^{
|
||||
[operations enumerateObjectsUsingBlock:^(AnimatedOperation operation, NSUInteger i, BOOL *stop) {
|
||||
operation(self->_nodesManager);
|
||||
}];
|
||||
[self->_nodesManager updateAnimations];
|
||||
});
|
||||
}
|
||||
|
||||
#pragma mark -- Events
|
||||
|
||||
- (NSArray<NSString *> *)supportedEvents
|
||||
{
|
||||
return @[@"onAnimatedValueUpdate"];
|
||||
}
|
||||
|
||||
- (void)animatedNode:(RCTValueAnimatedNode *)node didUpdateValue:(CGFloat)value
|
||||
@@ -326,81 +200,10 @@ RCT_EXPORT_METHOD(removeAnimatedEventFromView:(nonnull NSNumber *)viewTag
|
||||
- (void)eventDispatcherWillDispatchEvent:(id<RCTEvent>)event
|
||||
{
|
||||
// Native animated events only work for events dispatched from the main queue.
|
||||
if (!RCTIsMainQueue() || _eventAnimationDrivers.count == 0) {
|
||||
if (!RCTIsMainQueue()) {
|
||||
return;
|
||||
}
|
||||
|
||||
NSString *key = [NSString stringWithFormat:@"%@%@", event.viewTag, event.eventName];
|
||||
RCTEventAnimation *driver = _eventAnimationDrivers[key];
|
||||
|
||||
if (driver) {
|
||||
[driver updateWithEvent:event];
|
||||
[self updateViewsProps];
|
||||
[driver.valueNode cleanupAnimationUpdate];
|
||||
}
|
||||
}
|
||||
|
||||
- (void)updateViewsProps
|
||||
{
|
||||
for (RCTPropsAnimatedNode *propsNode in _propAnimationNodes) {
|
||||
[propsNode updateNodeIfNecessary];
|
||||
}
|
||||
}
|
||||
|
||||
#pragma mark -- Animation Loop
|
||||
|
||||
- (void)startAnimationLoopIfNeeded
|
||||
{
|
||||
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
|
||||
// This also recursively marks children nodes as needing update
|
||||
for (id<RCTAnimationDriver> animationDriver in _activeAnimations) {
|
||||
[animationDriver stepAnimation];
|
||||
}
|
||||
|
||||
// Perform node updates for marked nodes.
|
||||
// At this point all nodes that are in need of an update are properly marked as such.
|
||||
for (RCTPropsAnimatedNode *propsNode in _propAnimationNodes) {
|
||||
[propsNode updateNodeIfNecessary];
|
||||
}
|
||||
|
||||
// Cleanup nodes and prepare for next cycle. Remove updated nodes from bucket.
|
||||
for (id<RCTAnimationDriver> driverNode in _activeAnimations) {
|
||||
[driverNode cleanupAnimationUpdate];
|
||||
}
|
||||
for (RCTValueAnimatedNode *valueNode in _updatedValueNodes) {
|
||||
[valueNode cleanupAnimationUpdate];
|
||||
}
|
||||
[_updatedValueNodes removeAllObjects];
|
||||
|
||||
for (id<RCTAnimationDriver> driverNode in _activeAnimations) {
|
||||
if (driverNode.animationHasFinished) {
|
||||
[driverNode removeAnimation];
|
||||
[_finishedAnimations addObject:driverNode];
|
||||
}
|
||||
}
|
||||
for (id<RCTAnimationDriver> driverNode in _finishedAnimations) {
|
||||
[_activeAnimations removeObject:driverNode];
|
||||
[_animationDrivers removeObjectForKey:driverNode.animationId];
|
||||
}
|
||||
[_finishedAnimations removeAllObjects];
|
||||
|
||||
[self stopAnimationLoopIfNeeded];
|
||||
return [_nodesManager handleAnimatedEvent:event];
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
Reference in New Issue
Block a user