React Native/Expo:如何在滑动时禁用按压操作(仿主流App)
解决横向ScrollView中Pressable滑动时触发按压状态的问题
问题重现
当前使用Pressable组件实现Tile的点击交互,在横向ScrollView中滑动时,触摸的Tile会先触发isPressed状态,滑动结束后才恢复,而期望的是仅快速点击时触发按压状态和导航,滑动时完全不触发按压反馈。
原ScrollView代码:
<ScrollView removeClippedSubviews={true} horizontal showsHorizontalScrollIndicator={false} my={5} > {parcour_list?.map(parcour => parcour.parcour ? ( <ParcourTile navigation={navigation} key={parcour.id} parcour={parcour} /> ) : null )} </ScrollView>
原ParcourTile代码:
<Pressable onPress={() => navigation.navigate('parcour', { parcour })}> {({ isPressed }) => ( <View mr={5} style={{ width: 150, backgroundColor: isPressed ? 'rgba(255,255,255,0.05)' : null, transform: [ { scale: isPressed ? 0.96 : 1 } ] }} > <Image cacheKey={parcour.name} style={{ width: 150, height: 150, borderRadius: 8 }} source={parcour.image} alt={parcour.name} /> <Heading color="white" size="xs" mt={2.5}> Hindernis {parcour.number} </Heading> <Heading color="white" size="md" mt={0.5}> {parcour.name.length > 20 ? `${parcour.name.slice(0, 23)}...` : parcour.name} </Heading> </View> )} </Pressable>
解决方案
方案1:使用React Native Gesture Handler(推荐)
利用手势识别库区分点击和滑动动作,确保滑动时不触发按压状态,点击时正常响应。
- 先安装依赖(如果未安装):
npm install react-native-gesture-handler # 或 yarn add react-native-gesture-handler
- 修改ParcourTile组件:
import { GestureDetector, Gesture } from 'react-native-gesture-handler'; import { useState } from 'react'; const ParcourTile = ({ navigation, parcour }) => { const [isPressed, setIsPressed] = useState(false); const [isDragging, setIsDragging] = useState(false); // 点击手势:仅在非滑动状态下触发按压和导航 const tapGesture = Gesture.Tap() .onStart(() => { if (!isDragging) setIsPressed(true); }) .onEnd(() => { setIsPressed(false); if (!isDragging) { navigation.navigate('parcour', { parcour }); } }); // 滑动手势:优先触发,标记滑动状态并取消按压 const panGesture = Gesture.Pan() .onStart(() => { setIsDragging(true); setIsPressed(false); }) .onEnd(() => { setIsDragging(false); }); // 让滑动手势优先于点击手势 const composedGesture = Gesture.Exclusive(panGesture, tapGesture); return ( <GestureDetector gesture={composedGesture}> <View mr={5} style={{ width: 150, backgroundColor: isPressed ? 'rgba(255,255,255,0.05)' : null, transform: [ { scale: isPressed ? 0.96 : 1 } ] }} > <Image cacheKey={parcour.name} style={{ width: 150, height: 150, borderRadius: 8 }} source={parcour.image} alt={parcour.name} /> <Heading color="white" size="xs" mt={2.5}> Hindernis {parcour.number} </Heading> <Heading color="white" size="md" mt={0.5}> {parcour.name.length > 20 ? `${parcour.name.slice(0, 23)}...` : parcour.name} </Heading> </View> </GestureDetector> ); };
方案2:原生事件判断(无需第三方库)
通过监听触摸事件的位移,判断是点击还是滑动,超过阈值则取消按压状态。
修改ParcourTile组件:
import { useState } from 'react'; const ParcourTile = ({ navigation, parcour }) => { const [isPressed, setIsPressed] = useState(false); const [touchStartPos, setTouchStartPos] = useState({ x: 0, y: 0 }); const SWIPE_THRESHOLD = 10; // 滑动超过10px判定为滑动动作 // 记录触摸开始坐标,触发按压状态 const handlePressIn = (e) => { setTouchStartPos({ x: e.nativeEvent.pageX, y: e.nativeEvent.pageY }); setIsPressed(true); }; // 触摸结束,取消按压状态 const handlePressOut = () => { setIsPressed(false); }; // 触摸移动时判断位移,超过阈值则取消按压 const handleTouchMove = (e) => { const dx = Math.abs(e.nativeEvent.pageX - touchStartPos.x); const dy = Math.abs(e.nativeEvent.pageY - touchStartPos.y); if (dx > SWIPE_THRESHOLD || dy > SWIPE_THRESHOLD) { setIsPressed(false); } }; return ( <Pressable onPressIn={handlePressIn} onPressOut={handlePressOut} onMoveShouldSetResponderCapture={handleTouchMove} onPress={() => navigation.navigate('parcour', { parcour })} > <View mr={5} style={{ width: 150, backgroundColor: isPressed ? 'rgba(255,255,255,0.05)' : null, transform: [ { scale: isPressed ? 0.96 : 1 } ] }} > <Image cacheKey={parcour.name} style={{ width: 150, height: 150, borderRadius: 8 }} source={parcour.image} alt={parcour.name} /> <Heading color="white" size="xs" mt={2.5}> Hindernis {parcour.number} </Heading> <Heading color="white" size="md" mt={0.5}> {parcour.name.length > 20 ? `${parcour.name.slice(0, 23)}...` : parcour.name} </Heading> </View> </Pressable> ); };
方案说明
- 方案1的手势识别库处理逻辑更精准,能更好区分复杂的触摸场景(比如快速滑动后点击),适合生产环境使用。
- 方案2无需额外依赖,轻量简单,适合快速解决基础场景问题。
内容的提问来源于stack exchange,提问作者Alexander Bunt
相关产品推荐
相关产品推荐

