Permalink
Browse files

Native Animated - Support Animated.loop on iOS

Summary:
Follow up to #11973 to add support to Animated.loop with useNativeDriver on iOS.

**Test plan**
Test with new UIExplorer example
Run unit tests
Closes #13359

Differential Revision: D4960754

Pulled By: javache

fbshipit-source-id: caa840281f1b060df7a2b1c50405fcae1e1b0de6
  • Loading branch information...
janicduplessis authored and facebook-github-bot committed May 26, 2017
1 parent 32d35c3 commit 11424a8bc63174380e7c696a2b34c191a94cdaba
@@ -34,6 +34,8 @@ @implementation RCTFrameAnimation
NSTimeInterval _animationStartTime;
NSTimeInterval _animationCurrentTime;
RCTResponseSenderBlock _callback;
NSInteger _iterations;
NSInteger _currentLoop;
}
- (instancetype)initWithId:(NSNumber *)animationId
@@ -44,13 +46,17 @@ - (instancetype)initWithId:(NSNumber *)animationId
if ((self = [super init])) {
NSNumber *toValue = [RCTConvert NSNumber:config[@"toValue"]] ?: @1;
NSArray<NSNumber *> *frames = [RCTConvert NSNumberArray:config[@"frames"]];
NSNumber *iterations = [RCTConvert NSNumber:config[@"iterations"]] ?: @1;
_animationId = animationId;
_toValue = toValue.floatValue;
_fromValue = valueNode.value;
_valueNode = valueNode;
_frames = [frames copy];
_callback = [callback copy];
_animationHasFinished = iterations.integerValue == 0;
_iterations = iterations.integerValue;
_currentLoop = 1;
}
return self;
}
@@ -93,11 +99,19 @@ - (void)stepAnimationWithTime:(NSTimeInterval)currentTime
NSUInteger nextIndex = startIndex + 1;
if (nextIndex >= _frames.count) {
// We are at the end of the animation
// Update value and flag animation has ended.
NSNumber *finalValue = _frames.lastObject;
[self updateOutputWithFrameOutput:finalValue.doubleValue];
_animationHasFinished = YES;
if (_iterations == -1 || _currentLoop < _iterations) {
// Looping, reset to the first frame value.
_animationStartTime = currentTime;
_currentLoop++;
NSNumber *firstValue = _frames.firstObject;
[self updateOutputWithFrameOutput:firstValue.doubleValue];
} else {
_animationHasFinished = YES;
// We are at the end of the animation
// Update value and flag animation has ended.
NSNumber *finalValue = _frames.lastObject;
[self updateOutputWithFrameOutput:finalValue.doubleValue];
}
return;
}
@@ -41,6 +41,9 @@ @implementation RCTSpringAnimation
CGFloat _lastPosition;
CGFloat _lastVelocity;
NSInteger _iterations;
NSInteger _currentLoop;
}
- (instancetype)initWithId:(NSNumber *)animationId
@@ -49,6 +52,8 @@ - (instancetype)initWithId:(NSNumber *)animationId
callBack:(nullable RCTResponseSenderBlock)callback
{
if ((self = [super init])) {
NSNumber *iterations = [RCTConvert NSNumber:config[@"iterations"]] ?: @1;
_animationId = animationId;
_toValue = [RCTConvert CGFloat:config[@"toValue"]];
_fromValue = valueNode.value;
@@ -63,6 +68,10 @@ - (instancetype)initWithId:(NSNumber *)animationId
_lastPosition = _fromValue;
_lastVelocity = _initialVelocity;
_animationHasFinished = iterations.integerValue == 0;
_iterations = iterations.integerValue;
_currentLoop = 1;
}
return self;
}
@@ -178,7 +187,14 @@ - (void)stepAnimationWithTime:(NSTimeInterval)currentTime
[self onUpdate:_toValue];
}
_animationHasFinished = YES;
if (_iterations == -1 || _currentLoop < _iterations) {
_lastPosition = _fromValue;
_lastVelocity = _initialVelocity;
_currentLoop++;
[self onUpdate:_fromValue];
} else {
_animationHasFinished = YES;
}
}
}
@@ -183,6 +183,37 @@ - (void)testFramesAnimation
[_uiManager verify];
}
- (void)testFramesAnimationLoop
{
[self createSimpleAnimatedView:@1000 withOpacity:0];
NSArray<NSNumber *> *frames = @[@0, @0.2, @0.4, @0.6, @0.8, @1];
[_nodesManager startAnimatingNode:@1
nodeTag:@1
config:@{@"type": @"frames", @"frames": frames, @"toValue": @1, @"iterations": @5}
endCallback:nil];
for (NSUInteger it = 0; it < 5; it++) {
for (NSNumber *frame in frames) {
[[_uiManager expect] synchronouslyUpdateViewOnUIThread:@1000
viewName:@"UIView"
props:RCTPropChecker(@"opacity", frame)];
[_nodesManager stepAnimations:_displayLink];
[_uiManager verify];
}
}
[[_uiManager expect] synchronouslyUpdateViewOnUIThread:@1000
viewName:@"UIView"
props:RCTPropChecker(@"opacity", @1)];
[_nodesManager stepAnimations:_displayLink];
[_uiManager verify];
[[_uiManager reject] synchronouslyUpdateViewOnUIThread:OCMOCK_ANY viewName:OCMOCK_ANY props:OCMOCK_ANY];
[_nodesManager stepAnimations:_displayLink];
[_uiManager verify];
}
- (void)testNodeValueListenerIfNotListening
{
NSNumber *nodeId = @1;
@@ -377,6 +408,62 @@ - (void)testDecayAnimationLoop
[_uiManager verify];
}
- (void)testSpringAnimationLoop
{
[self createSimpleAnimatedView:@1000 withOpacity:0];
[_nodesManager startAnimatingNode:@1
nodeTag:@1
config:@{@"type": @"spring",
@"iterations": @5,
@"friction": @7,
@"tension": @40,
@"initialVelocity": @0,
@"toValue": @1,
@"restSpeedThreshold": @0.001,
@"restDisplacementThreshold": @0.001,
@"overshootClamping": @NO}
endCallback:nil];
BOOL didComeToRest = NO;
CGFloat previousValue = 0;
NSUInteger numberOfResets = 0;
__block CGFloat currentValue;
[[[_uiManager stub] andDo:^(NSInvocation *invocation) {
__unsafe_unretained NSDictionary<NSString *, NSNumber *> *props;
[invocation getArgument:&props atIndex:4];
currentValue = props[@"opacity"].doubleValue;
}] synchronouslyUpdateViewOnUIThread:OCMOCK_ANY viewName:OCMOCK_ANY props:OCMOCK_ANY];
// Run for 3 seconds five times.
for (NSUInteger i = 0; i < 3 * 60 * 5; i++) {
[_nodesManager stepAnimations:_displayLink];
if (!didComeToRest) {
// Verify that animation step is relatively small.
XCTAssertLessThan(fabs(currentValue - previousValue), 0.1);
}
// Test to see if it reset after coming to rest
if (didComeToRest && currentValue == 0) {
didComeToRest = NO;
numberOfResets++;
}
// Record that the animation did come to rest when it rests on toValue.
didComeToRest = fabs(currentValue - 1) < 0.001 && fabs(currentValue - previousValue) < 0.001;
previousValue = currentValue;
}
// Verify that value reset 4 times after finishing a full animation and is currently resting.
XCTAssertEqual(numberOfResets, 4u);
XCTAssertTrue(didComeToRest);
[[_uiManager reject] synchronouslyUpdateViewOnUIThread:OCMOCK_ANY viewName:OCMOCK_ANY props:OCMOCK_ANY];
[_nodesManager stepAnimations:_displayLink];
[_uiManager verify];
}
- (void)testAnimationCallbackFinish
{
[self createSimpleAnimatedView:@1000 withOpacity:0];
Oops, something went wrong.

0 comments on commit 11424a8

Please sign in to comment.