You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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(推荐)

利用手势识别库区分点击和滑动动作,确保滑动时不触发按压状态,点击时正常响应。

  1. 先安装依赖(如果未安装):
npm install react-native-gesture-handler
# 或 yarn add react-native-gesture-handler
  1. 修改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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.10 02:22:02