Skip to content

Commit 5ec0009

Browse files
zeyapfacebook-github-bot
authored andcommitted
Add pan gesture animation examples to rntester (#50851)
Summary: Pull Request resolved: #50851 ## Changelog: [General] [Added] - Add pan gesture animation example to rntester Including examples of * using native driven Animated.event + touch event (which will not be interrupted by busy js thread, and is potentially a boost to performance) - the code requires some hacks but it's doable * using js PanResponder to drive pan gesture animation Reviewed By: sammy-SC Differential Revision: D68909931 fbshipit-source-id: 484ecb0646fb249b31362013725219a1c1ec6181
1 parent 42251ec commit 5ec0009

File tree

2 files changed

+258
-0
lines changed

2 files changed

+258
-0
lines changed

packages/rn-tester/js/examples/Animated/AnimatedIndex.js

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ import EasingExample from './EasingExample';
1919
import FadeInViewExample from './FadeInViewExample';
2020
import LoopingExample from './LoopingExample';
2121
import MovingBoxExample from './MovingBoxExample';
22+
import PanGestureExample from './PanGestureExample';
2223
import PressabilityWithNativeDrivers from './PressabilityWithNativeDrivers';
2324
import RotatingImagesExample from './RotatingImagesExample';
2425
import TransformBounceExample from './TransformBounceExample';
@@ -47,5 +48,6 @@ export default ({
4748
ContinuousInteractionsExample,
4849
CombineExample,
4950
PressabilityWithNativeDrivers,
51+
PanGestureExample,
5052
],
5153
}: RNTesterModule);
Lines changed: 256 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,256 @@
1+
/**
2+
* Copyright (c) Meta Platforms, Inc. and affiliates.
3+
*
4+
* This source code is licensed under the MIT license found in the
5+
* LICENSE file in the root directory of this source tree.
6+
*
7+
* @flow strict-local
8+
* @format
9+
* @oncall react_native
10+
*/
11+
12+
import type {RNTesterModuleExample} from '../../types/RNTesterTypes';
13+
14+
import ToggleNativeDriver from './utils/ToggleNativeDriver';
15+
import * as React from 'react';
16+
import {useLayoutEffect, useRef, useState} from 'react';
17+
import {
18+
Animated,
19+
Button,
20+
PanResponder,
21+
StyleSheet,
22+
Text,
23+
View,
24+
} from 'react-native';
25+
26+
function TextBox({children}: $ReadOnly<{children: React.Node}>): React.Node {
27+
// Prevent touch from being hijacked by Text
28+
return (
29+
<View pointerEvents="none">
30+
<Text style={styles.text}>{children}</Text>
31+
</View>
32+
);
33+
}
34+
35+
function AnimatedEventExample({
36+
containerPageXY,
37+
useNativeDriver,
38+
}: $ReadOnly<{
39+
containerPageXY: $ReadOnly<{x: number, y: number}>,
40+
useNativeDriver: boolean,
41+
}>): React.Node {
42+
const boxRef = useRef<?React.ElementRef<typeof Animated.View>>();
43+
44+
const pointerPageXY = useRef(
45+
new Animated.ValueXY(
46+
{
47+
x: containerPageXY.x,
48+
y: containerPageXY.y,
49+
},
50+
{useNativeDriver},
51+
),
52+
).current;
53+
54+
const dragStartOffsetXY = useRef(
55+
new Animated.ValueXY({x: 0, y: 0}, {useNativeDriver}),
56+
).current;
57+
58+
// We'll no longer have to subtract containerPageXY.x/y from offset, if we can animate left/top props natively
59+
// TODO: T222117268 Adopt fabric driven c++ native animated in RNTester
60+
const finalOffsetX = Animated.subtract(
61+
Animated.subtract(pointerPageXY.x, dragStartOffsetXY.x),
62+
containerPageXY.x,
63+
);
64+
const finalOffsetY = Animated.subtract(
65+
Animated.subtract(pointerPageXY.y, dragStartOffsetXY.y),
66+
containerPageXY.y,
67+
);
68+
69+
const syncAnimationToHostView = () => {
70+
boxRef.current?.setNativeProps({
71+
transform: [
72+
{translateX: finalOffsetX.__getValue()},
73+
{translateY: finalOffsetY.__getValue()},
74+
],
75+
});
76+
};
77+
78+
return (
79+
<Animated.View
80+
ref={boxRef}
81+
onTouchMove={Animated.event(
82+
[
83+
{
84+
nativeEvent: {
85+
pageX: pointerPageXY.x,
86+
pageY: pointerPageXY.y,
87+
},
88+
},
89+
],
90+
{useNativeDriver},
91+
)}
92+
onTouchStart={Animated.event(
93+
[
94+
{
95+
nativeEvent: {
96+
pageX: pointerPageXY.x,
97+
pageY: pointerPageXY.y,
98+
locationX: dragStartOffsetXY.x,
99+
locationY: dragStartOffsetXY.y,
100+
},
101+
},
102+
],
103+
{useNativeDriver},
104+
)}
105+
onTouchEnd={() => {
106+
// Animated change sometimes doesn't commit to Fabric, and box will jump back to offset before animation
107+
// This is to make sure that finalOffsetX/Y are synced to native host view
108+
// TODO: T222117268 Adopt fabric driven c++ native animated in RNTester
109+
syncAnimationToHostView();
110+
}}
111+
style={[
112+
styles.box,
113+
{
114+
backgroundColor: useNativeDriver ? 'orange' : 'violet',
115+
transform: [
116+
{
117+
translateX: finalOffsetX,
118+
},
119+
{
120+
translateY: finalOffsetY,
121+
},
122+
],
123+
},
124+
]}>
125+
<TextBox>Use {useNativeDriver ? 'Native' : 'JS'} Animated.event</TextBox>
126+
</Animated.View>
127+
);
128+
}
129+
130+
function PanResponderExample({
131+
useNativeDriver,
132+
}: $ReadOnly<{useNativeDriver: boolean}>): React.Node {
133+
const finalOffsetXY = useRef(
134+
new Animated.ValueXY({x: 0, y: 0}, {useNativeDriver}),
135+
).current;
136+
const dragStartOffsetXY = useRef({x: 0, y: 0}).current;
137+
const panResponder = useRef(
138+
PanResponder.create({
139+
onMoveShouldSetPanResponder: (pressEvent, gestureState) => {
140+
dragStartOffsetXY.x = finalOffsetXY.x.__getValue();
141+
dragStartOffsetXY.y = finalOffsetXY.y.__getValue();
142+
return true;
143+
},
144+
onPanResponderMove: (pressEvent, gestureState) => {
145+
if (gestureState.dx !== 0) {
146+
finalOffsetXY.x.setValue(dragStartOffsetXY.x + gestureState.dx);
147+
}
148+
149+
if (gestureState.dy !== 0) {
150+
finalOffsetXY.y.setValue(dragStartOffsetXY.y + gestureState.dy);
151+
}
152+
},
153+
}),
154+
).current;
155+
156+
return (
157+
<Animated.View
158+
{...panResponder.panHandlers}
159+
style={[
160+
styles.box,
161+
{
162+
backgroundColor: useNativeDriver ? 'pink' : 'cyan',
163+
transform: [
164+
{
165+
translateX: finalOffsetXY.x,
166+
},
167+
{
168+
translateY: finalOffsetXY.y,
169+
},
170+
],
171+
},
172+
]}>
173+
<TextBox>
174+
Use PanResponder{' '}
175+
{`+ ${useNativeDriver ? 'Native Animated value' : 'JS Animated value'}`}
176+
</TextBox>
177+
</Animated.View>
178+
);
179+
}
180+
181+
function PanGestureExample(): React.Node {
182+
const [busy, setBusy] = useState(false);
183+
const [useNativeDriver, setUseNativeDriver] = useState(false);
184+
185+
const containerRef = useRef<?React.ElementRef<typeof View>>();
186+
const [containerPageXY, setContainerPageXY] =
187+
useState<?{x: number, y: number}>(null);
188+
189+
useLayoutEffect(() => {
190+
containerRef.current?.measure((x, y, width, height, pageX, pageY) => {
191+
setContainerPageXY({x: pageX, y: pageY});
192+
});
193+
}, []);
194+
195+
function sleep(t: number) {
196+
setBusy(true);
197+
setTimeout(() => {
198+
const start = Date.now();
199+
while (Date.now() - start < t) {
200+
// sleeping
201+
}
202+
setBusy(false);
203+
}, 1000);
204+
}
205+
206+
return (
207+
<View style={styles.container}>
208+
<ToggleNativeDriver
209+
value={useNativeDriver}
210+
onValueChange={setUseNativeDriver}
211+
/>
212+
<Button
213+
title={busy ? 'js thread blocked...' : 'Block js thread for 5s'}
214+
onPress={() => {
215+
sleep(5000);
216+
}}
217+
/>
218+
<View style={styles.examplesContainer} ref={containerRef}>
219+
{containerPageXY != null ? (
220+
<AnimatedEventExample
221+
useNativeDriver={useNativeDriver}
222+
containerPageXY={containerPageXY}
223+
/>
224+
) : null}
225+
<PanResponderExample useNativeDriver={useNativeDriver} />
226+
</View>
227+
</View>
228+
);
229+
}
230+
231+
const styles = StyleSheet.create({
232+
container: {
233+
alignItems: 'stretch',
234+
justifyContent: 'flex-start',
235+
},
236+
examplesContainer: {
237+
flex: 1,
238+
},
239+
box: {
240+
width: 160,
241+
height: 160,
242+
alignItems: 'center',
243+
justifyContent: 'center',
244+
padding: 12,
245+
},
246+
text: {
247+
pointerEvents: 'none',
248+
},
249+
});
250+
251+
export default ({
252+
title: 'Pan Gesture',
253+
name: 'panGesture',
254+
description: 'Animations driven by pan gesture.',
255+
render: () => <PanGestureExample />,
256+
}: RNTesterModuleExample);

0 commit comments

Comments
 (0)