mirror of
https://github.com/facebook/react-native.git
synced 2025-11-01 09:14:26 +00:00
Add pan gesture animation examples to rntester (#50851)
Summary: Pull Request resolved: https://github.com/facebook/react-native/pull/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
This commit is contained in:
committed by
Facebook GitHub Bot
parent
42251ec0ed
commit
5ec00097b6
@@ -19,6 +19,7 @@ import EasingExample from './EasingExample';
|
||||
import FadeInViewExample from './FadeInViewExample';
|
||||
import LoopingExample from './LoopingExample';
|
||||
import MovingBoxExample from './MovingBoxExample';
|
||||
import PanGestureExample from './PanGestureExample';
|
||||
import PressabilityWithNativeDrivers from './PressabilityWithNativeDrivers';
|
||||
import RotatingImagesExample from './RotatingImagesExample';
|
||||
import TransformBounceExample from './TransformBounceExample';
|
||||
@@ -47,5 +48,6 @@ export default ({
|
||||
ContinuousInteractionsExample,
|
||||
CombineExample,
|
||||
PressabilityWithNativeDrivers,
|
||||
PanGestureExample,
|
||||
],
|
||||
}: RNTesterModule);
|
||||
|
||||
@@ -0,0 +1,256 @@
|
||||
/**
|
||||
* Copyright (c) Meta Platforms, Inc. and affiliates.
|
||||
*
|
||||
* This source code is licensed under the MIT license found in the
|
||||
* LICENSE file in the root directory of this source tree.
|
||||
*
|
||||
* @flow strict-local
|
||||
* @format
|
||||
* @oncall react_native
|
||||
*/
|
||||
|
||||
import type {RNTesterModuleExample} from '../../types/RNTesterTypes';
|
||||
|
||||
import ToggleNativeDriver from './utils/ToggleNativeDriver';
|
||||
import * as React from 'react';
|
||||
import {useLayoutEffect, useRef, useState} from 'react';
|
||||
import {
|
||||
Animated,
|
||||
Button,
|
||||
PanResponder,
|
||||
StyleSheet,
|
||||
Text,
|
||||
View,
|
||||
} from 'react-native';
|
||||
|
||||
function TextBox({children}: $ReadOnly<{children: React.Node}>): React.Node {
|
||||
// Prevent touch from being hijacked by Text
|
||||
return (
|
||||
<View pointerEvents="none">
|
||||
<Text style={styles.text}>{children}</Text>
|
||||
</View>
|
||||
);
|
||||
}
|
||||
|
||||
function AnimatedEventExample({
|
||||
containerPageXY,
|
||||
useNativeDriver,
|
||||
}: $ReadOnly<{
|
||||
containerPageXY: $ReadOnly<{x: number, y: number}>,
|
||||
useNativeDriver: boolean,
|
||||
}>): React.Node {
|
||||
const boxRef = useRef<?React.ElementRef<typeof Animated.View>>();
|
||||
|
||||
const pointerPageXY = useRef(
|
||||
new Animated.ValueXY(
|
||||
{
|
||||
x: containerPageXY.x,
|
||||
y: containerPageXY.y,
|
||||
},
|
||||
{useNativeDriver},
|
||||
),
|
||||
).current;
|
||||
|
||||
const dragStartOffsetXY = useRef(
|
||||
new Animated.ValueXY({x: 0, y: 0}, {useNativeDriver}),
|
||||
).current;
|
||||
|
||||
// We'll no longer have to subtract containerPageXY.x/y from offset, if we can animate left/top props natively
|
||||
// TODO: T222117268 Adopt fabric driven c++ native animated in RNTester
|
||||
const finalOffsetX = Animated.subtract(
|
||||
Animated.subtract(pointerPageXY.x, dragStartOffsetXY.x),
|
||||
containerPageXY.x,
|
||||
);
|
||||
const finalOffsetY = Animated.subtract(
|
||||
Animated.subtract(pointerPageXY.y, dragStartOffsetXY.y),
|
||||
containerPageXY.y,
|
||||
);
|
||||
|
||||
const syncAnimationToHostView = () => {
|
||||
boxRef.current?.setNativeProps({
|
||||
transform: [
|
||||
{translateX: finalOffsetX.__getValue()},
|
||||
{translateY: finalOffsetY.__getValue()},
|
||||
],
|
||||
});
|
||||
};
|
||||
|
||||
return (
|
||||
<Animated.View
|
||||
ref={boxRef}
|
||||
onTouchMove={Animated.event(
|
||||
[
|
||||
{
|
||||
nativeEvent: {
|
||||
pageX: pointerPageXY.x,
|
||||
pageY: pointerPageXY.y,
|
||||
},
|
||||
},
|
||||
],
|
||||
{useNativeDriver},
|
||||
)}
|
||||
onTouchStart={Animated.event(
|
||||
[
|
||||
{
|
||||
nativeEvent: {
|
||||
pageX: pointerPageXY.x,
|
||||
pageY: pointerPageXY.y,
|
||||
locationX: dragStartOffsetXY.x,
|
||||
locationY: dragStartOffsetXY.y,
|
||||
},
|
||||
},
|
||||
],
|
||||
{useNativeDriver},
|
||||
)}
|
||||
onTouchEnd={() => {
|
||||
// Animated change sometimes doesn't commit to Fabric, and box will jump back to offset before animation
|
||||
// This is to make sure that finalOffsetX/Y are synced to native host view
|
||||
// TODO: T222117268 Adopt fabric driven c++ native animated in RNTester
|
||||
syncAnimationToHostView();
|
||||
}}
|
||||
style={[
|
||||
styles.box,
|
||||
{
|
||||
backgroundColor: useNativeDriver ? 'orange' : 'violet',
|
||||
transform: [
|
||||
{
|
||||
translateX: finalOffsetX,
|
||||
},
|
||||
{
|
||||
translateY: finalOffsetY,
|
||||
},
|
||||
],
|
||||
},
|
||||
]}>
|
||||
<TextBox>Use {useNativeDriver ? 'Native' : 'JS'} Animated.event</TextBox>
|
||||
</Animated.View>
|
||||
);
|
||||
}
|
||||
|
||||
function PanResponderExample({
|
||||
useNativeDriver,
|
||||
}: $ReadOnly<{useNativeDriver: boolean}>): React.Node {
|
||||
const finalOffsetXY = useRef(
|
||||
new Animated.ValueXY({x: 0, y: 0}, {useNativeDriver}),
|
||||
).current;
|
||||
const dragStartOffsetXY = useRef({x: 0, y: 0}).current;
|
||||
const panResponder = useRef(
|
||||
PanResponder.create({
|
||||
onMoveShouldSetPanResponder: (pressEvent, gestureState) => {
|
||||
dragStartOffsetXY.x = finalOffsetXY.x.__getValue();
|
||||
dragStartOffsetXY.y = finalOffsetXY.y.__getValue();
|
||||
return true;
|
||||
},
|
||||
onPanResponderMove: (pressEvent, gestureState) => {
|
||||
if (gestureState.dx !== 0) {
|
||||
finalOffsetXY.x.setValue(dragStartOffsetXY.x + gestureState.dx);
|
||||
}
|
||||
|
||||
if (gestureState.dy !== 0) {
|
||||
finalOffsetXY.y.setValue(dragStartOffsetXY.y + gestureState.dy);
|
||||
}
|
||||
},
|
||||
}),
|
||||
).current;
|
||||
|
||||
return (
|
||||
<Animated.View
|
||||
{...panResponder.panHandlers}
|
||||
style={[
|
||||
styles.box,
|
||||
{
|
||||
backgroundColor: useNativeDriver ? 'pink' : 'cyan',
|
||||
transform: [
|
||||
{
|
||||
translateX: finalOffsetXY.x,
|
||||
},
|
||||
{
|
||||
translateY: finalOffsetXY.y,
|
||||
},
|
||||
],
|
||||
},
|
||||
]}>
|
||||
<TextBox>
|
||||
Use PanResponder{' '}
|
||||
{`+ ${useNativeDriver ? 'Native Animated value' : 'JS Animated value'}`}
|
||||
</TextBox>
|
||||
</Animated.View>
|
||||
);
|
||||
}
|
||||
|
||||
function PanGestureExample(): React.Node {
|
||||
const [busy, setBusy] = useState(false);
|
||||
const [useNativeDriver, setUseNativeDriver] = useState(false);
|
||||
|
||||
const containerRef = useRef<?React.ElementRef<typeof View>>();
|
||||
const [containerPageXY, setContainerPageXY] =
|
||||
useState<?{x: number, y: number}>(null);
|
||||
|
||||
useLayoutEffect(() => {
|
||||
containerRef.current?.measure((x, y, width, height, pageX, pageY) => {
|
||||
setContainerPageXY({x: pageX, y: pageY});
|
||||
});
|
||||
}, []);
|
||||
|
||||
function sleep(t: number) {
|
||||
setBusy(true);
|
||||
setTimeout(() => {
|
||||
const start = Date.now();
|
||||
while (Date.now() - start < t) {
|
||||
// sleeping
|
||||
}
|
||||
setBusy(false);
|
||||
}, 1000);
|
||||
}
|
||||
|
||||
return (
|
||||
<View style={styles.container}>
|
||||
<ToggleNativeDriver
|
||||
value={useNativeDriver}
|
||||
onValueChange={setUseNativeDriver}
|
||||
/>
|
||||
<Button
|
||||
title={busy ? 'js thread blocked...' : 'Block js thread for 5s'}
|
||||
onPress={() => {
|
||||
sleep(5000);
|
||||
}}
|
||||
/>
|
||||
<View style={styles.examplesContainer} ref={containerRef}>
|
||||
{containerPageXY != null ? (
|
||||
<AnimatedEventExample
|
||||
useNativeDriver={useNativeDriver}
|
||||
containerPageXY={containerPageXY}
|
||||
/>
|
||||
) : null}
|
||||
<PanResponderExample useNativeDriver={useNativeDriver} />
|
||||
</View>
|
||||
</View>
|
||||
);
|
||||
}
|
||||
|
||||
const styles = StyleSheet.create({
|
||||
container: {
|
||||
alignItems: 'stretch',
|
||||
justifyContent: 'flex-start',
|
||||
},
|
||||
examplesContainer: {
|
||||
flex: 1,
|
||||
},
|
||||
box: {
|
||||
width: 160,
|
||||
height: 160,
|
||||
alignItems: 'center',
|
||||
justifyContent: 'center',
|
||||
padding: 12,
|
||||
},
|
||||
text: {
|
||||
pointerEvents: 'none',
|
||||
},
|
||||
});
|
||||
|
||||
export default ({
|
||||
title: 'Pan Gesture',
|
||||
name: 'panGesture',
|
||||
description: 'Animations driven by pan gesture.',
|
||||
render: () => <PanGestureExample />,
|
||||
}: RNTesterModuleExample);
|
||||
Reference in New Issue
Block a user