From b8fafb46c1ea1ecfa75b88cd1b494a7720afb046 Mon Sep 17 00:00:00 2001 From: Krzysztof Magiera Date: Thu, 20 Jul 2017 14:16:06 -0700 Subject: [PATCH] Stop native driver animations when value is set. Summary: This diff changes the behaviour of natively driven animations in case the node that they are being run for has its value changed using `setValue` or as a result of an incoming event. The reason for changing that is to match the JS implementation of `setValue` which behaves as described above (see relevant code here: https://github.com/facebook/react-native/blob/7cdd4d48c89ad642a1c107e0b40c25eb75682175/Libraries/Animated/src/AnimatedImplementation.js#L743) **Test Plan:** Use this sample app: https://snack.expo.io/B1V7RX9r- Change: `USE_NATIVE_DRIVER` const between `true` and `false`. See the animation stops regardless of the state of `USE_NATIVE_DRIVER` unlike before when it would stop only when `USE_NATIVE_DRIVER` was set to `false` Closes https://github.com/facebook/react-native/pull/15054 Differential Revision: D5463750 Pulled By: hramos fbshipit-source-id: e164c5299588ba8cac2937260c9ba9f6053b04e5 --- .../RCTNativeAnimatedNodesManager.m | 16 ++++++++++++++ .../animated/NativeAnimatedNodesManager.java | 21 ++++++++++++++++++- 2 files changed, 36 insertions(+), 1 deletion(-) diff --git a/Libraries/NativeAnimation/RCTNativeAnimatedNodesManager.m b/Libraries/NativeAnimation/RCTNativeAnimatedNodesManager.m index 2eed4b0a617..e69b2d5829e 100644 --- a/Libraries/NativeAnimation/RCTNativeAnimatedNodesManager.m +++ b/Libraries/NativeAnimation/RCTNativeAnimatedNodesManager.m @@ -170,6 +170,7 @@ RCTLogError(@"Not a value node."); return; } + [self stopAnimationsForNode:node]; RCTValueAnimatedNode *valueNode = (RCTValueAnimatedNode *)node; valueNode.value = value.floatValue; @@ -264,6 +265,20 @@ } } +- (void)stopAnimationsForNode:(nonnull RCTAnimatedNode *)node +{ + NSMutableArray> *discarded = [NSMutableArray new]; + for (id driver in _activeAnimations) { + if ([driver.valueNode isEqual:node]) { + [discarded addObject:driver]; + } + } + for (id driver in discarded) { + [driver stopAnimation]; + [_activeAnimations removeObject:driver]; + } +} + #pragma mark -- Events - (void)addAnimatedEventToView:(nonnull NSNumber *)viewTag @@ -328,6 +343,7 @@ NSMutableArray *driversForKey = _eventDrivers[key]; if (driversForKey) { for (RCTEventAnimation *driver in driversForKey) { + [self stopAnimationsForNode:driver.valueNode]; [driver updateWithEvent:event]; } diff --git a/ReactAndroid/src/main/java/com/facebook/react/animated/NativeAnimatedNodesManager.java b/ReactAndroid/src/main/java/com/facebook/react/animated/NativeAnimatedNodesManager.java index 6251b8f4dd5..9ed40b82368 100644 --- a/ReactAndroid/src/main/java/com/facebook/react/animated/NativeAnimatedNodesManager.java +++ b/ReactAndroid/src/main/java/com/facebook/react/animated/NativeAnimatedNodesManager.java @@ -29,7 +29,6 @@ import com.facebook.react.uimanager.events.EventDispatcherListener; import java.util.ArrayDeque; import java.util.ArrayList; -import java.util.Collections; import java.util.HashMap; import java.util.LinkedList; import java.util.List; @@ -145,6 +144,7 @@ import javax.annotation.Nullable; throw new JSApplicationIllegalArgumentException("Animated node with tag " + tag + " does not exists or is not a 'value' node"); } + stopAnimationsForNode(node); ((ValueAnimatedNode) node).mValue = value; mUpdatedNodes.put(tag, node); } @@ -208,6 +208,24 @@ import javax.annotation.Nullable; mActiveAnimations.put(animationId, animation); } + private void stopAnimationsForNode(AnimatedNode animatedNode) { + // in most of the cases there should never be more than a few active animations running at the + // same time. Therefore it does not make much sense to create an animationId -> animation + // object map that would require additional memory just to support the use-case of stopping + // an animation + for (int i = 0; i < mActiveAnimations.size(); i++) { + AnimationDriver animation = mActiveAnimations.valueAt(i); + if (animatedNode.equals(animation.mAnimatedValue)) { + // Invoke animation end callback with {finished: false} + WritableMap endCallbackResponse = Arguments.createMap(); + endCallbackResponse.putBoolean("finished", false); + animation.mEndCallback.invoke(endCallbackResponse); + mActiveAnimations.removeAt(i); + i--; + } + } + } + public void stopAnimation(int animationId) { // in most of the cases there should never be more than a few active animations running at the // same time. Therefore it does not make much sense to create an animationId -> animation @@ -362,6 +380,7 @@ import javax.annotation.Nullable; List driversForKey = mEventDrivers.get(event.getViewTag() + eventName); if (driversForKey != null) { for (EventAnimationDriver driver : driversForKey) { + stopAnimationsForNode(driver.mValueNode); event.dispatch(driver); mRunUpdateNodeList.add(driver.mValueNode); }