mirror of
https://github.com/facebook/react-native.git
synced 2025-11-01 09:14:26 +00:00
bb435a2b11
Summary: In order to support AnimatedColor.setValue for platform colors, we need to pass the platform color object to the native animated node which will then resolve and apply the color. Thus, the approach is: - Add a new API updateAnimatedNodeConfig to NativeAnimatedModule - [JS] On AnimatedColor.setValue, if the value is a platform color, then we call updateAnimatedNodeConfig - [Android] We introduce AnimatedNodeWithUpdateableConfig interface with a method updateConfig. On ColorAnimatedNode.java, we use updateConfig to resolve and apply the color Changelog: [Internal][Fixed] - Use context from view when resolving platform color Reviewed By: javache, mdvacca Differential Revision: D34025193 fbshipit-source-id: 8b368f6b7cb2cf7cebe8b66461cd4185cbadd44c
490 lines
15 KiB
Objective-C
490 lines
15 KiB
Objective-C
/*
|
|
* Copyright (c) Meta Platforms, Inc. and affiliates.
|
|
*
|
|
* This source code is licensed under the MIT license found in the
|
|
* LICENSE file in the root directory of this source tree.
|
|
*/
|
|
|
|
#import <React/RCTNativeAnimatedNodesManager.h>
|
|
|
|
#import <React/RCTConvert.h>
|
|
|
|
#import <React/RCTAdditionAnimatedNode.h>
|
|
#import <React/RCTAnimatedNode.h>
|
|
#import <React/RCTAnimationDriver.h>
|
|
#import <React/RCTDiffClampAnimatedNode.h>
|
|
#import <React/RCTDivisionAnimatedNode.h>
|
|
#import <React/RCTEventAnimation.h>
|
|
#import <React/RCTFrameAnimation.h>
|
|
#import <React/RCTDecayAnimation.h>
|
|
#import <React/RCTInterpolationAnimatedNode.h>
|
|
#import <React/RCTModuloAnimatedNode.h>
|
|
#import <React/RCTMultiplicationAnimatedNode.h>
|
|
#import <React/RCTPropsAnimatedNode.h>
|
|
#import <React/RCTSpringAnimation.h>
|
|
#import <React/RCTStyleAnimatedNode.h>
|
|
#import <React/RCTSubtractionAnimatedNode.h>
|
|
#import <React/RCTTransformAnimatedNode.h>
|
|
#import <React/RCTValueAnimatedNode.h>
|
|
#import <React/RCTTrackingAnimatedNode.h>
|
|
|
|
// We do some normalizing of the event names in RCTEventDispatcher#RCTNormalizeInputEventName.
|
|
// To make things simpler just get rid of the parts we change in the event names we use here.
|
|
// This is a lot easier than trying to denormalize because there would be multiple possible
|
|
// denormalized forms for a single input.
|
|
static NSString *RCTNormalizeAnimatedEventName(NSString *eventName)
|
|
{
|
|
if ([eventName hasPrefix:@"on"]) {
|
|
return [eventName substringFromIndex:2];
|
|
}
|
|
if ([eventName hasPrefix:@"top"]) {
|
|
return [eventName substringFromIndex:3];
|
|
}
|
|
return eventName;
|
|
}
|
|
|
|
@implementation RCTNativeAnimatedNodesManager
|
|
{
|
|
__weak RCTBridge *_bridge;
|
|
__weak id<RCTSurfacePresenterStub> _surfacePresenter;
|
|
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:(nullable RCTBridge *)bridge surfacePresenter:(id<RCTSurfacePresenterStub>)surfacePresenter;
|
|
{
|
|
if ((self = [super init])) {
|
|
_bridge = bridge;
|
|
_surfacePresenter = surfacePresenter;
|
|
_animationNodes = [NSMutableDictionary new];
|
|
_eventDrivers = [NSMutableDictionary new];
|
|
_activeAnimations = [NSMutableSet new];
|
|
}
|
|
return self;
|
|
}
|
|
|
|
- (BOOL)isNodeManagedByFabric:(NSNumber *)tag
|
|
{
|
|
RCTAnimatedNode *node = _animationNodes[tag];
|
|
if (node) {
|
|
return [node isManagedByFabric];
|
|
}
|
|
return false;
|
|
}
|
|
|
|
#pragma mark -- Graph
|
|
|
|
- (void)createAnimatedNode:(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:(NSNumber *)parentTag
|
|
childTag:(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:(NSNumber *)parentTag
|
|
childTag:(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:(NSNumber *)nodeTag
|
|
viewTag:(NSNumber *)viewTag
|
|
viewName:(nullable NSString *)viewName
|
|
{
|
|
RCTAnimatedNode *node = _animationNodes[nodeTag];
|
|
if ([node isKindOfClass:[RCTPropsAnimatedNode class]]) {
|
|
// viewName is not used when node is managed by Fabric
|
|
[(RCTPropsAnimatedNode *)node connectToView:viewTag
|
|
viewName:viewName
|
|
bridge:_bridge
|
|
surfacePresenter:_surfacePresenter];
|
|
}
|
|
[node setNeedsUpdate];
|
|
}
|
|
|
|
- (void)disconnectAnimatedNodeFromView:(NSNumber *)nodeTag
|
|
viewTag:(NSNumber *)viewTag
|
|
{
|
|
RCTAnimatedNode *node = _animationNodes[nodeTag];
|
|
if ([node isKindOfClass:[RCTPropsAnimatedNode class]]) {
|
|
[(RCTPropsAnimatedNode *)node disconnectFromView:viewTag];
|
|
}
|
|
}
|
|
|
|
- (void)restoreDefaultValues:(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:(NSNumber *)tag
|
|
{
|
|
RCTAnimatedNode *node = _animationNodes[tag];
|
|
if (node) {
|
|
[node detachNode];
|
|
[_animationNodes removeObjectForKey:tag];
|
|
}
|
|
}
|
|
|
|
#pragma mark -- Mutations
|
|
|
|
- (void)setAnimatedNodeValue:(NSNumber *)nodeTag
|
|
value:(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:(NSNumber *)nodeTag
|
|
offset:(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:(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:(NSNumber *)nodeTag
|
|
{
|
|
RCTAnimatedNode *node = _animationNodes[nodeTag];
|
|
if (![node isKindOfClass:[RCTValueAnimatedNode class]]) {
|
|
RCTLogError(@"Not a value node.");
|
|
return;
|
|
}
|
|
|
|
RCTValueAnimatedNode *valueNode = (RCTValueAnimatedNode *)node;
|
|
[valueNode extractOffset];
|
|
}
|
|
|
|
- (void)getValue:(NSNumber *)nodeTag saveCallback:(RCTResponseSenderBlock)saveCallback
|
|
{
|
|
RCTAnimatedNode *node = _animationNodes[nodeTag];
|
|
if (![node isKindOfClass:[RCTValueAnimatedNode class]]) {
|
|
RCTLogError(@"Not a value node.");
|
|
return;
|
|
}
|
|
RCTValueAnimatedNode *valueNode = (RCTValueAnimatedNode *)node;;
|
|
saveCallback(@[@(valueNode.value)]);
|
|
}
|
|
|
|
- (void)updateAnimatedNodeConfig:(NSNumber *)tag
|
|
config:(NSDictionary<NSString *, id> *)config
|
|
{
|
|
// TODO (T111179606): Support platform colors for color animations
|
|
}
|
|
|
|
#pragma mark -- Drivers
|
|
|
|
- (void)startAnimatingNode:(NSNumber *)animationId
|
|
nodeTag:(NSNumber *)nodeTag
|
|
config:(NSDictionary<NSString *, id> *)config
|
|
endCallback:(nullable 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:(NSNumber *)animationId
|
|
{
|
|
for (id<RCTAnimationDriver> driver in _activeAnimations) {
|
|
if ([driver.animationId isEqual:animationId]) {
|
|
[driver stopAnimation];
|
|
[_activeAnimations removeObject:driver];
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
- (void)stopAnimationsForNode:(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:(NSNumber *)viewTag
|
|
eventName:(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, RCTNormalizeAnimatedEventName(eventName)];
|
|
if (_eventDrivers[key] != nil) {
|
|
[_eventDrivers[key] addObject:driver];
|
|
} else {
|
|
NSMutableArray<RCTEventAnimation *> *drivers = [NSMutableArray new];
|
|
[drivers addObject:driver];
|
|
_eventDrivers[key] = drivers;
|
|
}
|
|
}
|
|
|
|
- (void)removeAnimatedEventFromView:(NSNumber *)viewTag
|
|
eventName:(NSString *)eventName
|
|
animatedNodeTag:(NSNumber *)animatedNodeTag
|
|
{
|
|
NSString *key = [NSString stringWithFormat:@"%@%@", viewTag, RCTNormalizeAnimatedEventName(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, RCTNormalizeAnimatedEventName(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:(NSNumber *)tag
|
|
valueObserver:(id<RCTValueAnimatedNodeObserver>)valueObserver
|
|
{
|
|
RCTAnimatedNode *node = _animationNodes[tag];
|
|
if ([node isKindOfClass:[RCTValueAnimatedNode class]]) {
|
|
((RCTValueAnimatedNode *)node).valueObserver = valueObserver;
|
|
}
|
|
}
|
|
|
|
- (void)stopListeningToAnimatedNodeValue:(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
|