Skip to content

Commit cc09760

Browse files
authored
NativeDetector <-> Reanimated integration (#3617)
## Description This PR introduces integration with `Reanimated` to `NativeDetector`. When `Reanimated` is installed callbacks are handled via `useEvent` hook. Otherwise, they run on JS. It also creates `v3` directory (name can be changed later) that will be used in further development to separate new code from the old one. ## Test plan Tested on basic-example app.
1 parent 3f1ff42 commit cc09760

File tree

12 files changed

+592
-120
lines changed

12 files changed

+592
-120
lines changed

packages/react-native-gesture-handler/src/NativeDetector.tsx

Lines changed: 0 additions & 43 deletions
This file was deleted.

packages/react-native-gesture-handler/src/handlers/gestures/reanimatedWrapper.ts

Lines changed: 13 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,15 @@
11
import { ComponentClass } from 'react';
2-
import {
3-
GestureUpdateEvent,
4-
GestureStateChangeEvent,
5-
} from '../gestureHandlerCommon';
62
import { tagMessage } from '../../utils';
3+
import { UpdateEvent } from '../../v3/types';
74

85
export interface SharedValue<T> {
96
value: T;
107
}
118

9+
export type ReanimatedContext = {
10+
lastUpdateEvent: UpdateEvent<Record<string, unknown>> | undefined;
11+
};
12+
1213
let Reanimated:
1314
| {
1415
default: {
@@ -18,13 +19,18 @@ let Reanimated:
1819
options?: unknown
1920
): ComponentClass<P>;
2021
};
21-
useEvent: (
22-
callback: (event: GestureUpdateEvent | GestureStateChangeEvent) => void,
22+
useHandler: (handlers: Record<string, unknown>) => {
23+
doDependenciesDiffer: boolean;
24+
context: ReanimatedContext;
25+
};
26+
useEvent: <T>(
27+
callback: (event: T) => void,
2328
events: string[],
2429
rebuild: boolean
25-
) => unknown;
30+
) => (event: unknown) => void;
2631
useSharedValue: <T>(value: T) => SharedValue<T>;
2732
setGestureState: (handlerTag: number, newState: number) => void;
33+
isSharedValue: (value: unknown) => value is SharedValue<unknown>;
2834
}
2935
| undefined;
3036

packages/react-native-gesture-handler/src/index.ts

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -162,9 +162,9 @@ export type {
162162
} from './components/DrawerLayout';
163163
export { default as DrawerLayout } from './components/DrawerLayout';
164164

165-
export type { NativeDetectorProps } from './NativeDetector';
166-
export { NativeDetector } from './NativeDetector';
165+
export type { NativeDetectorProps } from './v3/NativeDetector';
166+
export { NativeDetector } from './v3/NativeDetector';
167167

168-
export * from './useGesture';
168+
export * from './v3/hooks/useGesture';
169169

170170
initialize();

packages/react-native-gesture-handler/src/useGesture.ts

Lines changed: 0 additions & 67 deletions
This file was deleted.
Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,65 @@
1+
import React from 'react';
2+
import { NativeGesture } from './hooks/useGesture';
3+
import { Reanimated } from '../handlers/gestures/reanimatedWrapper';
4+
5+
import { Animated, StyleSheet } from 'react-native';
6+
import RNGestureHandlerDetectorNativeComponent from '../specs/RNGestureHandlerDetectorNativeComponent';
7+
import { tagMessage } from '../utils';
8+
9+
export interface NativeDetectorProps {
10+
children?: React.ReactNode;
11+
gesture: NativeGesture;
12+
}
13+
14+
const AnimatedNativeDetector = Animated.createAnimatedComponent(
15+
RNGestureHandlerDetectorNativeComponent
16+
);
17+
18+
const ReanimatedNativeDetector = Reanimated?.default.createAnimatedComponent(
19+
RNGestureHandlerDetectorNativeComponent
20+
);
21+
22+
export function NativeDetector({ gesture, children }: NativeDetectorProps) {
23+
const NativeDetectorComponent = gesture.dispatchesAnimatedEvents
24+
? AnimatedNativeDetector
25+
: gesture.shouldUseReanimated
26+
? ReanimatedNativeDetector
27+
: RNGestureHandlerDetectorNativeComponent;
28+
29+
// It might happen only with ReanimatedNativeDetector
30+
if (!NativeDetectorComponent) {
31+
throw new Error(
32+
tagMessage(
33+
'Gesture expects to run on the UI thread, but failed to create the Reanimated NativeDetector.'
34+
)
35+
);
36+
}
37+
38+
return (
39+
<NativeDetectorComponent
40+
onGestureHandlerStateChange={
41+
gesture.gestureEvents.onGestureHandlerStateChange
42+
}
43+
onGestureHandlerEvent={gesture.gestureEvents.onGestureHandlerEvent}
44+
onGestureHandlerAnimatedEvent={
45+
gesture.gestureEvents.onGestureHandlerAnimatedEvent
46+
}
47+
onGestureHandlerTouchEvent={
48+
gesture.gestureEvents.onGestureHandlerTouchEvent
49+
}
50+
dispatchesAnimatedEvents={gesture.dispatchesAnimatedEvents}
51+
moduleId={globalThis._RNGH_MODULE_ID}
52+
handlerTags={[gesture.tag]}
53+
style={styles.detector}>
54+
{children}
55+
</NativeDetectorComponent>
56+
);
57+
}
58+
59+
const styles = StyleSheet.create({
60+
detector: {
61+
display: 'contents',
62+
// TODO: remove, debug info only
63+
backgroundColor: 'red',
64+
},
65+
});
Lines changed: 73 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,73 @@
1+
import { CALLBACK_TYPE } from '../../../handlers/gestures/gesture';
2+
import { isEventForHandlerWithTag, runWorkletCallback } from '../utils';
3+
import {
4+
Reanimated,
5+
ReanimatedContext,
6+
} from '../../../handlers/gestures/reanimatedWrapper';
7+
import { CallbackHandlers, UpdateEvent } from '../../types';
8+
import { tagMessage } from '../../../utils';
9+
10+
export function useGestureHandlerEvent(
11+
handlerTag: number,
12+
config: any,
13+
shouldUseReanimated: boolean
14+
) {
15+
const handlers: CallbackHandlers = {
16+
onChange: config.onChange,
17+
onUpdate: config.onUpdate,
18+
};
19+
20+
const onGestureHandlerEvent = (
21+
event: UpdateEvent<Record<string, unknown>>,
22+
context: ReanimatedContext | undefined
23+
) => {
24+
'worklet';
25+
26+
if (!isEventForHandlerWithTag(handlerTag, event)) {
27+
return;
28+
}
29+
30+
// This should never happen, but since we don't want to call hooks conditionally, we have to mark
31+
// context as possibly undefined to make TypeScript happy.
32+
if (!context) {
33+
throw new Error(tagMessage('Event handler context is not defined'));
34+
}
35+
36+
runWorkletCallback(
37+
CALLBACK_TYPE.UPDATE,
38+
handlers,
39+
config.changeEventCalculator
40+
? config.changeEventCalculator(event, context.lastUpdateEvent)
41+
: event
42+
);
43+
44+
// TODO: Investigate why this is always undefined
45+
context.lastUpdateEvent = event;
46+
};
47+
48+
const jsContext: ReanimatedContext = {
49+
lastUpdateEvent: undefined,
50+
};
51+
52+
if (config.disableReanimated) {
53+
return (event: UpdateEvent<Record<string, unknown>>) =>
54+
onGestureHandlerEvent(event, jsContext);
55+
}
56+
57+
// eslint-disable-next-line react-hooks/rules-of-hooks
58+
const reanimatedHandler = Reanimated?.useHandler(handlers);
59+
// eslint-disable-next-line react-hooks/rules-of-hooks
60+
const reanimatedEvent = Reanimated?.useEvent(
61+
(event: UpdateEvent<Record<string, unknown>>) => {
62+
'worklet';
63+
onGestureHandlerEvent(event, reanimatedHandler?.context);
64+
},
65+
['onGestureHandlerEvent'],
66+
!!reanimatedHandler?.doDependenciesDiffer
67+
);
68+
69+
return shouldUseReanimated
70+
? reanimatedEvent
71+
: (event: UpdateEvent<Record<string, unknown>>) =>
72+
onGestureHandlerEvent(event, jsContext);
73+
}
Lines changed: 80 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,80 @@
1+
import { CALLBACK_TYPE } from '../../../handlers/gestures/gesture';
2+
import {
3+
isEventForHandlerWithTag,
4+
isNativeEvent,
5+
runWorkletCallback,
6+
} from '../utils';
7+
import { State } from '../../../State';
8+
import { Reanimated } from '../../../handlers/gestures/reanimatedWrapper';
9+
import { CallbackHandlers, StateChangeEvent } from '../../types';
10+
11+
export function useGestureStateChangeEvent(
12+
handlerTag: number,
13+
config: any,
14+
shouldUseReanimated: boolean
15+
) {
16+
const handlers: CallbackHandlers = {
17+
onBegin: config.onBegin,
18+
onStart: config.onStart,
19+
onEnd: config.onEnd,
20+
onFinalize: config.onFinalize,
21+
};
22+
23+
const onGestureHandlerStateChange = (
24+
event: StateChangeEvent<Record<string, unknown>>
25+
) => {
26+
'worklet';
27+
28+
if (!isEventForHandlerWithTag(handlerTag, event)) {
29+
return;
30+
}
31+
32+
let oldState: State | undefined;
33+
let state: State | undefined;
34+
35+
if (isNativeEvent(event)) {
36+
oldState = event.nativeEvent.oldState;
37+
state = event.nativeEvent.state;
38+
} else {
39+
oldState = event.oldState;
40+
state = event.state;
41+
}
42+
43+
if (oldState === State.UNDETERMINED && state === State.BEGAN) {
44+
runWorkletCallback(CALLBACK_TYPE.BEGAN, handlers, event);
45+
} else if (
46+
(oldState === State.BEGAN || oldState === State.UNDETERMINED) &&
47+
state === State.ACTIVE
48+
) {
49+
runWorkletCallback(CALLBACK_TYPE.START, handlers, event);
50+
} else if (oldState !== state && state === State.END) {
51+
if (oldState === State.ACTIVE) {
52+
runWorkletCallback(CALLBACK_TYPE.END, handlers, event, true);
53+
}
54+
runWorkletCallback(CALLBACK_TYPE.FINALIZE, handlers, event, true);
55+
} else if (
56+
(state === State.FAILED || state === State.CANCELLED) &&
57+
state !== oldState
58+
) {
59+
if (oldState === State.ACTIVE) {
60+
runWorkletCallback(CALLBACK_TYPE.END, handlers, event, false);
61+
}
62+
runWorkletCallback(CALLBACK_TYPE.FINALIZE, handlers, event, false);
63+
}
64+
};
65+
66+
if (config.disableReanimated) {
67+
return onGestureHandlerStateChange;
68+
}
69+
70+
// eslint-disable-next-line react-hooks/rules-of-hooks
71+
const reanimatedHandler = Reanimated?.useHandler(handlers);
72+
// eslint-disable-next-line react-hooks/rules-of-hooks
73+
const reanimatedEvent = Reanimated?.useEvent(
74+
onGestureHandlerStateChange,
75+
['onGestureHandlerStateChange'],
76+
!!reanimatedHandler?.doDependenciesDiffer
77+
);
78+
79+
return shouldUseReanimated ? reanimatedEvent : onGestureHandlerStateChange;
80+
}

0 commit comments

Comments
 (0)