From f81aa001247289b8e3f2718d265088df8b17248c Mon Sep 17 00:00:00 2001 From: Sharon Zheng Date: Thu, 30 Mar 2023 22:31:54 -0700 Subject: [PATCH] return final animation values to JS when animation completes (#36731) Summary: Pull Request resolved: https://github.com/facebook/react-native/pull/36731 When using the native driver for animations that involve layout changes (ie. translateY and other transforms, but not styles such as opacity), because it bypasses Fabric, the new coordinates are not updated so the Pressability responder region/tap target is incorrect **This diff:** - Returning the final values from the native side, at the same place it sets the "finished" flag. This gets sent to JS in `animated/animations/Animation.js`. In this diff I'm passing the value when 'hasFinishedAnimations' is true, but in a follow up diff I will look into other cases such as cancelled animations Next: 2. Update the Animated.Value to reflect the new values 3. Call the onEnd function if there is one set 4. Use `setNativeProps` to pass the new values down for layout calculations and the correct Pressability responder region Changelog: [General][Changed] - return animated values to JS for natively driven animations Differential Revision: D44110833 fbshipit-source-id: 3418aed9ae724d33dcedb0e678a7e5b4c89ca65e --- .../Libraries/Animated/AnimatedImplementation.js | 2 +- .../Libraries/Animated/NativeAnimatedModule.js | 2 +- .../Libraries/Animated/NativeAnimatedTurboModule.js | 2 +- .../Libraries/Animated/animations/Animation.js | 2 +- .../react/animated/NativeAnimatedNodesManager.java | 9 ++++----- 5 files changed, 8 insertions(+), 9 deletions(-) diff --git a/packages/react-native/Libraries/Animated/AnimatedImplementation.js b/packages/react-native/Libraries/Animated/AnimatedImplementation.js index 7e25c65f2a461e..e516115733360b 100644 --- a/packages/react-native/Libraries/Animated/AnimatedImplementation.js +++ b/packages/react-native/Libraries/Animated/AnimatedImplementation.js @@ -378,7 +378,7 @@ const parallel = function ( } animations.forEach((animation, idx) => { - const cb = function (endResult: EndResult | {finished: boolean}) { + const cb = function (endResult: EndResult) { hasEnded[idx] = true; doneCount++; if (doneCount === animations.length) { diff --git a/packages/react-native/Libraries/Animated/NativeAnimatedModule.js b/packages/react-native/Libraries/Animated/NativeAnimatedModule.js index 9fc932e6b509cc..50f5b7205dce44 100644 --- a/packages/react-native/Libraries/Animated/NativeAnimatedModule.js +++ b/packages/react-native/Libraries/Animated/NativeAnimatedModule.js @@ -12,7 +12,7 @@ import type {TurboModule} from '../TurboModule/RCTExport'; import * as TurboModuleRegistry from '../TurboModule/TurboModuleRegistry'; -type EndResult = {finished: boolean, ...}; +type EndResult = {finished: boolean, value?: number, ...}; type EndCallback = (result: EndResult) => void; type SaveValueCallback = (value: number) => void; diff --git a/packages/react-native/Libraries/Animated/NativeAnimatedTurboModule.js b/packages/react-native/Libraries/Animated/NativeAnimatedTurboModule.js index 58664ca8742173..77bebd3752bb27 100644 --- a/packages/react-native/Libraries/Animated/NativeAnimatedTurboModule.js +++ b/packages/react-native/Libraries/Animated/NativeAnimatedTurboModule.js @@ -12,7 +12,7 @@ import type {TurboModule} from '../TurboModule/RCTExport'; import * as TurboModuleRegistry from '../TurboModule/TurboModuleRegistry'; -type EndResult = {finished: boolean, ...}; +type EndResult = {finished: boolean, value?: number, ...}; type EndCallback = (result: EndResult) => void; type SaveValueCallback = (value: number) => void; diff --git a/packages/react-native/Libraries/Animated/animations/Animation.js b/packages/react-native/Libraries/Animated/animations/Animation.js index f3d2ac6d3ac234..d71a86b5137d7d 100644 --- a/packages/react-native/Libraries/Animated/animations/Animation.js +++ b/packages/react-native/Libraries/Animated/animations/Animation.js @@ -15,7 +15,7 @@ import type AnimatedValue from '../nodes/AnimatedValue'; import NativeAnimatedHelper from '../NativeAnimatedHelper'; -export type EndResult = {finished: boolean, ...}; +export type EndResult = {finished: boolean, value?: number, ...}; export type EndCallback = (result: EndResult) => void; export type AnimationConfig = { diff --git a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/animated/NativeAnimatedNodesManager.java b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/animated/NativeAnimatedNodesManager.java index 7ccfe4c2fece28..36c438b5fe08b1 100644 --- a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/animated/NativeAnimatedNodesManager.java +++ b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/animated/NativeAnimatedNodesManager.java @@ -640,17 +640,16 @@ public void runUpdates(long frameTimeNanos) { for (int i = mActiveAnimations.size() - 1; i >= 0; i--) { AnimationDriver animation = mActiveAnimations.valueAt(i); if (animation.mHasFinished) { + WritableMap params = Arguments.createMap(); + params.putBoolean("finished", true); + params.putDouble("value", animation.mAnimatedValue.mValue); if (animation.mEndCallback != null) { - WritableMap endCallbackResponse = Arguments.createMap(); - endCallbackResponse.putBoolean("finished", true); - animation.mEndCallback.invoke(endCallbackResponse); + animation.mEndCallback.invoke(params); } else if (mReactApplicationContext != null) { // If no callback is passed in, this /may/ be an animation set up by the single-op // instruction from JS, meaning that no jsi::functions are passed into native and // we communicate via RCTDeviceEventEmitter instead of callbacks. - WritableMap params = Arguments.createMap(); params.putInt("animationId", animation.mId); - params.putBoolean("finished", true); mReactApplicationContext.emitDeviceEvent( "onNativeAnimatedModuleAnimationFinished", params); }