How to lock Y vs X depending on gesture with the new API? #2254
-
I have a I've searched online and can't find any clear description on how to make this work. Here is what I've found so far: #359 (I tried this, but did not seem to have any effect) I've also tried using composed gestures. For example, I passed a I've created a snack here for review: Also am posting the code here: import { useRef } from 'react';
import {
Dimensions,
Pressable,
StyleSheet,
Text,
SafeAreaView,
StatusBar
} from 'react-native';
import Animated, {
useAnimatedStyle,
useSharedValue,
withTiming,
withSpring,
SharedValue,
runOnJS
} from 'react-native-reanimated';
import {
GestureHandlerRootView,
GestureDetector,
Gesture,
ScrollView
} from 'react-native-gesture-handler';
const ITEMS = [
{ id: 1, text: 'Example 1' },
{ id: 2, text: 'Example 2' },
{ id: 3, text: 'Example 3' },
{ id: 4, text: 'Example 4' },
{ id: 5, text: 'Example 5' },
{ id: 6, text: 'Example 6' },
{ id: 7, text: 'Example 7' },
{ id: 8, text: 'Example 8' },
{ id: 9, text: 'Example 9' },
{ id: 10, text: 'Example 10' },
{ id: 11, text: 'Example 11' },
{ id: 12, text: 'Example 12' }
];
const { width: SCREEN_WIDTH } = Dimensions.get('window');
const ITEM_HEIGHT = 80;
const BUTTON_WIDTH = 100;
const TRANSLATE_X_THRESHOLD = -SCREEN_WIDTH * 0.7;
type Props = {
id: number;
text: string;
scrollRef: React.MutableRefObject<null>;
};
export const Item = ({ id, text, scrollRef }: Props) => {
const translateX = useSharedValue(0);
const contextX = useSharedValue(0);
const itemHeight = useSharedValue(ITEM_HEIGHT);
const swipeToDeleteGesture = Gesture.Pan()
.onBegin(() => {
contextX.value = translateX.value;
})
.onUpdate(event => {
switch (true) {
// Prevent swiping to the right
case event.translationX > 0:
translateX.value = withTiming(0);
break;
default:
translateX.value = event.translationX + contextX.value;
break;
}
})
.onEnd(event => {
const positionX = event.translationX + contextX.value;
switch (true) {
// If swiping to the right, close item
case event.translationX > 0:
translateX.value = withTiming(0);
break;
case positionX < TRANSLATE_X_THRESHOLD:
break;
case positionX > TRANSLATE_X_THRESHOLD && positionX < -BUTTON_WIDTH / 2:
translateX.value = withSpring(-BUTTON_WIDTH, {
damping: 50
});
break;
default:
translateX.value = withTiming(0, { duration: 500 });
break;
}
})
.simultaneousWithExternalGesture(scrollRef);
const animatedSliderStyle = useAnimatedStyle(() => {
return {
transform: [{ translateX: translateX.value }]
};
}, []);
return (
<Animated.View style={styles.container}>
<Pressable style={styles.button} onPress={() => console.log('Pressed')}>
<Text style={styles.buttonText}>Delete</Text>
</Pressable>
<GestureDetector gesture={swipeToDeleteGesture}>
<Animated.View style={[styles.slider, animatedSliderStyle]}>
<Text style={styles.sliderText}>{text}</Text>
</Animated.View>
</GestureDetector>
</Animated.View>
);
};
const App = () => {
const scrollRef = useRef(null);
return (
<GestureHandlerRootView style={{ flex: 1 }}>
<SafeAreaView style={{ flex: 1 }}>
<StatusBar barStyle="default" />
<ScrollView ref={scrollRef}>
{ITEMS.map(item => (
<Item key={item.id} scrollRef={scrollRef} {...item} />
))}
</ScrollView>
</SafeAreaView>
</GestureHandlerRootView>
);
};
const styles = StyleSheet.create({
container: {
backgroundColor: 'red'
},
slider: {
backgroundColor: '#fff',
borderColor: '#e1e1e1',
borderBottomWidth: 1,
height: ITEM_HEIGHT,
justifyContent: 'center',
paddingLeft: 20,
width: SCREEN_WIDTH
},
sliderText: {
fontSize: 18
},
button: {
alignItems: 'center',
height: '100%',
justifyContent: 'center',
position: 'absolute',
right: 0,
width: 100
},
buttonText: {
color: '#fff',
fontSize: 18
}
});
export default App; Would really appreciate any help with this. |
Beta Was this translation helpful? Give feedback.
Replies: 1 comment
-
I figured it out. The key was to use Here's the code in case anyone is interested: import { useRef } from 'react';
import {
Dimensions,
Pressable,
StyleSheet,
Text,
SafeAreaView,
StatusBar
} from 'react-native';
import Animated, {
useAnimatedStyle,
useSharedValue,
withTiming,
withSpring,
SharedValue,
runOnJS
} from 'react-native-reanimated';
import {
GestureHandlerRootView,
GestureDetector,
Gesture,
ScrollView
} from 'react-native-gesture-handler';
const ITEMS = [
{ id: 1, text: 'Example 1' },
{ id: 2, text: 'Example 2' },
{ id: 3, text: 'Example 3' },
{ id: 4, text: 'Example 4' },
{ id: 5, text: 'Example 5' },
{ id: 6, text: 'Example 6' },
{ id: 7, text: 'Example 7' },
{ id: 8, text: 'Example 8' },
{ id: 9, text: 'Example 9' },
{ id: 10, text: 'Example 10' },
{ id: 11, text: 'Example 11' },
{ id: 12, text: 'Example 12' }
];
const { width: SCREEN_WIDTH } = Dimensions.get('window');
const ITEM_HEIGHT = 80;
const BUTTON_WIDTH = 100;
const TRANSLATE_X_THRESHOLD = -SCREEN_WIDTH * 0.7;
type Props = {
id: number;
text: string;
panRef: React.MutableRefObject<any>;
};
export const Item = ({ id, text, panRef }: Props) => {
const translateX = useSharedValue(0);
const contextX = useSharedValue(0);
const itemHeight = useSharedValue(ITEM_HEIGHT);
const swipeToDeleteGesture = Gesture.Pan()
.onBegin(() => {
contextX.value = translateX.value;
})
.onUpdate(event => {
switch (true) {
// Prevent swiping to the right
case event.translationX > 0:
translateX.value = withTiming(0);
break;
default:
translateX.value = event.translationX + contextX.value;
break;
}
})
.onEnd(event => {
const positionX = event.translationX + contextX.value;
switch (true) {
// If swiping to the right, close item
case event.translationX > 0:
translateX.value = withTiming(0);
break;
case positionX < TRANSLATE_X_THRESHOLD:
break;
case positionX > TRANSLATE_X_THRESHOLD && positionX < -BUTTON_WIDTH / 2:
translateX.value = withSpring(-BUTTON_WIDTH, {
damping: 50
});
break;
default:
translateX.value = withTiming(0, { duration: 500 });
break;
}
})
.withRef(panRef)
.activeOffsetX([-20, 20]);
const animatedSliderStyle = useAnimatedStyle(() => {
return {
transform: [{ translateX: translateX.value }]
};
}, []);
return (
<Animated.View style={styles.container}>
<Pressable style={styles.button} onPress={() => console.log('Pressed')}>
<Text style={styles.buttonText}>Delete</Text>
</Pressable>
<GestureDetector gesture={swipeToDeleteGesture}>
<Animated.View style={[styles.slider, animatedSliderStyle]}>
<Text style={styles.sliderText}>{text}</Text>
</Animated.View>
</GestureDetector>
</Animated.View>
);
};
const App = () => {
const panRef = useRef(null);
return (
<GestureHandlerRootView style={{ flex: 1 }}>
<SafeAreaView style={{ flex: 1 }}>
<StatusBar barStyle="default" />
<ScrollView ref={panRef}>
{ITEMS.map(item => (
<Item key={item.id} panRef={panRef} {...item} />
))}
</ScrollView>
</SafeAreaView>
</GestureHandlerRootView>
);
};
const styles = StyleSheet.create({
container: {
backgroundColor: 'red'
},
slider: {
backgroundColor: '#fff',
borderColor: '#e1e1e1',
borderBottomWidth: 1,
height: ITEM_HEIGHT,
justifyContent: 'center',
paddingLeft: 20,
width: SCREEN_WIDTH
},
sliderText: {
fontSize: 18
},
button: {
alignItems: 'center',
height: '100%',
justifyContent: 'center',
position: 'absolute',
right: 0,
width: 100
},
buttonText: {
color: '#fff',
fontSize: 18
}
});
export default App; |
Beta Was this translation helpful? Give feedback.
I figured it out. The key was to use
withRef
andactiveOffsetX
.Here's the code in case anyone is interested: