React-native - Animation

React-native 에서 어떻게 animation 을 사용하는지
알아보도록 하겠습니다.

 

Animated

핸드폰 어플을 실행하면 해당 어플이 정적인 어플보다는 동적인 어플일 가능성이 높습니다.

이러한 동적인 어플들은 animation 기능을 추가했기 때문에 자유롭게 움직일 수 있고, 가시성도 좋습니다.

React-native 에서 어떻게 animation 을 사용하는지 제가 공부한 내용을 정리해 보도록 하겠습니다.

import { Animated } from "react-native";

 

react-native 에서는 자체적으로 animation 을 지원해 주는 Animated 기능이 있습니다.

 

react-native 에서 제공하는 Animated 기능은 지켜줘야 할 규칙이 몇가지 있습니다.

  1. animation 의 어떤 값 (state, value..) 은 절대로 react에 state 에 두지 않습니다. 즉 필요한 값은 Animated API 에 할당합니다.
  2. Animated.Value 값은 절대 직접 수정하지 않습니다.
  3. View, Text 등 아무 Component 에 Animated 의 value 값을 할당할 수 없습니다. 

이게 무슨 말인지 이해하기 쉽지는 않다고 생각합니다. 따라서 밑에 코드를 보면 대략적으로나마 이해할 수 있습니다.

const Home = () => {
  const Y = new Animated.Value(0)
  
  // 직접 할당 금지
  if(Y > 20) Y = 30
  
  return (
    <View>
      <Text style={{ 여기다가 Y 값 넣을 수 없음 }}></Text>
    </View>
  )
}

 

즉 Y 값은 Animated 를 이용해서 할당해주어야 하며 직접적으로 할당할 수 없고 <Text> 에 넣을 수 없습니다.

 

Y 의 값을 넣어서 animation 기능을 이용하고 싶다면 아래의 Animated 가 지원하는 component 를 이용하시면 됩니다.

  • Animated.Image
  • Animated.ScrollView
  • Animated.FlatList
  • Animated.Text
  • Animated.View
  • Animated.SectionList

만약 Animated 에서 지원해주는 component 외에 component 를 사용하고 싶다면 어떻게 해야 할까요??

예를 들어 TouchableOpacity 같은 component 를 말입니다.

그럴 때 Animated 에서 createAnimatedComponent 함수를 사용하시면 됩니다.

예시 코드

import { Animated } from "react-native";
import styled from "styled-components/native";

export default function App() {

  const Y = new Animated.Value(0);
  const moveUp = () => {};

  return (
    <Container>
      <AnimatedBox onPress={moveUp} style={{ transform: [{ translateX : Y }]}}/>
    </Container>
  )
}

const Container = styled.View`
  flex: 1;
  justify-content: center;
  align-items: center;
`

const Box = styled.TouchableOpacity`
  background-color: gray;
  width: 200px;
  height: 200px;
`

const AnimatedBox = Animated.createAnimatedComponent(Box);

 

애니메이션 사용하기

직접 애니메이션을 사용해 보겠습니다. Animated 를 실행하는 함수는 여러가지가 있지만 timing 을 많이 사용합니다.

Animated.timing(value, {})

 

첫번째 인자로 value, 즉 new Animated.Value 로 설정한 변수 값이 들어갑니다.

두번 째 인자로는 객체가 들어가는데 어디로 이동할지, 얼마동안 실행할지에 대한 자세한 기능을 넣을 수 있습니다.

export default function App() {

  const Y = new Animated.Value(0);
  const moveUp = () => {
    Animated.timing(Y, {
      toValue: 200,
      delay: 200,
      useNativeDriver: true
    }).start();
  };

 

toValue 는 Y 값이 0 -> 200 으로 이동시켜 준다는 뜻이며, delay 는 0.2 초동안 실행한다는 뜻입니다.

여기서 useNativeDriver 은 무엇일까요??

useNativeDriver
는 React Native에서 애니메이션 성능을 최적화하기 위해 사용하는 옵션입니다. React Native는 JavaScript와 네이티브 모듈 간의 브릿지를 통해 애니메이션을 구현하지만, useNativeDriver 를 사용하면 애니메이션을 네이티브 코드에서 직접 처리하도록 하여 성능을 크게 개선할 수 있습니다.

 

우리가 모바일 앱에서 애니메이션을 만들 때, 예를 들어 버튼이 눌릴 때 살짝 움직이거나 화면이 스크롤될 때 부드럽게 움직이는 것 같은 효과를 만들 때, 이 애니메이션은 JavaScript라는 언어로 제어됩니다. React Native는 JavaScript 코드를 사용해서 앱을 만들죠.

하지만, JavaScript는 컴퓨터나 스마트폰의 네이티브(기본) 성능을 최대한으로 활용하지는 못합니다. JavaScript는 한 가지 일만 할 수 있는 단일 스레드에서 실행되기 때문에, 다른 일들도 동시에 진행하고 있다면 애니메이션이 느려지거나 끊길 수 있습니다.

 

React Native는 JavaScript 코드를 사용하지만, 실제로 애니메이션을 실행할 때는 JavaScript 코드를 네이티브 코드(스마트폰의 기본 운영체제에서 동작하는 코드)로 번역해서 실행합니다. 이 번역 작업을 "브릿지"라고 불러요.

예를 들어, 애니메이션을 만들 때 JavaScript는 어떤 움직임을 어떻게 만들지 계산한 다음, 그 결과를 네이티브 코드로 보내서 실제 애니메이션을 실행하게 합니다. 이 과정에서 JavaScript와 네이티브 코드 간의 통신이 자주 발생하면 애니메이션이 느려질 수 있습니다.

 

useNativeDriver를 사용하면 이 문제를 해결할 수 있습니다. 이 옵션을 사용하면 애니메이션을 네이티브 코드에서 직접 실행하게 되어서, JavaScript가 계속해서 네이티브 코드와 통신할 필요가 없어요. 이렇게 하면 애니메이션이 훨씬 더 부드럽고 빠르게 동작할 수 있죠.

예를 들어, useNativeDriver를 사용하는 애니메이션은 스마트폰의 강력한 성능을 더 잘 활용해서 JavaScript가 다른 일을 하고 있을 때도 끊김 없이 부드럽게 움직입니다.

 

이러한 이유 때문에 만약 Animated 를 사용한다면 꼭 들어가야 하는 필수 기능입니다. 

 

TouchableOpacity 로 View 감싸기

애니메이션을 적용할 때 TouchableOpacity 에 애니메이션을 직접 적용하고 스타일도 적용해주면 TouchableOpacity 의 깜빡이는 애니메이션 특성 상 애니메이션이 두번 실행되는 문제가 발생합니다. 그렇게 된다면 애니메이션이 의도치 않게 느리게 작동할 수 있습니다.

이럴 때 TouchableOpacity 에 animation 함수를 적용하고 자식요소에 View 를 추가해 거기에 style 을 적용해 주면 됩니다.

  return (
    <Container>
      <TouchableOpacity onPress={moveUp} >
        <AnimatedBox style={{ transform: [{ translateY : Y }]}}/>
      </TouchableOpacity>
    </Container>
  )
}

const Container = styled.View`
  flex: 1;
  justify-content: center;
  align-items: center;
`

const Box = styled.View`
  background-color: gray;
  width: 200px;
  height: 200px;
`

const AnimatedBox = Animated.createAnimatedComponent(Box);

 

Animated 의 엄청난 강점은 component 를 다시 렌더링 하지 않는다는 점입니다. 

즉 Animated 를 사용하고 있는 component 에 console.log("hello") 로 component 가 렌더링 되는지

확인해 보면 전혀 작동하지 않는 모습을 확인할 수 있습니다. 

 

만약 Y 의 값을 확인하고 싶다면 Y.addListner(() => console.log(Y)) 로 확인하시면 됩니다.

 

애니메이션 동적으로 바꾸기

만약에 애니메이션이 적용된 컴포넌트에 클릭할 때 마다 위, 아래로 이동시키고 싶다면 어떻게 할까요 ??

예를 들어 한번 클릭하면 위로 100px , 다시 클릭하면 아래로 100px .. 이런식으로 말이죠.

 

그럴 때 사용할 수 있는 코드를 보여드리겠습니다.

import { useRef, useState } from "react";
import { Animated, TouchableOpacity } from "react-native";
import styled from "styled-components/native";

export default function App() {

  const [up, setUp] = useState(false);

  const Y = useRef(new Animated.Value(0)).current;

  const toggleUp = () => setUp(prev => !prev);

  const moveUp = () => {
    Animated.timing(Y, {
      toValue: up ? 200 : -200,
      useNativeDriver: true
    }).start(toggleUp);
  };

  Y.addListener(() => console.log(Y))

  return (
    <Container>
      <TouchableOpacity onPress={moveUp} >
        <AnimatedBox style={{ transform: [{ translateY : Y }]}}/>
      </TouchableOpacity>
    </Container>
  )
}

 

여기서 useRef(new Animated.Value(0)).current;  ,  .start(toggleUp);  을 설명하겠습니다.

.start() 안에 함수를 넣으면 애니메이션이 작동이 되고 해당 함수를 실행시켜 줍니다.  

다음으로 useRef 로 왜 묶었는지?? 입니다. 만약 toggleUp 함수가 실행이 된다면 setUp 함수로 인해서 up 이라는 변수가 true 로 

변하게 되고 컴포넌트가 다시 리렌더링 됩니다. 그렇게 된다면 new Animated.Value(0) 값이 다시 0 으로 되돌아 오는 상황이 발생합니다. 

이런 경우에 대비하여 useRef 를 사용하는 것입니다. useRef 는 값이 바뀌더라도 컴포넌트를 리렌더링 하지 않는 함수입니다.

따라서 useRef().current 를 이용해 컴포넌트가 리렌더링 되더라도 new Animated.Value(0) 값을 보호하는 것이죠.

 

Interpolation

 

애니메이션을 적용한 컴포넌트가 위 아래로 움직이는 모습을 확인할 수 있습니다. 만약 위아래로 움직이면서 위에 있을 때는 opacity 값을 

0 으로 설정해서 안보이게 하고 싶다면 어떻게 해야 할지 감이 잡히지 않습니다.  이 기능을 손쉽게 도와주는 방법이 Interpolation 이라는 기능입니다.

 

간단하게 설명하자면 어떤 input 값을 가지고 다른 output 값을 도출하는 것입니다. 

즉 만약 Y 의 값이 [0, 100, 200, 100, 0] 으로 변하는 상황이라면 opacity 값을 [1, 0.5, 0, 0.5, 1]  로 만들어 줍니다.

const opacityValue = Y.interpolate({
  inputRange: [],
  outputRange: []
})

 

inputRange - Y 의 값을 넣어 줍니다. [-200, 0, 200]

outputRange - Y 의 값에 따른 결과 값을 넣어줍니다. [1, 0 ,1]

즉 Y 의 값이 -200 일 때 opacityValue 는 1 이 되며 Y의 값이 0 일 때는 opacityValue 값은 0 이 됩니다.

예시 코드

import { useRef, useState } from "react";
import { Animated, TouchableOpacity, Pressable } from "react-native";
import styled from "styled-components/native";

export default function App() {

  const [up, setUp] = useState(false);

  const Y = useRef(new Animated.Value(200)).current;

  const toggleUp = () => setUp(prev => !prev);

  const moveUp = () => {
    Animated.timing(Y, {
      toValue: up ? 200 : -200,
      duration: 2000,
      useNativeDriver: true
    }).start(toggleUp);
  };

  const opacityValue = Y.interpolate({
    inputRange: [-200, 0, 200],
    outputRange: [1, 0, 1]
  })

  return (
    <Container>
      <Pressable onPress={moveUp} >
        <AnimatedBox style={{ opacity: opacityValue, transform: [{ translateY : Y }]}}/>
      </Pressable>
    </Container>
  )
}

const Container = styled.View`
  flex: 1;
  justify-content: center;
  align-items: center;
`

const Box = styled.View`
  background-color: gray;
  width: 200px;
  height: 200px;
`

const AnimatedBox = Animated.createAnimatedComponent(Box);

 

주의할 점
backgroundColor 도 interpolate 할 수 있지만, useNativeDriver : false 로 설정을 해주어야 합니다.
outputRange: ["rgba(0,0,0,0.5)", " rgba(255,255,255,0.5) "]

ValueXY

기존에 쓰던 new Animated.Value(0)  는 하나의 값만 설정할 수 있었습니다. 

즉 X  또는 Y 값만 설정할 수 있었죠. 하지만 ValueXY 는 이름에서 유추할 수 있듯이 X,Y 값을 동시에 설정할 수 있습니다.

const position = new Animated.ValueXY({x: 300, y: 300});

 

기본적으로 이렇게 사용합니다. 하지만 ValueXY 를 사용하면 기존에 사용하던 interpolate 에서 오류가 생길 수 있습니다.

그 이유는 position 에서 x 값을 interpolate 할 건지, y 값을 interpolate 할 건지 모르기 때문입니다. 

 

따라서 밑에 코드처럼 바꾸어 주시면 됩니다.

const position = useRef(new Animated.ValueXY({x: 300, y: 300})).current;

const rotateValue = position.y.interpolate({
  inputRange: [],
  outputRange: []
})

// component 에 적용할 때

<Animated.Text style={{transform: [{rotateY: position.y}]}}> Hello </Animated.Text>

 

ValueXY 를 사용할 때 Animated.timing 변경하기

ValueXY 를 사용하고 있을 때 Animated.timing 도 변경을 해주어야 합니다.

x , y 값을 어디로 이동시켜야 할지 정해주어야 하기 때문이죠.

  const [up, setUp] = useState(false);
  
  const position = useRef(new Animated.ValueXY({x: 0, y: 0})).current;
  
  const toggleUp = () => setUp(prev => !prev);

  const moveUp = () => {
    Animated.timing(postion, {
      toValue: up ? 200 : -200,
      duration: 2000,
      useNativeDriver: true
    }).start(toggleUp);
  };
  
  // 만약 세세하게 구분하고 싶다면
  
    const moveUp = () => {
    Animated.timing(postion, {
      toValue: up ? {x: 100, y: 200} : {x: -100, y: -200},
      duration: 2000,
      useNativeDriver: true
    }).start(toggleUp);
  };

 

이렇게 설정하면 x,y 값이 동시에 200, -200 으로 가거나,

세세하게 구분해서 x 는 100, y 는 200 으로 이동시킬 수 있습니다.

 

 transfrom: [                     

   {translateX: position.x},

   {translateY: position.y} 

 ]                                      

 

만약 이렇게 스타일을 적용했다고 생각해 봅시다. 제가 생각하는 개발자는 귀찮은 것은 하지 않기 때문에 어떻게든

코드를 줄이고 실용성 있게 바꾸고 싶어합니다. 저도 그렇고요 ㅎㅎ...

그래서 저 코드를 한 줄로 바꿀 수 있습니다.

 transform: [ ...position.getTranslateTransform() ]   라고 적용을 하면 위에 코드와 똑같은 동작을 합니다.

 

loop, sequence

만약 Animated.timing 으로 애니메이션을 적용했는데 여러개의 애니메이션을 적용하고 싶다면 어떻게 해야 할까요??

즉 component 를 클릭했을 때 2번 이상의 애니메이션이 적용되고 싶을 때 사용하는 기능이 sequence 입니다.

 

사용법은 매우 간단합니다.

여러개의 Animated.timing 을 이용해 함수 여러개를 만들고 해당 함수들을 sequence 안에 배열 형태로 넣어 주면 됩니다.

sequence 안에 넣을 timing 은 
const topLeft = () => { Animated.timing(... ) }  이렇게 하면 안되고,
const topLeft = Animated.timing(...) 이렇게 해주셔야 합니다.

 

코드로 보면 이해가 쉬우실 것입니다.

const topLeft = Animated.timing(position, {
    toValue: {
      x: -300,
      y: 300
    },
    useNativeDriver: true
})

const bottomLeft = Animated.timing(position, {
    toValue: {
      x: -300,
      y: -300
    },
    useNativeDriver: true
})

const moveUp = () => {
  Animated.sequence([ topLeft, bottomLeft ]).start()
}

// <TouchableOpacity onPress={moveUp}></TouchableOpacity>

 

이렇게 sequence 안에 배열 형태로 넣어주면 클릭했을 때 내가 만든 애니메이션 2개가 실행됩니다.

topLeft 먼저 실행하고 그 다음으로 bottomLeft 가 실행되는 것이죠. 만약 이러한 애니메이션을 무한 루프로 돌리고 싶다면

loop 로 감싸주면 됩니다.

const moveUp = () => {
  Animated.loop(Animated.sequence([ topLeft, bottomLeft ])).start()
}

 

PanResponder

 

react-native 에서 제공해주는 기능으로 기본적으로 손가락의 제스처나 움직임을 감지할 수 있게 해줍니다.

이 기능을 이용해서 도형을 이리저리 옮기는 것도 가능합니다.

import { Animated, PanResponder } from 'react-native';
const panResponder = React.useRef(PanResponder.create({})).current

 

panResponder 을 console.log() 로 찍어보면 

 

이렇게 나옵니다. 여기서 중요한 부분은 첫번째 줄에 panHandlers 입니다.

panHandlers 안에 있는 함수들을 이용해서 손가락 애니메이션에 적용합니다.

그리고 그렇게 만든 함수를 view 에 넣어줍니다. 즉 사용자가 drag 할 때 그 view 가 반응하기를 원한다면 말이죠. 

그리고 가능하다면 View 안에 넣어주는 것을 추천합니다. 다른 component 안에 넣으면 오류가 발생할 수도 있습니다.

    <TouchableOpacity onPress={moveUp}>
      <AnimatedBox 
        {...panResponder.panHandlers}  <------
        style={{
          opacity: opacityValue,
          transform: [...POSITION.getTranslateTransform()]
        }}
      />
    </TouchableOpacity>

 

이렇게 설정을 하면 해당 AnimatedBox (View 로 만든..) component 는 손가락 제스처로 특정 기능을 수행할 수 있는 상태가 됩니다. 다음으로는 저희가 정의한 panResponder.create({}) 에서 원하는 기능을 만들어야 합니다.

onStartShouldSetPanResponder

이 함수는 PanResponder.create({}) 에서 객체 안에 사용하는 함수인데
이 함수가 true 를 반환하면 PanResponder 가 여러분의 사각형에서 터치를 감지하게 됩니다.

만약 expo 환경에서 실행이 되지 않는다면
onStartShouldSetPanResponder 대신 onMoveShouldSetPanResponder: () => true 로 넣으세요.

 

하지만 터치만 감지하게 되고 어디로 어떻게 이동하는지에 대한 정보는 확인할 수 없습니다. 

따라서 그 다음으로 적용해야 할 함수는 onPanResponderMove 입니다.

onPanResponderMove

이 함수는 마찬가지로 PanResponder.create({}) 에서 객체 안에 사용하는 함수인데 거리가 바뀌면 호출되는 함수 입니다.

  const panResponder = React.useRef(PanResponder.create({
    onStartShouldSetPanResponder: () => true,
    onPanResponderMove: (_, gestureState) => {
      console.log(gestureState)
    } 
  })).current

 

panResponder 을 적용한 component 를 마우스로 클릭해서 움직이면 gestureState 를 통해 console.log 로 확인을 하면 다양한 값이 나오는 것을 확인할 수 있습니다.


그중에서 dx, dy 값이 있는데 내가 손으로 어디까지 도형을 누르고 어디까지 움직였는지 확인할 수 있는 값입니다.
이걸 이용해서 드래그 기능을 만들 수 있습니다. 

  const POSITION = useRef(new Animated.ValueXY({x: 0, y: 0})).current;
  
  const panResponder = React.useRef(PanResponder.create({
    onStartShouldSetPanResponder: () => true,
    onPanResponderMove: (_, {dx, dy}) => {
      POSITION.setValue({
        x: dx,
        y: dy
      })
    } 
  })).current
  
  return (
      <AnimatedBox 
        {...panResponder.panHandlers}
        style={{
          opacity: opacityValue,
          transform: [...POSITION.getTranslateTransform()]
        }}
      />
  )

 

setValue 는 우리가 animate 의 value 값을 수동으로 조작할 수 있게 도와줍니다.

 

하지만 여기까지 했다면 한가지 문제가 있습니다. 만약 애니메이션이 끝나고 다시 해당 component 를 클릭하면

제자리에서 다시 시작합니다.  왜 그런 걸까요??

dy, dx 값은 내가 손가락으로 터치한 순간부터 이동한 y , x 의 거리를 나타냅니다. 

즉 처음에 터치를 시작할 순간부터 dy, dx 값은 0 에서부터 시작합니다. 따라서 다시 클릭을 한다면 0 에서부터 시작하기 때문에 component 가 제자리로 순간이동 하는 것이죠. dx, dy 로 설정한 y, x 값이 0 으로 변경되기 때문입니다.

즉 내가 옮겨놓은 component 에서 시작하는 것이 아니라 원래 초기에 설정했던 

component 자리에 다시 순간이동해서 시작한다는 것이죠.  그럼 가독성 측면에서 좋지 않습니다.

따라서 onPanResponderPelease 함수를 사용해야 합니다.

onPanResponderRelease

이 함수는 마찬가지로 PanResponder.create({}) 에서 객체 안에 사용하는 함수인데

사용자가 손가락을 떼면 동작하는 함수 입니다.

  const panResponder = React.useRef(PanResponder.create({
    onStartShouldSetPanResponder: () => true,
    onPanResponderMove: (_, {dx, dy}) => {
      POSITION.setValue({
        x: dx,
        y: dy
      })
    },
    onPanResponderRelease: () => {
      Animated.spring(POSITION, {
        toValue: {
          x: 0,
          y: 0
        },
        useNativeDriver: false
      }).start();
    }
  })).current

 

이렇게 설정을 한다면 좀 더 부드럽고 자연스러운 애니메이션을 볼 수 있습니다.

 

최종 코드

import React, { useRef } from "react";
import { Animated, PanResponder } from "react-native";
import styled from "styled-components/native";

const Container = styled.View`
`;

const Box = styled.View`
  width: 200px;
  height: 200px;
  background-color: "#CDCDCD";
`

const AnimatedBox = Animated.createAnimatedComponent(Box);


export default function App() {

  const POSITION = useRef(
    new Animated.ValueXY({
      x: 0,
      y: 0,
    })
  ).current;

  const borderRadius = POSITION.y.interpolate({
    inputRange: [-300, 300],
    outputRange: [100, 0],
  })
  
  const bgColor = POSITION.y.interpolate({
    inputRange: [-300, 300],
    outputRange: ["rgb(255, 99, 71)", "rgb(71, 166, 255)"],
  });
  
  const panResponder = useRef(
    PanResponder.create({
      onStartShouldSetPanResponder: () => true,
      onPanResponderMove: (_, { dx, dy }) => {
        POSITION.setValue({
          x: dx,
          y: dy,
        });
      },
      onPanResponderRelease: () => {
          Animated.spring(POSITION, {
            toValue: {
              x: 0,
              y: 0
            },
            useNativeDriver: false
          }).start();
      }
    })
  ).current;
  
  return (
    <Container>
    
      <AnimatedBox
        {...panResponder.panHandlers}
        style={{
          borderRadius,
          backgroundColor: bgColor,
          transform: [...POSITION.getTranslateTransform()],
        }}
      />
      
    </Container>
  );
}

 

onPanResponderGrant

이 함수는 component 가 움직임이 시작될 때 호출됩니다.

 

  • POSITION.setOffset:
    • 지금 위치를 기억해두는 것.
    • 다음 번 움직임을 계산할 때 기준점으로 사용.
  • POSITION.flattenOffset:
    • 기억해둔 위치를 현재 위치에 더해서 새로운 시작점으로 설정.
    • 다음 번 움직임을 다시 새로운 기준점에서 시작할 수 있도록 준비.

 

즉 setOffset 은 내가 설정한 new Animated.ValueXY({x: 0, y: 0}) 값을 수시로 바꾸어 줍니다.

그리고 flattenOffset 은 초기에 설정한 위치와 움직인 위치를 더해서 새로운 값으로 바꾸어 ValueXY({}) 에 넣어줍니다.

 

최종적으로 밑에 코드처럼 만들면 component 를 움직이고 놔둬도 그 자리에서 다시 시작할 수 있습니다.

  const panResponder = useRef(
    PanResponder.create({
      onStartShouldSetPanResponder: () => true,
      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,
        });
      },
      onPanResponderRelease: () => {
        console.log("Touch Finished");
        POSITION.flattenOffset();
      },
    })
  ).current;

 

_value 의 이해

이 part 는 _value 를 왜 쓰는지 너무 궁금해서 따로 찾아본 부분이라 궁금하지 않으시다면 넘어가셔도 됩니다.

왜 _value를 사용하나요?

  1. Animated.Value의 내부 구조:
    • Animated.Value 객체는 단순한 숫자가 아니라, 애니메이션 값의 상태를 관리하는 복잡한 객체에요.
    • 이 객체는 애니메이션 값의 현재 상태뿐만 아니라 애니메이션을 적용하거나 계산하는 데 필요한 여러 속성과 메서드를 포함하고 있어요.
    • POSITION.x와 같은 경우 Animated.Value 객체로서 다양한 메서드와 속성들을 가지고 있어요.
  2. _value 속성:
    • POSITION.x._value는 Animated.Value 객체 내부의 실제 숫자 값을 가리켜요.
    • _value는 Animated.Value 객체의 현재 값을 직접 가져오는 속성이에요.
    • POSITION.x는 Animated.Value 자체를 가리키고, _value는 그 안의 실제 값, 즉 숫자만을 가리켜요.
    • 우리가 화면에서 보이는 위치를 계산할 때는 이 실제 숫자 값이 필요하기 때문에 _value를 사용해요.

예시로 설명하기

  1. POSITION.x:
    • POSITION.x는 Animated.Value 객체 전체를 나타내요.
    • 이 객체는 애니메이션 관련 메서드 (예: .setValue(), .addListener()) 등을 포함하고 있어요.
  2. POSITION.x._value:
    • POSITION.x._value는 Animated.Value 객체 안에 저장된 실제 숫자 값이에요.
    • 예를 들어, POSITION.x가 애니메이션 중에 위치를 관리하는 객체라면, POSITION.x._value는 그 위치의 현재 값 (예: 150, 200 등)을 나타내요.

간단한 비유

  • 자동차와 속도계:
    • 생각해보세요, 자동차(=Animated.Value)가 있다고 할게요. 이 자동차는 여러 가지 기능과 정보를 가지고 있죠 (엔진, 바퀴, 스피드 메터 등).
    • 이 중에서 우리가 자동차의 현재 속도(=_value)를 알고 싶을 때는 스피드 메터를 봐야 해요.
    • 자동차 전체가 아니라 스피드 메터를 직접 확인하는 것처럼, POSITION.x의 전체 객체가 아니라 _value를 통해 현재의 정확한 숫자 값을 확인하는 거예요.

만약 _value를 사용하지 않으면?

만약 그냥 POSITION.x를 사용한다면, 그것은 Animated.Value 객체 자체를 가리키게 되어요. POSITION.setOffset은 실제 숫자 값을 필요로 하기 때문에, _value를 사용해서 그 숫자 값을 꺼내와야 해요. 그렇지 않으면 원하는 결과를 얻을 수 없고, 에러가 발생할 수도 있어요.

 

 

'React-native' 카테고리의 다른 글

React-native 에서 firebase 적용하기  (0) 2024.06.21
React-native - 기술  (1) 2024.05.26
React-native  (0) 2024.05.23