qcoding

[ReactNative_study_13]PanResponder (스크린 화면 터치감지) 본문

ReactNative

[ReactNative_study_13]PanResponder (스크린 화면 터치감지)

Qcoding 2021. 12. 12. 12:12
반응형

https://reactnative.dev/docs/panresponder

 

PanResponder · React Native

PanResponder reconciles several touches into a single gesture. It makes single-touch gestures resilient to extra touches, and can be used to recognize basic multi-touch gestures.

reactnative.dev

1) 스마트폰 화면에서 사용자의 터치입력을 받아서 위치를 변경할 수 있는 Api이다.

   아래 처럼 여러 props가 있으며 docs를 통해서 사용법을 확인할 수 있다.

Object {
  "getInteractionHandle": [Function getInteractionHandle],
  "panHandlers": Object {
    "onMoveShouldSetResponder": [Function onMoveShouldSetResponder],
    "onMoveShouldSetResponderCapture": [Function onMoveShouldSetResponderCapture],        
    "onResponderEnd": [Function onResponderEnd],
    "onResponderGrant": [Function onResponderGrant],
    "onResponderMove": [Function onResponderMove],
    "onResponderReject": [Function onResponderReject],
    "onResponderRelease": [Function onResponderRelease],
    "onResponderStart": [Function onResponderStart],
    "onResponderTerminate": [Function onResponderTerminate],
    "onResponderTerminationRequest": [Function onResponderTerminationRequest],
    "onStartShouldSetResponder": [Function onStartShouldSetResponder],
    "onStartShouldSetResponderCapture": [Function onStartShouldSetResponderCapture],      
  },
}

 

2) 사용법

- 화면상에 View를 움직이는 예제

// panResponder props 확인
const panResponder = useRef(
    PanResponder.create({
     
      // 화면을 터치한 것을 감지함
      onStartShouldSetPanResponder: () => true,
      
      // 실제 화면에 터치를 시작할 때 시작됨
      onPanResponderGrant:()=>{
      }
      
      // 화면에 터치를 하고 움직일 때를 감지함
      // 이동에 대한 props로 event와 gesture 값을 받을 수 있다. 
      // gesture내에 우리가 필요한 dx,dy 이동값이 들어있다.
      onPanResponderMove: (evt , gesture) => {
        console.log(ges)
      },
      
      //화면에 터치를 하고 손을 뗏을 때를 감지함
      onPanResponderRelease: () => {
    
      }
    })
  ).current;
  
  
  
  //console.log(gesture)값 확인
  // 얼만큼 이동했는 지 dx,dy에 대한 값을 확인할 수 있다.
  Object {
      "_accountsForMovesUpTo": 27808094,
      "dx": -1.9940185546875,
      "dy": 145.654296875,
      "moveX": 202.9888916015625,
      "moveY": 518.626953125,
      "numberActiveTouches": 1,
      "stateID": 0.9849285425468876,
      "vx": -0.11077880859375,
      "vy": 0.0026109483506944445,
      "x0": 204.98291015625,
      "y0": 372.97265625,
}

전체코드 --> Animation으로 touch 후 되돌아 가기

import { Animated, PanResponder} from 'react-native'

// 이동에 필요한 초기위치를 설정한다.
const POSITION = useRef(new Animated.ValueXY({x:0,y:0})).current;

// 화면을 감지하기 위해 panResponder 사용
  const panResponder = useRef(
    PanResponder.create({
      onStartShouldSetPanResponder: () => true,
      
      // 내부에서 dx,dy를 이용하여 setValue 메서드를 활용하여 x,y 위치값을 변경한다.
      onPanResponderMove: (_, { dx, dy }) => {
        POSITION.setValue({
          x: dx,
          y: dy,
        });
      },
      
      // 손을 감지한 것을 떼고 났을 때 다시 원래 위치로 되돌아가는 애니메이션을
      // 만들어줌. setValue()를 사용하면 animation없이 바로 되므로 애니메이션을
      // 이용하여 부드럽게 원래 위치로 돌아가게 설정함.
      // * start()를 까먹으면 안됨
      onPanResponderRelease:()=>{
        Animated.spring(POSITION,{
          toValue:{
            x:0,
            y:0
          },
          useNativeDriver:true,
          bounciness:15
        }).start()
      
    })
  ).current;
  
  return (
    <Container>
      <AnimatedBox 
        style={{
          borderRadius,
          opacity,
          transform: [...POSITION.getTranslateTransform()],
        }}
        // animation이 필요한 곳에 아래와 같이 panHandlers을 등록한다.
        {...panResponder.panHandlers}
        ></AnimatedBox>
    </Container>
  )

전체코드 --> touch 하는 데로 계속 움직이게 함.


  const panResponder = useRef(
    PanResponder.create({
      // 터치가 시작되면 감지
      onStartShouldSetPanResponder: () => true,

      // 터치 시작될떄 실행되는 함수
      // setOffset은 터치가 시작될 때 값이 reset 되지 않고 저번 터치가 시작됐을 때
      // 위치에서 다시 시작되기 위해 사용함
      onPanResponderGrant: () => {
        console.log("Touch Started");
        POSITION.setOffset({
          x: POSITION.x._value,
          y: POSITION.y._value,
        });
      },

      onPanResponderMove: (_, { dx, dy }) => {
        console.log("Finger Moving");
        POSITION.setValue({
          x: dx,
          y: dy,
        });
      },
      
      // touch가 완료 된 후에 offset 값이 계속 적산된 것을 막기 위해서 
      // offset을 reset 시키기 위함 POSITION.flattenOffset();
      onPanResponderRelease:()=>{
        console.log("Touch Finished");
        POSITION.flattenOffset();
      }
    })
  ).current;

 

전체코드 --> 여러애니메이션 중첩해서 사용함

import React,{useState,useRef} from 'react'
import { Animated,View,Dimensions,PanResponder,StyleSheet} from 'react-native'
import styled from 'styled-components/native'
import { Ionicons } from "@expo/vector-icons";

const {width:SCREEN_WIDTH,height:SCREEN_HEIGHT}=Dimensions.get("window")

const Container=styled.View`
  flex:1;
  justify-content:center;
  align-items:center;
  background-color:rgba(9, 132, 227,0.7);
  
`;

const Box=styled.View`
  background-color:white;
  justify-content:center;
  align-items:center;
  width:200px;
  height:200px;
  box-shadow: 1px 1px 5px rgba(0, 0, 0, 0.2);
`;

const AnimatedBox=Animated.createAnimatedComponent(Box);


const AnimatedBox_OneLine = styled(Animated.createAnimatedComponent(View))`
  background-color: white;
  width: 200px;
  height: 200px;
  justify-content: center;
  align-items: center;
  border-radius: 12px;
  box-shadow: 1px 1px 5px rgba(0, 0, 0, 0.2);
`;

// 

const App = () => {

  // touch 시 scale 변경을 위한 초기값설정
  const scale=useRef(new Animated.Value(1)).current;

  // Position 위치 변경을 위한 초기값지정
  const position_x=useRef(new Animated.Value(0)).current;
  
  // touch 시 scale up
  const scaleUP=()=>{Animated.spring(scale,{
      toValue:1.1,
      useNativeDriver:true
    }).start()
  }
  
  // touch 후 scale down
  const scaleDown=()=>{Animated.spring(scale,{
      toValue:1,
      useNativeDriver:true
    }).start()
  }

  // touch 후 왼쪽 오른쪽으로 많이 이동하지 않는 경우 
  // center로 돌아옴
  const goCenter=()=>{
    Animated.spring(position_x,{
      toValue:0,
      useNativeDriver:true
    }).start();
  }
  // 왼쪽으로 이동하는 애니메이션
  const goLeft=()=>{
    Animated.spring(position_x,{
      toValue:-500,
      useNativeDriver:true
    }).start();
  }

  // 오른쪽으로 이동하는 애니메이션
  const goRight=()=>{
    Animated.spring(position_x,{
      toValue:500,
      useNativeDriver:true
    }).start();
  }

  const panResponder = useRef(
    PanResponder.create({
      onStartShouldSetPanResponder: () => true,

      onPanResponderGrant: () => {
        scaleUP();
    
      },

      onPanResponderMove: (evt,{dx}) => {
        scaleUP();
        position_x.setValue(dx)
       
      },
      onPanResponderRelease:(evt,{dx})=>{
        // 최종위치를 보고 위치를 변경함.
        console.log(dx)
        if(dx<-200){
          goLeft();
          scaleDown();
        }
        else if(dx>200){
          goRight();
          scaleDown();
        }else{
        // 동시에 animation 되는 걸 활용할 때에는 parallel을 사용함.
          Animated.parallel([goCenter(), scaleDown()])
        }
      }
    })
  ).current;


  return (
    <Container>
      <AnimatedBox 
        {...panResponder.panHandlers}
        
        // 2개의 style object를 적용함.
        
        style={[
          styles.elevation,
          {
            transform:[{translateX:position_x},{scale:scale}]
          }
        ]}
        >
          <Ionicons name='airplane-sharp' size={70} color="black"></Ionicons>
        </AnimatedBox>
    </Container>
  )
}


// android의 경우는 elevation api을 사용하여 그림자를 만들 수 있음

const styles = StyleSheet.create({
  elevation: {
    elevation: 20,
    shadowColor: 'black',
  },

})


export default App

 

 

반응형
Comments