From d950db4ef76ecbf229ea2de1c09e5616f2a4aa24 Mon Sep 17 00:00:00 2001 From: Ryan Gomba Date: Fri, 21 Oct 2016 04:20:01 -0700 Subject: [PATCH] Add support for springs for NativeAnimated on iOS Summary: This diff adds support for native spring animations on iOS. This overlaps some spring work done by kmagiera on the Android side of things. **Test plan (required)** Run UIExplorer NativeAnimated examples before and after - compare the results. Pay special attention to the spring examples. Closes https://github.com/facebook/react-native/pull/9048 Differential Revision: D4056088 Pulled By: foghina fbshipit-source-id: a593408cb61cb850572bab4a0884f7157cece656 --- .../RCTAnimationDriver.h} | 11 +- .../Drivers/RCTFrameAnimation.h | 14 ++ .../RCTFrameAnimation.m} | 33 +-- .../Drivers/RCTSpringAnimation.h | 14 ++ .../Drivers/RCTSpringAnimation.m | 200 ++++++++++++++++++ .../RCTAnimation.xcodeproj/project.pbxproj | 34 ++- .../NativeAnimation/RCTNativeAnimatedModule.m | 54 +++-- 7 files changed, 310 insertions(+), 50 deletions(-) rename Libraries/NativeAnimation/{Nodes/RCTAnimationDriverNode.h => Drivers/RCTAnimationDriver.h} (76%) create mode 100644 Libraries/NativeAnimation/Drivers/RCTFrameAnimation.h rename Libraries/NativeAnimation/{Nodes/RCTAnimationDriverNode.m => Drivers/RCTFrameAnimation.m} (83%) create mode 100644 Libraries/NativeAnimation/Drivers/RCTSpringAnimation.h create mode 100644 Libraries/NativeAnimation/Drivers/RCTSpringAnimation.m diff --git a/Libraries/NativeAnimation/Nodes/RCTAnimationDriverNode.h b/Libraries/NativeAnimation/Drivers/RCTAnimationDriver.h similarity index 76% rename from Libraries/NativeAnimation/Nodes/RCTAnimationDriverNode.h rename to Libraries/NativeAnimation/Drivers/RCTAnimationDriver.h index 1d713437bfd..1911c1a5337 100644 --- a/Libraries/NativeAnimation/Nodes/RCTAnimationDriverNode.h +++ b/Libraries/NativeAnimation/Drivers/RCTAnimationDriver.h @@ -14,20 +14,17 @@ NS_ASSUME_NONNULL_BEGIN -@interface RCTAnimationDriverNode : NSObject +@protocol RCTAnimationDriver @property (nonatomic, readonly) NSNumber *animationId; -@property (nonatomic, readonly) NSNumber *outputValue; - +@property (nonatomic, readonly) RCTValueAnimatedNode *valueNode; @property (nonatomic, readonly) BOOL animationHasBegun; @property (nonatomic, readonly) BOOL animationHasFinished; - (instancetype)initWithId:(NSNumber *)animationId - delay:(NSTimeInterval)delay - toValue:(CGFloat)toValue - frames:(NSArray *)frames + config:(NSDictionary *)config forNode:(RCTValueAnimatedNode *)valueNode - callBack:(nullable RCTResponseSenderBlock)callback NS_DESIGNATED_INITIALIZER; + callBack:(nullable RCTResponseSenderBlock)callback; - (void)startAnimation; - (void)stopAnimation; diff --git a/Libraries/NativeAnimation/Drivers/RCTFrameAnimation.h b/Libraries/NativeAnimation/Drivers/RCTFrameAnimation.h new file mode 100644 index 00000000000..403ce7796a4 --- /dev/null +++ b/Libraries/NativeAnimation/Drivers/RCTFrameAnimation.h @@ -0,0 +1,14 @@ +/** + * 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 "RCTAnimationDriver.h" + +@interface RCTFrameAnimation : NSObject + +@end diff --git a/Libraries/NativeAnimation/Nodes/RCTAnimationDriverNode.m b/Libraries/NativeAnimation/Drivers/RCTFrameAnimation.m similarity index 83% rename from Libraries/NativeAnimation/Nodes/RCTAnimationDriverNode.m rename to Libraries/NativeAnimation/Drivers/RCTFrameAnimation.m index ae15dc84c21..b74d7811b71 100644 --- a/Libraries/NativeAnimation/Nodes/RCTAnimationDriverNode.m +++ b/Libraries/NativeAnimation/Drivers/RCTFrameAnimation.m @@ -7,17 +7,27 @@ * of patent rights can be found in the PATENTS file in the same directory. */ -#import "RCTAnimationDriverNode.h" +#import "RCTFrameAnimation.h" #import +#import "RCTConvert.h" #import "RCTAnimationUtils.h" #import "RCTDefines.h" #import "RCTValueAnimatedNode.h" const double SINGLE_FRAME_INTERVAL = 1.0 / 60.0; -@implementation RCTAnimationDriverNode +@interface RCTFrameAnimation () + +@property (nonatomic, strong) NSNumber *animationId; +@property (nonatomic, strong) RCTValueAnimatedNode *valueNode; +@property (nonatomic, assign) BOOL animationHasBegun; +@property (nonatomic, assign) BOOL animationHasFinished; + +@end + +@implementation RCTFrameAnimation { NSArray *_frames; CGFloat _toValue; @@ -25,25 +35,25 @@ const double SINGLE_FRAME_INTERVAL = 1.0 / 60.0; NSTimeInterval _delay; NSTimeInterval _animationStartTime; NSTimeInterval _animationCurrentTime; - RCTValueAnimatedNode *_valueNode; RCTResponseSenderBlock _callback; } -- (instancetype)initWithId:(nonnull NSNumber *)animationId - delay:(NSTimeInterval)delay - toValue:(CGFloat)toValue - frames:(nonnull NSArray *)frames - forNode:(nonnull RCTValueAnimatedNode *)valueNode - callBack:(nullable RCTResponseSenderBlock)callback +- (instancetype)initWithId:(NSNumber *)animationId + config:(NSDictionary *)config + forNode:(RCTValueAnimatedNode *)valueNode + callBack:(nullable RCTResponseSenderBlock)callback; { if ((self = [super init])) { + NSNumber *toValue = [RCTConvert NSNumber:config[@"toValue"]] ?: @1; + NSTimeInterval delay = [RCTConvert double:config[@"delay"]]; + NSArray *frames = [RCTConvert NSNumberArray:config[@"frames"]]; + _animationId = animationId; - _toValue = toValue; + _toValue = toValue.floatValue; _fromValue = valueNode.value; _valueNode = valueNode; _delay = delay; _frames = [frames copy]; - _outputValue = @0; _callback = [callback copy]; } return self; @@ -135,7 +145,6 @@ RCT_NOT_IMPLEMENTED(- (instancetype)init) EXTRAPOLATE_TYPE_EXTEND, EXTRAPOLATE_TYPE_EXTEND); - _outputValue = @(outputValue); _valueNode.value = outputValue; [_valueNode setNeedsUpdate]; } diff --git a/Libraries/NativeAnimation/Drivers/RCTSpringAnimation.h b/Libraries/NativeAnimation/Drivers/RCTSpringAnimation.h new file mode 100644 index 00000000000..acf1ae6063b --- /dev/null +++ b/Libraries/NativeAnimation/Drivers/RCTSpringAnimation.h @@ -0,0 +1,14 @@ +/** + * 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 "RCTAnimationDriver.h" + +@interface RCTSpringAnimation : NSObject + +@end diff --git a/Libraries/NativeAnimation/Drivers/RCTSpringAnimation.m b/Libraries/NativeAnimation/Drivers/RCTSpringAnimation.m new file mode 100644 index 00000000000..0c787500c20 --- /dev/null +++ b/Libraries/NativeAnimation/Drivers/RCTSpringAnimation.m @@ -0,0 +1,200 @@ +/** + * 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 "RCTSpringAnimation.h" + +#import + +#import "RCTConvert.h" +#import "RCTAnimationUtils.h" +#import "RCTDefines.h" +#import "RCTValueAnimatedNode.h" + +@interface RCTSpringAnimation () + +@property (nonatomic, strong) NSNumber *animationId; +@property (nonatomic, strong) RCTValueAnimatedNode *valueNode; +@property (nonatomic, assign) BOOL animationHasBegun; +@property (nonatomic, assign) BOOL animationHasFinished; + +@end + +@implementation RCTSpringAnimation +{ + CGFloat _toValue; + CGFloat _fromValue; + BOOL _overshootClamping; + CGFloat _restDisplacementThreshold; + CGFloat _restSpeedThreshold; + CGFloat _tension; + CGFloat _friction; + CGFloat _initialVelocity; + NSTimeInterval _animationStartTime; + NSTimeInterval _animationCurrentTime; + RCTResponseSenderBlock _callback; + + CGFloat _lastPosition; + CGFloat _lastVelocity; +} + +- (instancetype)initWithId:(NSNumber *)animationId + config:(NSDictionary *)config + forNode:(RCTValueAnimatedNode *)valueNode + callBack:(nullable RCTResponseSenderBlock)callback +{ + if ((self = [super init])) { + _animationId = animationId; + _toValue = [RCTConvert CGFloat:config[@"toValue"]]; + _fromValue = valueNode.value; + _valueNode = valueNode; + _overshootClamping = [RCTConvert BOOL:config[@"overshootClamping"]]; + _restDisplacementThreshold = [RCTConvert CGFloat:config[@"restDisplacementThreshold"]]; + _restSpeedThreshold = [RCTConvert CGFloat:config[@"restSpeedThreshold"]]; + _tension = [RCTConvert CGFloat:config[@"tension"]]; + _friction = [RCTConvert CGFloat:config[@"friction"]]; + _initialVelocity = [RCTConvert CGFloat:config[@"initialVelocity"]]; + _callback = [callback copy]; + + _lastPosition = _fromValue; + _lastVelocity = _initialVelocity; + } + return self; +} + +RCT_NOT_IMPLEMENTED(- (instancetype)init) + +- (void)startAnimation +{ + _animationStartTime = CACurrentMediaTime(); + _animationCurrentTime = _animationStartTime; + _animationHasBegun = YES; +} + +- (void)stopAnimation +{ + _animationHasFinished = YES; +} + +- (void)removeAnimation +{ + [self stopAnimation]; + _valueNode = nil; + if (_callback) { + _callback(@[@{ + @"finished": @(_animationHasFinished) + }]); + } +} + +- (void)stepAnimation +{ + if (!_animationHasBegun || _animationHasFinished) { + // Animation has not begun or animation has already finished. + return; + } + + // We are using a fixed time step and a maximum number of iterations. + // The following post provides a lot of thoughts into how to build this + // loop: http://gafferongames.com/game-physics/fix-your-timestep/ + CGFloat TIMESTEP_MSEC = 1; + // Velocity is based on seconds instead of milliseconds + CGFloat step = TIMESTEP_MSEC / 1000; + + NSTimeInterval currentTime = CACurrentMediaTime(); + NSInteger numSteps = floorf((currentTime - _animationCurrentTime) / step); + _animationCurrentTime = currentTime; + if (numSteps == 0) { + return; + } + + CGFloat position = _lastPosition; + CGFloat velocity = _lastVelocity; + + CGFloat tempPosition = _lastPosition; + CGFloat tempVelocity = _lastVelocity; + + for (NSInteger i = 0; i < numSteps; ++i) { + // This is using RK4. A good blog post to understand how it works: + // http://gafferongames.com/game-physics/integration-basics/ + CGFloat aVelocity = velocity; + CGFloat aAcceleration = _tension * (_toValue - tempPosition) - _friction * tempVelocity; + tempPosition = position + aVelocity * step / 2; + tempVelocity = velocity + aAcceleration * step / 2; + + CGFloat bVelocity = tempVelocity; + CGFloat bAcceleration = _tension * (_toValue - tempPosition) - _friction * tempVelocity; + tempPosition = position + bVelocity * step / 2; + tempVelocity = velocity + bAcceleration * step / 2; + + CGFloat cVelocity = tempVelocity; + CGFloat cAcceleration = _tension * (_toValue - tempPosition) - _friction * tempVelocity; + tempPosition = position + cVelocity * step / 2; + tempVelocity = velocity + cAcceleration * step / 2; + + CGFloat dVelocity = tempVelocity; + CGFloat dAcceleration = _tension * (_toValue - tempPosition) - _friction * tempVelocity; + tempPosition = position + cVelocity * step / 2; + tempVelocity = velocity + cAcceleration * step / 2; + + CGFloat dxdt = (aVelocity + 2 * (bVelocity + cVelocity) + dVelocity) / 6; + CGFloat dvdt = (aAcceleration + 2 * (bAcceleration + cAcceleration) + dAcceleration) / 6; + + position += dxdt * step; + velocity += dvdt * step; + } + + _lastPosition = position; + _lastVelocity = velocity; + + [self onUpdate:position]; + + if (_animationHasFinished) { + return; + } + + // Conditions for stopping the spring animation + BOOL isOvershooting = NO; + if (_overshootClamping && _tension != 0) { + if (_fromValue < _toValue) { + isOvershooting = position > _toValue; + } else { + isOvershooting = position < _toValue; + } + } + BOOL isVelocity = ABS(velocity) <= _restSpeedThreshold; + BOOL isDisplacement = YES; + if (_tension != 0) { + isDisplacement = ABS(_toValue - position) <= _restDisplacementThreshold; + } + + if (isOvershooting || (isVelocity && isDisplacement)) { + if (_tension != 0) { + // Ensure that we end up with a round value + if (_animationHasFinished) { + return; + } + [self onUpdate:_toValue]; + } + + [self stopAnimation]; + } +} + +- (void)onUpdate:(CGFloat)outputValue +{ + _valueNode.value = outputValue; + [_valueNode setNeedsUpdate]; +} + +- (void)cleanupAnimationUpdate +{ + [_valueNode cleanupAnimationUpdate]; +} + +@end diff --git a/Libraries/NativeAnimation/RCTAnimation.xcodeproj/project.pbxproj b/Libraries/NativeAnimation/RCTAnimation.xcodeproj/project.pbxproj index b42e078f91c..657150b28a0 100644 --- a/Libraries/NativeAnimation/RCTAnimation.xcodeproj/project.pbxproj +++ b/Libraries/NativeAnimation/RCTAnimation.xcodeproj/project.pbxproj @@ -12,7 +12,6 @@ 13E501D41D07A644005F35D8 /* RCTViewPropertyMapper.m in Sources */ = {isa = PBXBuildFile; fileRef = 13E501C81D07A644005F35D8 /* RCTViewPropertyMapper.m */; }; 13E501E81D07A6C9005F35D8 /* RCTAdditionAnimatedNode.m in Sources */ = {isa = PBXBuildFile; fileRef = 13E501D71D07A6C9005F35D8 /* RCTAdditionAnimatedNode.m */; }; 13E501E91D07A6C9005F35D8 /* RCTAnimatedNode.m in Sources */ = {isa = PBXBuildFile; fileRef = 13E501D91D07A6C9005F35D8 /* RCTAnimatedNode.m */; }; - 13E501EA1D07A6C9005F35D8 /* RCTAnimationDriverNode.m in Sources */ = {isa = PBXBuildFile; fileRef = 13E501DB1D07A6C9005F35D8 /* RCTAnimationDriverNode.m */; }; 13E501EB1D07A6C9005F35D8 /* RCTInterpolationAnimatedNode.m in Sources */ = {isa = PBXBuildFile; fileRef = 13E501DD1D07A6C9005F35D8 /* RCTInterpolationAnimatedNode.m */; }; 13E501EC1D07A6C9005F35D8 /* RCTMultiplicationAnimatedNode.m in Sources */ = {isa = PBXBuildFile; fileRef = 13E501DF1D07A6C9005F35D8 /* RCTMultiplicationAnimatedNode.m */; }; 13E501ED1D07A6C9005F35D8 /* RCTPropsAnimatedNode.m in Sources */ = {isa = PBXBuildFile; fileRef = 13E501E11D07A6C9005F35D8 /* RCTPropsAnimatedNode.m */; }; @@ -27,7 +26,6 @@ 2D3B5EF61D9B0B4800451313 /* RCTDiffClampAnimatedNode.m in Sources */ = {isa = PBXBuildFile; fileRef = 193F64F31D776EC6004D1CAA /* RCTDiffClampAnimatedNode.m */; }; 2D3B5EF71D9B0B4800451313 /* RCTAdditionAnimatedNode.m in Sources */ = {isa = PBXBuildFile; fileRef = 13E501D71D07A6C9005F35D8 /* RCTAdditionAnimatedNode.m */; }; 2D3B5EF81D9B0B4800451313 /* RCTAnimatedNode.m in Sources */ = {isa = PBXBuildFile; fileRef = 13E501D91D07A6C9005F35D8 /* RCTAnimatedNode.m */; }; - 2D3B5EF91D9B0B4800451313 /* RCTAnimationDriverNode.m in Sources */ = {isa = PBXBuildFile; fileRef = 13E501DB1D07A6C9005F35D8 /* RCTAnimationDriverNode.m */; }; 2D3B5EFA1D9B0B4800451313 /* RCTInterpolationAnimatedNode.m in Sources */ = {isa = PBXBuildFile; fileRef = 13E501DD1D07A6C9005F35D8 /* RCTInterpolationAnimatedNode.m */; }; 2D3B5EFB1D9B0B4800451313 /* RCTModuloAnimatedNode.m in Sources */ = {isa = PBXBuildFile; fileRef = 94DAE3F81D7334A70059942F /* RCTModuloAnimatedNode.m */; }; 2D3B5EFC1D9B0B4800451313 /* RCTMultiplicationAnimatedNode.m in Sources */ = {isa = PBXBuildFile; fileRef = 13E501DF1D07A6C9005F35D8 /* RCTMultiplicationAnimatedNode.m */; }; @@ -36,6 +34,10 @@ 2D3B5EFF1D9B0B4800451313 /* RCTTransformAnimatedNode.m in Sources */ = {isa = PBXBuildFile; fileRef = 13E501E51D07A6C9005F35D8 /* RCTTransformAnimatedNode.m */; }; 2D3B5F001D9B0B4800451313 /* RCTValueAnimatedNode.m in Sources */ = {isa = PBXBuildFile; fileRef = 13E501E71D07A6C9005F35D8 /* RCTValueAnimatedNode.m */; }; 5C9894951D999639008027DB /* RCTDivisionAnimatedNode.m in Sources */ = {isa = PBXBuildFile; fileRef = 5C9894941D999639008027DB /* RCTDivisionAnimatedNode.m */; }; + 944244D01DB962DA0032A02B /* RCTFrameAnimation.m in Sources */ = {isa = PBXBuildFile; fileRef = 94C1294D1D4069170025F25C /* RCTFrameAnimation.m */; }; + 944244D11DB962DC0032A02B /* RCTSpringAnimation.m in Sources */ = {isa = PBXBuildFile; fileRef = 94C1294F1D4069170025F25C /* RCTSpringAnimation.m */; }; + 94C129511D40692B0025F25C /* RCTFrameAnimation.m in Sources */ = {isa = PBXBuildFile; fileRef = 94C1294D1D4069170025F25C /* RCTFrameAnimation.m */; }; + 94C129521D40692B0025F25C /* RCTSpringAnimation.m in Sources */ = {isa = PBXBuildFile; fileRef = 94C1294F1D4069170025F25C /* RCTSpringAnimation.m */; }; 94DAE3F91D7334A70059942F /* RCTModuloAnimatedNode.m in Sources */ = {isa = PBXBuildFile; fileRef = 94DAE3F81D7334A70059942F /* RCTModuloAnimatedNode.m */; }; /* End PBXBuildFile section */ @@ -72,8 +74,6 @@ 13E501D71D07A6C9005F35D8 /* RCTAdditionAnimatedNode.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RCTAdditionAnimatedNode.m; sourceTree = ""; }; 13E501D81D07A6C9005F35D8 /* RCTAnimatedNode.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RCTAnimatedNode.h; sourceTree = ""; }; 13E501D91D07A6C9005F35D8 /* RCTAnimatedNode.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RCTAnimatedNode.m; sourceTree = ""; }; - 13E501DA1D07A6C9005F35D8 /* RCTAnimationDriverNode.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RCTAnimationDriverNode.h; sourceTree = ""; }; - 13E501DB1D07A6C9005F35D8 /* RCTAnimationDriverNode.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RCTAnimationDriverNode.m; sourceTree = ""; }; 13E501DC1D07A6C9005F35D8 /* RCTInterpolationAnimatedNode.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RCTInterpolationAnimatedNode.h; sourceTree = ""; }; 13E501DD1D07A6C9005F35D8 /* RCTInterpolationAnimatedNode.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RCTInterpolationAnimatedNode.m; sourceTree = ""; }; 13E501DE1D07A6C9005F35D8 /* RCTMultiplicationAnimatedNode.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RCTMultiplicationAnimatedNode.h; sourceTree = ""; }; @@ -91,6 +91,11 @@ 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 = ""; }; + 94C1294A1D4069170025F25C /* RCTAnimationDriver.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = RCTAnimationDriver.h; sourceTree = ""; }; + 94C1294C1D4069170025F25C /* RCTFrameAnimation.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = RCTFrameAnimation.h; sourceTree = ""; }; + 94C1294D1D4069170025F25C /* RCTFrameAnimation.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = RCTFrameAnimation.m; sourceTree = ""; }; + 94C1294E1D4069170025F25C /* RCTSpringAnimation.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = RCTSpringAnimation.h; sourceTree = ""; }; + 94C1294F1D4069170025F25C /* RCTSpringAnimation.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = RCTSpringAnimation.m; sourceTree = ""; }; 94DAE3F71D7334A70059942F /* RCTModuloAnimatedNode.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RCTModuloAnimatedNode.h; sourceTree = ""; }; 94DAE3F81D7334A70059942F /* RCTModuloAnimatedNode.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RCTModuloAnimatedNode.m; sourceTree = ""; }; /* End PBXFileReference section */ @@ -132,8 +137,6 @@ 13E501D71D07A6C9005F35D8 /* RCTAdditionAnimatedNode.m */, 13E501D81D07A6C9005F35D8 /* RCTAnimatedNode.h */, 13E501D91D07A6C9005F35D8 /* RCTAnimatedNode.m */, - 13E501DA1D07A6C9005F35D8 /* RCTAnimationDriverNode.h */, - 13E501DB1D07A6C9005F35D8 /* RCTAnimationDriverNode.m */, 13E501DC1D07A6C9005F35D8 /* RCTInterpolationAnimatedNode.h */, 13E501DD1D07A6C9005F35D8 /* RCTInterpolationAnimatedNode.m */, 94DAE3F71D7334A70059942F /* RCTModuloAnimatedNode.h */, @@ -161,12 +164,25 @@ 13E501C81D07A644005F35D8 /* RCTViewPropertyMapper.m */, 13E501BD1D07A644005F35D8 /* RCTNativeAnimatedModule.h */, 13E501BE1D07A644005F35D8 /* RCTNativeAnimatedModule.m */, + 94C129491D4069170025F25C /* Drivers */, 13E501D51D07A6C9005F35D8 /* Nodes */, 134814211AA4EA7D00B7C361 /* Products */, 2D2A28201D9B03D100D4039D /* libRCTAnimation-tvOS.a */, ); sourceTree = ""; }; + 94C129491D4069170025F25C /* Drivers */ = { + isa = PBXGroup; + children = ( + 94C1294A1D4069170025F25C /* RCTAnimationDriver.h */, + 94C1294C1D4069170025F25C /* RCTFrameAnimation.h */, + 94C1294D1D4069170025F25C /* RCTFrameAnimation.m */, + 94C1294E1D4069170025F25C /* RCTSpringAnimation.h */, + 94C1294F1D4069170025F25C /* RCTSpringAnimation.m */, + ); + path = Drivers; + sourceTree = ""; + }; /* End PBXGroup section */ /* Begin PBXNativeTarget section */ @@ -255,11 +271,12 @@ 2D3B5EF81D9B0B4800451313 /* RCTAnimatedNode.m in Sources */, 2D3B5EFE1D9B0B4800451313 /* RCTStyleAnimatedNode.m in Sources */, 2D3B5EFA1D9B0B4800451313 /* RCTInterpolationAnimatedNode.m in Sources */, - 2D3B5EF91D9B0B4800451313 /* RCTAnimationDriverNode.m in Sources */, 2D3B5EFF1D9B0B4800451313 /* RCTTransformAnimatedNode.m in Sources */, 2D3B5EFC1D9B0B4800451313 /* RCTMultiplicationAnimatedNode.m in Sources */, 2D3B5EFD1D9B0B4800451313 /* RCTPropsAnimatedNode.m in Sources */, 2D3B5EF31D9B0B3400451313 /* RCTViewPropertyMapper.m in Sources */, + 944244D01DB962DA0032A02B /* RCTFrameAnimation.m in Sources */, + 944244D11DB962DC0032A02B /* RCTSpringAnimation.m in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -267,6 +284,8 @@ isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( + 94C129511D40692B0025F25C /* RCTFrameAnimation.m in Sources */, + 94C129521D40692B0025F25C /* RCTSpringAnimation.m in Sources */, 13E501F01D07A6C9005F35D8 /* RCTValueAnimatedNode.m in Sources */, 94DAE3F91D7334A70059942F /* RCTModuloAnimatedNode.m in Sources */, 193F64F41D776EC6004D1CAA /* RCTDiffClampAnimatedNode.m in Sources */, @@ -279,7 +298,6 @@ 13E501EB1D07A6C9005F35D8 /* RCTInterpolationAnimatedNode.m in Sources */, 13E501E81D07A6C9005F35D8 /* RCTAdditionAnimatedNode.m in Sources */, 5C9894951D999639008027DB /* RCTDivisionAnimatedNode.m in Sources */, - 13E501EA1D07A6C9005F35D8 /* RCTAnimationDriverNode.m in Sources */, 13E501EF1D07A6C9005F35D8 /* RCTTransformAnimatedNode.m in Sources */, 13E501D41D07A644005F35D8 /* RCTViewPropertyMapper.m in Sources */, ); diff --git a/Libraries/NativeAnimation/RCTNativeAnimatedModule.m b/Libraries/NativeAnimation/RCTNativeAnimatedModule.m index e88db8ade49..5c76c10e462 100644 --- a/Libraries/NativeAnimation/RCTNativeAnimatedModule.m +++ b/Libraries/NativeAnimation/RCTNativeAnimatedModule.m @@ -9,7 +9,9 @@ #import "RCTNativeAnimatedModule.h" #import "RCTAdditionAnimatedNode.h" -#import "RCTAnimationDriverNode.h" +#import "RCTAnimationDriver.h" +#import "RCTFrameAnimation.h" +#import "RCTSpringAnimation.h" #import "RCTAnimationUtils.h" #import "RCTBridge.h" #import "RCTConvert.h" @@ -27,9 +29,9 @@ @implementation RCTNativeAnimatedModule { NSMutableDictionary *_animationNodes; - NSMutableDictionary *_animationDrivers; - NSMutableSet *_activeAnimations; - NSMutableSet *_finishedAnimations; + NSMutableDictionary> *_animationDrivers; + NSMutableSet> *_activeAnimations; + NSMutableSet> *_finishedAnimations; NSMutableSet *_updatedValueNodes; NSMutableSet *_propAnimationNodes; CADisplayLink *_displayLink; @@ -87,6 +89,7 @@ RCT_EXPORT_METHOD(createAnimatedNode:(nonnull NSNumber *)tag } RCTAnimatedNode *node = [[nodeClass alloc] initWithTag:tag config:config]; + _animationNodes[tag] = node; if ([node isKindOfClass:[RCTPropsAnimatedNode class]]) { @@ -129,24 +132,28 @@ RCT_EXPORT_METHOD(startAnimatingNode:(nonnull NSNumber *)animationId config:(NSDictionary *)config endCallback:(RCTResponseSenderBlock)callBack) { - if (RCT_DEBUG && ![config[@"type"] isEqual:@"frames"]) { + RCTValueAnimatedNode *valueNode = (RCTValueAnimatedNode *)_animationNodes[nodeTag]; + + NSString *type = config[@"type"]; + idanimationDriver; + + 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; } - NSTimeInterval delay = [RCTConvert double:config[@"delay"]]; - NSNumber *toValue = [RCTConvert NSNumber:config[@"toValue"]] ?: @1; - NSArray *frames = [RCTConvert NSNumberArray:config[@"frames"]]; - - RCTValueAnimatedNode *valueNode = (RCTValueAnimatedNode *)_animationNodes[nodeTag]; - - RCTAnimationDriverNode *animationDriver = - [[RCTAnimationDriverNode alloc] initWithId:animationId - delay:delay - toValue:toValue.doubleValue - frames:frames - forNode:valueNode - callBack:callBack]; [_activeAnimations addObject:animationDriver]; _animationDrivers[animationId] = animationDriver; [animationDriver startAnimation]; @@ -155,7 +162,7 @@ RCT_EXPORT_METHOD(startAnimatingNode:(nonnull NSNumber *)animationId RCT_EXPORT_METHOD(stopAnimation:(nonnull NSNumber *)animationId) { - RCTAnimationDriverNode *driver = _animationDrivers[animationId]; + iddriver = _animationDrivers[animationId]; if (driver) { [driver removeAnimation]; [_animationDrivers removeObjectForKey:animationId]; @@ -172,6 +179,7 @@ RCT_EXPORT_METHOD(setAnimatedNodeValue:(nonnull NSNumber *)nodeTag RCTLogError(@"Not a value node."); return; } + RCTValueAnimatedNode *valueNode = (RCTValueAnimatedNode *)node; valueNode.value = value.floatValue; [valueNode setNeedsUpdate]; @@ -272,7 +280,7 @@ RCT_EXPORT_METHOD(stopListeningToAnimatedNodeValue:(nonnull NSNumber *)tag) { // Step Current active animations // This also recursively marks children nodes as needing update - for (RCTAnimationDriverNode *animationDriver in _activeAnimations) { + for (idanimationDriver in _activeAnimations) { [animationDriver stepAnimation]; } @@ -283,7 +291,7 @@ RCT_EXPORT_METHOD(stopListeningToAnimatedNodeValue:(nonnull NSNumber *)tag) } // Cleanup nodes and prepare for next cycle. Remove updated nodes from bucket. - for (RCTAnimationDriverNode *driverNode in _activeAnimations) { + for (iddriverNode in _activeAnimations) { [driverNode cleanupAnimationUpdate]; } for (RCTValueAnimatedNode *valueNode in _updatedValueNodes) { @@ -291,13 +299,13 @@ RCT_EXPORT_METHOD(stopListeningToAnimatedNodeValue:(nonnull NSNumber *)tag) } [_updatedValueNodes removeAllObjects]; - for (RCTAnimationDriverNode *driverNode in _activeAnimations) { + for (iddriverNode in _activeAnimations) { if (driverNode.animationHasFinished) { [driverNode removeAnimation]; [_finishedAnimations addObject:driverNode]; } } - for (RCTAnimationDriverNode *driverNode in _finishedAnimations) { + for (iddriverNode in _finishedAnimations) { [_activeAnimations removeObject:driverNode]; [_animationDrivers removeObjectForKey:driverNode.animationId]; }