mirror of
https://github.com/zhigang1992/react-native.git
synced 2026-01-12 22:50:10 +08:00
Summary: Not super clean, but not terrible. Unfortunately this still relies on the old Paper UIManager calling delegate methods to flush the operations queues. This will work for Marketplace You since Paper will be active, but we need to fix this, along with Animated Events which don't work at all yet. Random aside: it seems like taps are less responsive in fabric vs. paper, at least on iOS. There is a sporadic delay between the touches event coming in nativly to the JS callback invoking the native module function to start the animation - this will need some debugging. Reviewed By: shergin Differential Revision: D14143331 fbshipit-source-id: 63a17eaafa1217d77a532a2716d9f886a96fae59
443 lines
14 KiB
Objective-C
443 lines
14 KiB
Objective-C
/**
|
|
* Copyright (c) Facebook, Inc. and its affiliates.
|
|
*
|
|
* This source code is licensed under the MIT license found in the
|
|
* LICENSE file in the root directory of this source tree.
|
|
*/
|
|
|
|
#import "RCTNativeAnimatedNodesManager.h"
|
|
|
|
#import <React/RCTConvert.h>
|
|
|
|
#import "RCTAdditionAnimatedNode.h"
|
|
#import "RCTAnimatedNode.h"
|
|
#import "RCTAnimationDriver.h"
|
|
#import "RCTDiffClampAnimatedNode.h"
|
|
#import "RCTDivisionAnimatedNode.h"
|
|
#import "RCTEventAnimation.h"
|
|
#import "RCTFrameAnimation.h"
|
|
#import "RCTDecayAnimation.h"
|
|
#import "RCTInterpolationAnimatedNode.h"
|
|
#import "RCTModuloAnimatedNode.h"
|
|
#import "RCTMultiplicationAnimatedNode.h"
|
|
#import "RCTPropsAnimatedNode.h"
|
|
#import "RCTSpringAnimation.h"
|
|
#import "RCTStyleAnimatedNode.h"
|
|
#import "RCTSubtractionAnimatedNode.h"
|
|
#import "RCTTransformAnimatedNode.h"
|
|
#import "RCTValueAnimatedNode.h"
|
|
#import "RCTTrackingAnimatedNode.h"
|
|
|
|
@implementation RCTNativeAnimatedNodesManager
|
|
{
|
|
__weak RCTBridge *_bridge;
|
|
NSMutableDictionary<NSNumber *, RCTAnimatedNode *> *_animationNodes;
|
|
// Mapping of a view tag and an event name to a list of event animation drivers. 99% of the time
|
|
// there will be only one driver per mapping so all code code should be optimized around that.
|
|
NSMutableDictionary<NSString *, NSMutableArray<RCTEventAnimation *> *> *_eventDrivers;
|
|
NSMutableSet<id<RCTAnimationDriver>> *_activeAnimations;
|
|
CADisplayLink *_displayLink;
|
|
}
|
|
|
|
- (instancetype)initWithBridge:(nonnull RCTBridge *)bridge
|
|
{
|
|
if ((self = [super init])) {
|
|
_bridge = bridge;
|
|
_animationNodes = [NSMutableDictionary new];
|
|
_eventDrivers = [NSMutableDictionary new];
|
|
_activeAnimations = [NSMutableSet new];
|
|
}
|
|
return self;
|
|
}
|
|
|
|
#pragma mark -- Graph
|
|
|
|
- (void)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],
|
|
@"subtraction" : [RCTSubtractionAnimatedNode class],
|
|
@"transform" : [RCTTransformAnimatedNode class],
|
|
@"tracking" : [RCTTrackingAnimatedNode 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];
|
|
node.manager = self;
|
|
_animationNodes[tag] = node;
|
|
[node setNeedsUpdate];
|
|
}
|
|
|
|
- (void)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];
|
|
[childNode setNeedsUpdate];
|
|
}
|
|
|
|
- (void)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];
|
|
[childNode setNeedsUpdate];
|
|
}
|
|
|
|
- (void)connectAnimatedNodeToView:(nonnull NSNumber *)nodeTag
|
|
viewTag:(nonnull NSNumber *)viewTag
|
|
viewName:(nonnull NSString *)viewName
|
|
{
|
|
RCTAnimatedNode *node = _animationNodes[nodeTag];
|
|
if ([node isKindOfClass:[RCTPropsAnimatedNode class]]) {
|
|
[(RCTPropsAnimatedNode *)node connectToView:viewTag viewName:viewName bridge:_bridge];
|
|
}
|
|
[node setNeedsUpdate];
|
|
}
|
|
|
|
- (void)disconnectAnimatedNodeFromView:(nonnull NSNumber *)nodeTag
|
|
viewTag:(nonnull NSNumber *)viewTag
|
|
{
|
|
RCTAnimatedNode *node = _animationNodes[nodeTag];
|
|
if ([node isKindOfClass:[RCTPropsAnimatedNode class]]) {
|
|
[(RCTPropsAnimatedNode *)node disconnectFromView:viewTag];
|
|
}
|
|
}
|
|
|
|
- (void)restoreDefaultValues:(nonnull NSNumber *)nodeTag
|
|
{
|
|
RCTAnimatedNode *node = _animationNodes[nodeTag];
|
|
// Restoring default values needs to happen before UIManager operations so it is
|
|
// possible the node hasn't been created yet if it is being connected and
|
|
// disconnected in the same batch. In that case we don't need to restore
|
|
// default values since it will never actually update the view.
|
|
if (node == nil) {
|
|
return;
|
|
}
|
|
if (![node isKindOfClass:[RCTPropsAnimatedNode class]]) {
|
|
RCTLogError(@"Not a props node.");
|
|
}
|
|
[(RCTPropsAnimatedNode *)node restoreDefaultValues];
|
|
}
|
|
|
|
- (void)dropAnimatedNode:(nonnull NSNumber *)tag
|
|
{
|
|
RCTAnimatedNode *node = _animationNodes[tag];
|
|
if (node) {
|
|
[node detachNode];
|
|
[_animationNodes removeObjectForKey:tag];
|
|
}
|
|
}
|
|
|
|
#pragma mark -- Mutations
|
|
|
|
- (void)setAnimatedNodeValue:(nonnull NSNumber *)nodeTag
|
|
value:(nonnull NSNumber *)value
|
|
{
|
|
RCTAnimatedNode *node = _animationNodes[nodeTag];
|
|
if (![node isKindOfClass:[RCTValueAnimatedNode class]]) {
|
|
RCTLogError(@"Not a value node.");
|
|
return;
|
|
}
|
|
[self stopAnimationsForNode:node];
|
|
|
|
RCTValueAnimatedNode *valueNode = (RCTValueAnimatedNode *)node;
|
|
valueNode.value = value.floatValue;
|
|
[valueNode setNeedsUpdate];
|
|
}
|
|
|
|
- (void)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];
|
|
[valueNode setNeedsUpdate];
|
|
}
|
|
|
|
- (void)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];
|
|
}
|
|
|
|
- (void)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];
|
|
}
|
|
|
|
#pragma mark -- Drivers
|
|
|
|
- (void)startAnimatingNode:(nonnull NSNumber *)animationId
|
|
nodeTag:(nonnull NSNumber *)nodeTag
|
|
config:(NSDictionary<NSString *, id> *)config
|
|
endCallback:(RCTResponseSenderBlock)callBack
|
|
{
|
|
// check if the animation has already started
|
|
for (id<RCTAnimationDriver> driver in _activeAnimations) {
|
|
if ([driver.animationId isEqual:animationId]) {
|
|
// if the animation is running, we restart it with an updated configuration
|
|
[driver resetAnimationConfig:config];
|
|
return;
|
|
}
|
|
}
|
|
|
|
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 if ([type isEqual:@"decay"]) {
|
|
animationDriver = [[RCTDecayAnimation alloc] initWithId:animationId
|
|
config:config
|
|
forNode:valueNode
|
|
callBack:callBack];
|
|
} else {
|
|
RCTLogError(@"Unsupported animation type: %@", config[@"type"]);
|
|
return;
|
|
}
|
|
|
|
[_activeAnimations addObject:animationDriver];
|
|
[animationDriver startAnimation];
|
|
[self startAnimationLoopIfNeeded];
|
|
}
|
|
|
|
- (void)stopAnimation:(nonnull NSNumber *)animationId
|
|
{
|
|
for (id<RCTAnimationDriver> driver in _activeAnimations) {
|
|
if ([driver.animationId isEqual:animationId]) {
|
|
[driver stopAnimation];
|
|
[_activeAnimations removeObject:driver];
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
- (void)stopAnimationsForNode:(nonnull RCTAnimatedNode *)node
|
|
{
|
|
NSMutableArray<id<RCTAnimationDriver>> *discarded = [NSMutableArray new];
|
|
for (id<RCTAnimationDriver> driver in _activeAnimations) {
|
|
if ([driver.valueNode isEqual:node]) {
|
|
[discarded addObject:driver];
|
|
}
|
|
}
|
|
for (id<RCTAnimationDriver> driver in discarded) {
|
|
[driver stopAnimation];
|
|
[_activeAnimations removeObject:driver];
|
|
}
|
|
}
|
|
|
|
#pragma mark -- Events
|
|
|
|
- (void)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];
|
|
|
|
NSString *key = [NSString stringWithFormat:@"%@%@", viewTag, eventName];
|
|
if (_eventDrivers[key] != nil) {
|
|
[_eventDrivers[key] addObject:driver];
|
|
} else {
|
|
NSMutableArray<RCTEventAnimation *> *drivers = [NSMutableArray new];
|
|
[drivers addObject:driver];
|
|
_eventDrivers[key] = drivers;
|
|
}
|
|
}
|
|
|
|
- (void)removeAnimatedEventFromView:(nonnull NSNumber *)viewTag
|
|
eventName:(nonnull NSString *)eventName
|
|
animatedNodeTag:(nonnull NSNumber *)animatedNodeTag
|
|
{
|
|
NSString *key = [NSString stringWithFormat:@"%@%@", viewTag, eventName];
|
|
if (_eventDrivers[key] != nil) {
|
|
if (_eventDrivers[key].count == 1) {
|
|
[_eventDrivers removeObjectForKey:key];
|
|
} else {
|
|
NSMutableArray<RCTEventAnimation *> *driversForKey = _eventDrivers[key];
|
|
for (NSUInteger i = 0; i < driversForKey.count; i++) {
|
|
if (driversForKey[i].valueNode.nodeTag == animatedNodeTag) {
|
|
[driversForKey removeObjectAtIndex:i];
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
- (void)handleAnimatedEvent:(id<RCTEvent>)event
|
|
{
|
|
if (_eventDrivers.count == 0) {
|
|
return;
|
|
}
|
|
|
|
NSString *key = [NSString stringWithFormat:@"%@%@", event.viewTag, event.eventName];
|
|
NSMutableArray<RCTEventAnimation *> *driversForKey = _eventDrivers[key];
|
|
if (driversForKey) {
|
|
for (RCTEventAnimation *driver in driversForKey) {
|
|
[self stopAnimationsForNode:driver.valueNode];
|
|
[driver updateWithEvent:event];
|
|
}
|
|
|
|
[self updateAnimations];
|
|
}
|
|
}
|
|
|
|
#pragma mark -- Listeners
|
|
|
|
- (void)startListeningToAnimatedNodeValue:(nonnull NSNumber *)tag
|
|
valueObserver:(id<RCTValueAnimatedNodeObserver>)valueObserver
|
|
{
|
|
RCTAnimatedNode *node = _animationNodes[tag];
|
|
if ([node isKindOfClass:[RCTValueAnimatedNode class]]) {
|
|
((RCTValueAnimatedNode *)node).valueObserver = valueObserver;
|
|
}
|
|
}
|
|
|
|
- (void)stopListeningToAnimatedNodeValue:(nonnull NSNumber *)tag
|
|
{
|
|
RCTAnimatedNode *node = _animationNodes[tag];
|
|
if ([node isKindOfClass:[RCTValueAnimatedNode class]]) {
|
|
((RCTValueAnimatedNode *)node).valueObserver = nil;
|
|
}
|
|
}
|
|
|
|
|
|
#pragma mark -- Animation Loop
|
|
|
|
- (void)startAnimationLoopIfNeeded
|
|
{
|
|
if (!_displayLink && _activeAnimations.count > 0) {
|
|
_displayLink = [CADisplayLink displayLinkWithTarget:self selector:@selector(stepAnimations:)];
|
|
[_displayLink addToRunLoop:[NSRunLoop mainRunLoop] forMode:NSRunLoopCommonModes];
|
|
}
|
|
}
|
|
|
|
- (void)stopAnimationLoopIfNeeded
|
|
{
|
|
if (_activeAnimations.count == 0) {
|
|
[self stopAnimationLoop];
|
|
}
|
|
}
|
|
|
|
- (void)stopAnimationLoop
|
|
{
|
|
if (_displayLink) {
|
|
[_displayLink invalidate];
|
|
_displayLink = nil;
|
|
}
|
|
}
|
|
|
|
- (void)stepAnimations:(CADisplayLink *)displaylink
|
|
{
|
|
NSTimeInterval time = displaylink.timestamp;
|
|
for (id<RCTAnimationDriver> animationDriver in _activeAnimations) {
|
|
[animationDriver stepAnimationWithTime:time];
|
|
}
|
|
|
|
[self updateAnimations];
|
|
|
|
for (id<RCTAnimationDriver> animationDriver in [_activeAnimations copy]) {
|
|
if (animationDriver.animationHasFinished) {
|
|
[animationDriver stopAnimation];
|
|
[_activeAnimations removeObject:animationDriver];
|
|
}
|
|
}
|
|
|
|
[self stopAnimationLoopIfNeeded];
|
|
}
|
|
|
|
|
|
#pragma mark -- Updates
|
|
|
|
- (void)updateAnimations
|
|
{
|
|
[_animationNodes enumerateKeysAndObjectsUsingBlock:^(NSNumber *key, RCTAnimatedNode *node, BOOL *stop) {
|
|
if (node.needsUpdate) {
|
|
[node updateNodeIfNecessary];
|
|
}
|
|
}];
|
|
}
|
|
|
|
@end
|