mirror of
https://github.com/facebook/react-native.git
synced 2025-11-01 09:14:26 +00:00
React to onUserDrivenAnimationEnded event in JS (#45414)
Summary: Pull Request resolved: https://github.com/facebook/react-native/pull/45414 This change completes the fix for broken pressable when animations were applied to components with native driven animations. When creating the AnimatedProps, if they are natively drive animation, we look for the AnimatedValue involved and we register a listener. This is needed to make sure that the NativeModule will send te updated value upon calling the `update` function. Then, when observing the props lifecycle, it register a listener to the new `OnUserAnimationEnded` event, fired by the NativeAnimation module. When the `OnUserAnimationEnded` event is fired, the AnimatedProps will update the props that depends on the user driven animation. ## Changelog [General][Fixed] - reallign the shadow tree and the native tree when the user finishes interacting with the app. Reviewed By: sammy-SC Differential Revision: D59681428 fbshipit-source-id: c6690c41ea6d5517b7f8413e9dba1e12861a2400
This commit is contained in:
committed by
Facebook GitHub Bot
parent
076c28ced9
commit
afa887b622
@@ -10,12 +10,16 @@
|
||||
|
||||
'use strict';
|
||||
|
||||
import type {EventSubscription} from '../EventEmitter/NativeEventEmitter';
|
||||
|
||||
import * as ReactNativeFeatureFlags from '../../src/private/featureflags/ReactNativeFeatureFlags';
|
||||
import {isPublicInstance as isFabricPublicInstance} from '../ReactNative/ReactFabricPublicInstance/ReactFabricPublicInstanceUtils';
|
||||
import useRefEffect from '../Utilities/useRefEffect';
|
||||
import {AnimatedEvent} from './AnimatedEvent';
|
||||
import NativeAnimatedHelper from './NativeAnimatedHelper';
|
||||
import AnimatedNode from './nodes/AnimatedNode';
|
||||
import AnimatedProps from './nodes/AnimatedProps';
|
||||
import AnimatedValue from './nodes/AnimatedValue';
|
||||
import {
|
||||
useCallback,
|
||||
useEffect,
|
||||
@@ -32,6 +36,11 @@ type ReducedProps<TProps> = {
|
||||
};
|
||||
type CallbackRef<T> = T => mixed;
|
||||
|
||||
type AnimatedValueListeners = Array<{
|
||||
propValue: AnimatedValue,
|
||||
listenerId: string,
|
||||
}>;
|
||||
|
||||
export default function useAnimatedProps<TProps: {...}, TInstance>(
|
||||
props: TProps,
|
||||
): [ReducedProps<TProps>, CallbackRef<TInstance | null>] {
|
||||
@@ -152,6 +161,7 @@ export default function useAnimatedProps<TProps: {...}, TInstance>(
|
||||
|
||||
const target = getEventTarget(instance);
|
||||
const events = [];
|
||||
const animatedValueListeners: AnimatedValueListeners = [];
|
||||
|
||||
for (const propName in props) {
|
||||
// $FlowFixMe[invalid-computed-prop]
|
||||
@@ -159,6 +169,8 @@ export default function useAnimatedProps<TProps: {...}, TInstance>(
|
||||
if (propValue instanceof AnimatedEvent && propValue.__isNative) {
|
||||
propValue.__attach(target, propName);
|
||||
events.push([propName, propValue]);
|
||||
// $FlowFixMe[incompatible-call] - the `addListenersToPropsValue` drills down the propValue.
|
||||
addListenersToPropsValue(propValue, animatedValueListeners);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -168,6 +180,10 @@ export default function useAnimatedProps<TProps: {...}, TInstance>(
|
||||
for (const [propName, propValue] of events) {
|
||||
propValue.__detach(target, propName);
|
||||
}
|
||||
|
||||
for (const {propValue, listenerId} of animatedValueListeners) {
|
||||
propValue.removeListener(listenerId);
|
||||
}
|
||||
};
|
||||
},
|
||||
[
|
||||
@@ -182,9 +198,7 @@ export default function useAnimatedProps<TProps: {...}, TInstance>(
|
||||
return [reduceAnimatedProps<TProps>(node), callbackRef];
|
||||
}
|
||||
|
||||
function reduceAnimatedProps<TProps>(
|
||||
node: AnimatedProps,
|
||||
): ReducedProps<TProps> {
|
||||
function reduceAnimatedProps<TProps>(node: AnimatedNode): ReducedProps<TProps> {
|
||||
// Force `collapsable` to be false so that the native view is not flattened.
|
||||
// Flattened views cannot be accurately referenced by the native driver.
|
||||
return {
|
||||
@@ -193,6 +207,35 @@ function reduceAnimatedProps<TProps>(
|
||||
};
|
||||
}
|
||||
|
||||
function addListenersToPropsValue(
|
||||
propValue: AnimatedValue,
|
||||
accumulator: AnimatedValueListeners,
|
||||
) {
|
||||
// propValue can be a scalar value, an array or an object.
|
||||
if (propValue instanceof AnimatedValue) {
|
||||
const listenerId = propValue.addListener(() => {});
|
||||
accumulator.push({propValue, listenerId});
|
||||
} else if (Array.isArray(propValue)) {
|
||||
// An array can be an array of scalar values, arrays of arrays, or arrays of objects
|
||||
for (const prop of propValue) {
|
||||
addListenersToPropsValue(prop, accumulator);
|
||||
}
|
||||
} else if (propValue instanceof Object) {
|
||||
addAnimatedValuesListenersToProps(propValue, accumulator);
|
||||
}
|
||||
}
|
||||
|
||||
function addAnimatedValuesListenersToProps(
|
||||
props: AnimatedNode,
|
||||
accumulator: AnimatedValueListeners,
|
||||
) {
|
||||
for (const propName in props) {
|
||||
// $FlowFixMe[prop-missing] - This is an object contained in a prop, but we don't know the exact type.
|
||||
const propValue = props[propName];
|
||||
addListenersToPropsValue(propValue, accumulator);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Manages the lifecycle of the supplied `AnimatedProps` by invoking `__attach`
|
||||
* and `__detach`. However, this is more complicated because `AnimatedProps`
|
||||
@@ -203,12 +246,30 @@ function reduceAnimatedProps<TProps>(
|
||||
function useAnimatedPropsLifecycle_layoutEffects(node: AnimatedProps): void {
|
||||
const prevNodeRef = useRef<?AnimatedProps>(null);
|
||||
const isUnmountingRef = useRef<boolean>(false);
|
||||
const userDrivenAnimationEndedListener = useRef<?EventSubscription>(null);
|
||||
|
||||
useEffect(() => {
|
||||
// It is ok for multiple components to call `flushQueue` because it noops
|
||||
// if the queue is empty. When multiple animated components are mounted at
|
||||
// the same time. Only first component flushes the queue and the others will noop.
|
||||
NativeAnimatedHelper.API.flushQueue();
|
||||
|
||||
if (node.__isNative) {
|
||||
userDrivenAnimationEndedListener.current =
|
||||
NativeAnimatedHelper.nativeEventEmitter.addListener(
|
||||
'onUserDrivenAnimationEnded',
|
||||
data => {
|
||||
node.update();
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
return () => {
|
||||
if (userDrivenAnimationEndedListener.current) {
|
||||
userDrivenAnimationEndedListener.current?.remove();
|
||||
userDrivenAnimationEndedListener.current = null;
|
||||
}
|
||||
};
|
||||
});
|
||||
|
||||
useLayoutEffect(() => {
|
||||
|
||||
Reference in New Issue
Block a user