Files
react-native/Libraries/NativeAnimation/RCTNativeAnimatedNodesManager.m
T
Genki Kondo bb435a2b11 Support AnimatedColor.setValue for platform colors
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
2022-02-08 16:31:14 -08:00

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