React Native에서 다중 클릭 이벤트 처리

원래 my blog 에 게시되었습니다.

React Native의 클릭 이벤트 소개



React Native는 클릭 이벤트를 처리하기 위한 3가지 기본 기본 요소를 제공합니다.
  • 터치 가능한 하이라이트
  • 터치 가능 불투명도
  • 피드백 없이 터치 가능

  • 이들 중 하나를 사용하는 것은 꽤 일반적인 일입니다. 클릭 이벤트에 응답해야 하는 구성 요소를 래핑하기만 하면 됩니다.

    // imports...
    
    <TouchableHighlight onPress={() => console.log('Clicked')}>
      <Text>Click me</Text>
    </TouchableHighlight>
    


    터치 가능한 문제



    대부분의 사용 사례에서 잘 작동하지만 처리할 수 없는 까다로운 상황이 있습니다. 단일 클릭, 더블 클릭 및 길게 누르기 이벤트를 모두 동일한 요소에서 처리하는 경우를 고려하십시오.

    구조에 PanResponder



    PanResponder는 하위 수준Gesture Responder System API에 예측 가능한 래퍼를 제공합니다. 터치 이벤트에 대한 훨씬 세분화된 제어를 제공하고 터치 시작 위치, 터치 종료 위치, 제스처 속도 등과 같은 유용한 메타 정보에 대한 액세스도 제공합니다.

    PanResponder를 사용하여 View 컴포넌트가 터치 이벤트에 응답하도록 하는 방법을 알아보겠습니다.

    import { View, PanResponder, Text } from 'react-native';
    
    const MyComponent = () => {
      const responder = PanResponder.create({
        onStartShouldSetPanResponder: () => true,
        onMoveShouldSetPanResponder: () => true,
    
        onPanResponderStart: (event) => {
          console.log('Touch has started !');
        },
    
        onPanResponderRelease: (event, gestureState) => {
          console.log('Touch has ended !');
        },
    
        onPanResponderTerminate: () => {},
      });
    
      return (
        <View {...responder.panHandlers}>
          <Text>Click Me </Text>
        </View>
      );
    };
    

    onStartShouldSetPanRespondertrue를 반환해야 터치 이벤트 시작 시 뷰가 응답자가 될 수 있습니다.
    onMoveShouldSetPanRespondertrue를 반환해야 드래그 이벤트가 시작될 때 뷰가 응답자가 될 수 있습니다.
    onPanResponderStart 콜백은 PanResponder가 터치 이벤트를 등록할 때 시작됩니다.
    onPanResponderRelease 터치가 해제되면 콜백이 시작됩니다.
    onPanResponderTerminate 보기에서 응답자를 가져오면 콜백이 시작됩니다. 이것은 다른 보기가 onPanResponderTerminationRequest를 호출하거나
    묻지 않는 OS(iOS의 제어 센터/알림 센터에서 발생).

    더블 클릭이 작동하도록 하려면 카운터를 사용하고 클릭 사이의 최대 시간을 설정하여 더블 클릭으로 처리해야 합니다. 클릭 사이의 400ms 지연은 시작하기에 좋은 위치입니다. handleTap를 사용하여 타이머를 기반으로 클릭 이벤트 유형을 결정합니다.

    const MyComponent = () => {
      const [isTerminated, setTerminated] = useState(false);
      const [touchStartTime, setTouchStartTime] = useState(0);
      const [lastTap, setLastTap] = useState(0);
    
      const DOUBLE_PRESS_DELAY = 400;
    
      const handleTap = (event, gestureState) => {
        const timeNow = Date.now();
        if (lastTap && timeNow - lastTap < DOUBLE_PRESS_DELAY) {
          console.log('Handle double press');
        } else {
          setLastTap(timeNow);
    
          const timeout = setTimeout(() => {
            setLastTap(0);
            console.log('Handle single press');
          }, DOUBLE_PRESS_DELAY);
        }
      };
    
      const responder = PanResponder.create({
        onStartShouldSetPanResponder: () => true,
        onMoveShouldSetPanResponder: () => true,
    
        onPanResponderStart: () => {
          const timeout = setTimeout(() => {
            if (!isTerminated) {
              setTouchStartTime(Date.now());
            }
          });
        },
    
        onPanResponderRelease: (event, gestureState) => {
          handleTap(event, gestureState);
        },
    
        onPanResponderTerminate: () => {
          setTerminated(true);
        },
      });
    
      return (
        <View {...responder.panHandlers}>
          <Text>Click Me </Text>
        </View>
      );
    };
    


    이제 길게 누르기가 작동하도록 하려면 700ms 지연이 있는 다른 카운터가 필요합니다. 한 번 누르는지 두 번 누르는지 확인하기 전에 길게 누르는지 먼저 확인합니다. handlePressOut를 사용하여 클릭 유형을 결정하고 이에 대한 작업을 위임합니다.

    const MyComponent = () => {
      const [isTerminated, setTerminated] = useState(false);
      const [touchStartTime, setTouchStartTime] = useState(0);
      const [lastTap, setLastTap] = useState(0);
    
      const [longPressTimer, setLongPressTimer] = useState(0);
      const [singlePressTimer, setSinglePressTimer] = useState(0);
    
      const DOUBLE_PRESS_DELAY = 400;
      const LONG_PRESS_DELAY = 700;
    
      const cancelLongPressTimer = () => {
        if (longPressTimer) {
          clearTimeout(longPressTimer);
          setLongPressTimer(0);
        }
      };
    
      const cancelSinglePressTimer = () => {
        if (singlePressTimer) {
          clearTimeout(singlePressTimer);
          setSinglePressTimer(0);
        }
      };
    
      const handleTap = (event, gestureState) => {
        cancelSinglePressTimer();
    
        const timeNow = Date.now();
        if (lastTap && timeNow - lastTap < DOUBLE_PRESS_DELAY) {
          console.log('Handle double press');
        } else {
          setLastTap(timeNow);
    
          const timeout = setTimeout(() => {
            setLastTap(0);
            console.log('Handle single press');
          }, DOUBLE_PRESS_DELAY);
    
          setSinglePressTimer(timeout);
        }
      };
    
      const handlePressOut = (event, gestureState) => {
        const elapsedTime = Date.now() - touchStartTime;
        if (elapsedTime > LONG_PRESS_DELAY) {
          console.log('Handle long press');
        } else {
          handleTap(event, gestureState); // handles the single or double click
        }
        setTouchStartTime(0);
      };
    
      const responder = PanResponder.create({
        onStartShouldSetPanResponder: () => true,
        onMoveShouldSetPanResponder: () => true,
    
        onPanResponderStart: () => {
          cancelLongPressTimer();
    
          const timeout = setTimeout(() => {
            if (!isTerminated) {
              setTouchStartTime(Date.now());
            }
          });
    
          setLongPressTimer(timeout);
        },
    
        onPanResponderRelease: (event, gestureState) => {
          handlePressOut(event, gestureState);
        },
    
        onPanResponderTerminate: () => {
          setTerminated(true);
        },
      });
    
      return (
        <View {...responder.panHandlers}>
          <Text>Click Me </Text>
        </View>
      );
    };
    


    결론



    동일한 요소에 대한 여러 클릭을 손쉽게 처리할 수 있도록 정확히 이 작업을 수행하는 react-native-gifted-touch을 만들었습니다. 라이브러리의 기본 시간 지연은 요구 사항에 더 잘 맞도록 props를 사용하여 구성할 수 있습니다. 확인하시기 바랍니다.

    좋은 웹페이지 즐겨찾기