React-Native/RN CLI

React-Native / Animated

HELLICAT 2023. 12. 7. 17:30
반응형

들어가기 앞서...

npm start 및 개발을 위해 시작을 하는데 터미널에서 watchmanResponse가

 error: 'std::__1::system_error: open:/ 파일경로 : Operation not permitted'

와 같이 나온다면 

$ watchman watch-del-all
$ watchman shutdown-server

터미널에 입력해주자

원인은 macOS의 보안 제한 사항으로 watchman이 디스크 엑세스 권한을 받지 못한 상태이다.

입력후 다시 실행시켜 권한요청을 확인눌러준다.

Animated

Animated를 import를 하고 원하는 값을 상수로 선언해준다. animate에 사용할 value는 state에 들어가면 안된다.

const Y = new Animated.Value(0);

절대 직접적으로 값을 변경할 수 없다.  예를 들어  

 

let Y = new Animated.Value(0);
y = 20

절대 불가 절대 금지

값은 Animate API를 통해서만 변경 할수 있다.

 

또한 Animate에는 아무것에나 animation을 줄수 없다.  가능한 것은 다음과 같다

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

또한 위에 없는 것을 사용하고 싶다면 createAnimatedComponent()를 사용해야한다. 

 

만약 styled-components와 함께 사용하고 싶다면 다음과 같다.

const Box = styled(Animated.createAnimatedComponent(View))`
 // 스타일링
`;

또한 다음과 같은 방법도 있다.

const Box = styled.View`
//스타일링
`;
const AnimatedBox = Animated.createAnimatedComponent(Box);

 

Modify Animated Value

다음 세가지가 animated 의  값을 변경할 수 있다.

초기 속도로 시작해서 점점 느려지고 멈춤
Animated.decay()

물리 모델을 제공함.
Animated.spring()

대부분의 경우 timing을 사용함
Animated.timing()

 

Animated.timing() / spring() / decay()

()의 값에는 두가지가 들어가야한다. 하나는 value 다른 하나는 configuration이다.

configuration에서 보낼 수 있는건 첫째로 toValue가 있다.

toValue는 value로 부터 어디까지 가는지를 정하는것이다. 

useNativeDriver는  animation에 대한 모든 데이터와 관련된것이 시작하기 전에 animation에 대한 모든 것을 native 쪽으로 보내준다. 이것은 꼭 true로 값을 해두자

 

spring()에는 bounciness / speed / tension / friction이 들어간다. 또한 bounciness와 speed를 같이 사용할 수 있고 tension과 friction이 같이 사용가능하다.

 

timing()에는 duration, easing, delay, isInteraction도 있다.

Animated Active

Animated.timing().start()
Animated.timing().reset()
Animated.timing().stop()

 

BIG TIP

TouchableOpacity나 Highlight component를 직접적으로 움직이는 것을 되도록 삼가하는게 좋다. 이들은 자체적인 animate가 있기에 animation이 부자연스럽게 작동한다. 

 

new Animated.Value를 사용하게 되면 애니메이션이 활성화면서 Value값으로 돌아오게 되는 이슈를 가질수있게 될것이다. 이럴때 다시 렌더링이 일어나더라도 value를 유지 하게 해주는 hook인 useRef를 사용해주는게 좋다.

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

 

Interpolation

Y의 값을 input으로 가지고 다른 값으로 변환하는것이다.

예를 들어 [-10 , 10]으로 움직이눈 물체가 있다고 가정하자. 우리는 가운데 0의 값 , 즉, [-10, 0, 10] 값 중 0에 크기를 키운다던지 투명도를 준다든지 따위의 값으로 [1,0,1] 바꿔주는것이다. 

interpolate는 두개의 옵션을 받는다. 

하나는 inputRange이다. 기본적으로 어떤 값들을 가져올것인지 뜻한다.

다른 하나는 outputRange 이다. 가져온 값을 다른 값으로 반환해주는 것.

또한 두값의 길이는 같아야한다.

const AnimatedBox = Animated.createAnimatedComponent(Box);

export default function App() {
  const [toggle, setToggle] = useState(false);
  const Y = useRef(new Animated.Value(200)).current;
  const toggleClick = () => setToggle((prev) => !prev);
  const moveUp = () => {
    Animated.timing(Y, {
      toValue: toggle ? -200 : 200,
      useNativeDriver: true,
      easing: Easing.inOut(Easing.cubic),
    }).start(toggleClick);
  };
  const opacityValue = Y.interpolate({
    inputRange: [-200, 0, 200],
    outputRange: [1, 0, 1],
  });
  return (
    <Container>
      <TouchableWithoutFeedback onPress={moveUp}>
        <AnimatedBox
          style={{transform: [{translateY: Y}], opacity: opacityValue}}
        />
      </TouchableWithoutFeedback>
    </Container>
  );
}

 

Animated.ValueXY

값을 두개를 지정할 수 있다.

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

Animated.sequence / Animated.loop

  const {width: SCREEN_WIDTH, height: SCREEN_HEIGHT} = Dimensions.get("window");
  const POSITION = useRef(new Animated.ValueXY({x: 0, y: 0})).current;
  const topLeft = Animated.timing(POSITION, {
    toValue: {x: -SCREEN_WIDTH / 2 + 100, y: -SCREEN_HEIGHT / 2 + 100},
    useNativeDriver: true,
  });
  const bottomLeft = Animated.timing(POSITION, {
    toValue: {x: -SCREEN_WIDTH / 2 + 100, y: SCREEN_HEIGHT / 2 - 100},
    useNativeDriver: true,
  });
  const bottomRight = Animated.timing(POSITION, {
    toValue: {x: SCREEN_WIDTH / 2 - 100, y: SCREEN_HEIGHT / 2 - 100},
    useNativeDriver: true,
  });
  const topRight = Animated.timing(POSITION, {
    toValue: {x: SCREEN_WIDTH / 2 - 100, y: -SCREEN_HEIGHT / 2 + 100},
    useNativeDriver: true,
  });
  const moveUp = () => {
    Animated.sequence([topLeft, bottomLeft, bottomRight, topRight]).start();
    /**
     Animated.loop(
      Animated.sequence([topLeft, bottomLeft, bottomRight, topRight])
    ).start();
    */
  };

 

 

DRAG

panResponder API

기본적으로 손가락의 제스쳐나 움직임을 감지할 수 있게 해준다.

panResponder에는 많은 function들이 있다. 그 기능들은 panHandlers 안에 묶여져있다. 그중 가장 중요한 function은 onStartShouldSetPanResponder이다. 만약 true를 리턴해주면 이벤트가 지정된 곳에서 터치를 감지하기 시작할 것이다.

 

onPanResponderMove에는 event와 gesture 인자가 있는데 그중 gesture 안에는 여러가지가 내포되어 있는데 가로와 세로축의 움직임을 감지하는 dx , dy 가 있다.    *** (x으로 부터 d 만큼 y로부터 d만큼 이동했다.)

 

onPanResponderRelease 는 손가락을 떼면 동작하는 기능

export default function App() {
  const {width: SCREEN_WIDTH, height: SCREEN_HEIGHT} = Dimensions.get("window");
  const POSITION = useRef(
    new Animated.ValueXY({
      x: 0,
      y: 0,
    })
  ).current;

  const rotation = POSITION.y.interpolate({
    inputRange: [-200, 200],
    outputRange: ["-360deg", "360deg"],
  });
  const borderRadius = POSITION.y.interpolate({
    inputRange: [-200, 0, 200],
    outputRange: [100, 0, 100],
  });
  const bgColor = POSITION.y.interpolate({
    inputRange: [-200, 200],
    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,
        });
      },
    })
  ).current;
  return (
    <Container>
      <AnimatedBox
        {...panResponder.panHandlers}
        style={{
          transform: [...POSITION.getTranslateTransform()],
          backgroundColor: bgColor,
          borderRadius,
        }}
      />
    </Container>
  );
}

 

offset

opPanResponderMove를 사용해 POSITION의 값을 변경이 가능하게 되어 움직임이게 되면  클릭할 때 중앙으로 점프하는 모습을 볼수가 있다. 마지막 위치를 기억해주기 위해  onPanResponderGrant를 사용해준다.

  onPanResponderGrant: () => {
        POSITION.setOffset({
          x: POSITION.x._value,
          y: POSITION.y._value,
        });
      },

onPanResponderGrant 이 함수는 움직임이 시작될 때 부터 호출되어 터치를 놓을 때까지 추적하고 손가락을 놓으면 자리를 기억한다.

값은 ._value라고 작성해야 값이 넘어간다. 하지만 onPanResponderGrant을 계속 사용하게 되면 값이 계속 더해져서 움직이는 물체가 화면밖으로 나가게 된다. 이때 손가락을 놓을 때 그자리를 0으로 만들어주는 함수를 작성해야한다.

 onPanResponderRelease: () => {
        POSITION.flattenOffset();
      },
728x90