RCTFrameAnimation.m 5.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158
  1. /*
  2. * Copyright (c) Facebook, Inc. and its affiliates.
  3. *
  4. * This source code is licensed under the MIT license found in the
  5. * LICENSE file in the root directory of this source tree.
  6. */
  7. #import <React/RCTFrameAnimation.h>
  8. #import <UIKit/UIKit.h>
  9. #import <React/RCTConvert.h>
  10. #import <React/RCTDefines.h>
  11. #import <React/RCTAnimationUtils.h>
  12. #import <React/RCTValueAnimatedNode.h>
  13. @interface RCTFrameAnimation ()
  14. @property (nonatomic, strong) NSNumber *animationId;
  15. @property (nonatomic, strong) RCTValueAnimatedNode *valueNode;
  16. @property (nonatomic, assign) BOOL animationHasBegun;
  17. @property (nonatomic, assign) BOOL animationHasFinished;
  18. @end
  19. @implementation RCTFrameAnimation
  20. {
  21. NSArray<NSNumber *> *_frames;
  22. CGFloat _toValue;
  23. CGFloat _fromValue;
  24. CGFloat _lastPosition;
  25. NSTimeInterval _animationStartTime;
  26. NSTimeInterval _animationCurrentTime;
  27. RCTResponseSenderBlock _callback;
  28. NSInteger _iterations;
  29. NSInteger _currentLoop;
  30. }
  31. - (instancetype)initWithId:(NSNumber *)animationId
  32. config:(NSDictionary *)config
  33. forNode:(RCTValueAnimatedNode *)valueNode
  34. callBack:(nullable RCTResponseSenderBlock)callback
  35. {
  36. if ((self = [super init])) {
  37. _animationId = animationId;
  38. _lastPosition = _fromValue = valueNode.value;
  39. _valueNode = valueNode;
  40. _callback = [callback copy];
  41. [self resetAnimationConfig:config];
  42. }
  43. return self;
  44. }
  45. - (void)resetAnimationConfig:(NSDictionary *)config
  46. {
  47. NSNumber *toValue = [RCTConvert NSNumber:config[@"toValue"]] ?: @1;
  48. NSArray<NSNumber *> *frames = [RCTConvert NSNumberArray:config[@"frames"]];
  49. NSNumber *iterations = [RCTConvert NSNumber:config[@"iterations"]] ?: @1;
  50. _fromValue = _lastPosition;
  51. _toValue = toValue.floatValue;
  52. _frames = [frames copy];
  53. _animationStartTime = _animationCurrentTime = -1;
  54. _animationHasFinished = iterations.integerValue == 0;
  55. _iterations = iterations.integerValue;
  56. _currentLoop = 1;
  57. }
  58. RCT_NOT_IMPLEMENTED(- (instancetype)init)
  59. - (void)startAnimation
  60. {
  61. _animationStartTime = _animationCurrentTime = -1;
  62. _animationHasBegun = YES;
  63. }
  64. - (void)stopAnimation
  65. {
  66. _valueNode = nil;
  67. if (_callback) {
  68. _callback(@[@{
  69. @"finished": @(_animationHasFinished)
  70. }]);
  71. }
  72. }
  73. - (void)stepAnimationWithTime:(NSTimeInterval)currentTime
  74. {
  75. if (!_animationHasBegun || _animationHasFinished || _frames.count == 0) {
  76. // Animation has not begun or animation has already finished.
  77. return;
  78. }
  79. if (_animationStartTime == -1) {
  80. _animationStartTime = _animationCurrentTime = currentTime;
  81. }
  82. _animationCurrentTime = currentTime;
  83. NSTimeInterval currentDuration = (_animationCurrentTime - _animationStartTime) / RCTAnimationDragCoefficient();
  84. // Determine how many frames have passed since last update.
  85. // Get index of frames that surround the current interval
  86. NSUInteger startIndex = floor(currentDuration / RCTSingleFrameInterval);
  87. NSUInteger nextIndex = startIndex + 1;
  88. if (nextIndex >= _frames.count) {
  89. if (_iterations == -1 || _currentLoop < _iterations) {
  90. // Looping, reset to the first frame value.
  91. _animationStartTime = currentTime;
  92. _currentLoop++;
  93. NSNumber *firstValue = _frames.firstObject;
  94. [self updateOutputWithFrameOutput:firstValue.doubleValue];
  95. } else {
  96. _animationHasFinished = YES;
  97. // We are at the end of the animation
  98. // Update value and flag animation has ended.
  99. NSNumber *finalValue = _frames.lastObject;
  100. [self updateOutputWithFrameOutput:finalValue.doubleValue];
  101. }
  102. return;
  103. }
  104. // Do a linear remap of the two frames to safeguard against variable framerates
  105. NSNumber *fromFrameValue = _frames[startIndex];
  106. NSNumber *toFrameValue = _frames[nextIndex];
  107. NSTimeInterval fromInterval = startIndex * RCTSingleFrameInterval;
  108. NSTimeInterval toInterval = nextIndex * RCTSingleFrameInterval;
  109. // Interpolate between the individual frames to ensure the animations are
  110. //smooth and of the proper duration regardless of the framerate.
  111. CGFloat frameOutput = RCTInterpolateValue(currentDuration,
  112. fromInterval,
  113. toInterval,
  114. fromFrameValue.doubleValue,
  115. toFrameValue.doubleValue,
  116. EXTRAPOLATE_TYPE_EXTEND,
  117. EXTRAPOLATE_TYPE_EXTEND);
  118. [self updateOutputWithFrameOutput:frameOutput];
  119. }
  120. - (void)updateOutputWithFrameOutput:(CGFloat)frameOutput
  121. {
  122. CGFloat outputValue = RCTInterpolateValue(frameOutput,
  123. 0,
  124. 1,
  125. _fromValue,
  126. _toValue,
  127. EXTRAPOLATE_TYPE_EXTEND,
  128. EXTRAPOLATE_TYPE_EXTEND);
  129. _lastPosition = outputValue;
  130. _valueNode.value = outputValue;
  131. [_valueNode setNeedsUpdate];
  132. }
  133. @end