TIL

[React-Native] react native navigation animation/ reanimated / react-native-animatable/ 리액트 네이티브 페이지 전환 애니메이션, 요소 애니메이션 사용법 위주

자바칩 프라푸치노 2022. 10. 14. 18:02

결과 화면을 먼저 봅시다. 

(참고로 이것은 실제 서비스 하는 코드와 화면이 아님)

 

 

애니메이션을 적용한 곳은

1. 리스트 페이지에서 디테일 페이지로 이동할 때 사진이 그 자리에서 커지면서 이동하고 

디테일 페이지에서 리스트 페이지로 넘어갈때 작아지면서 그 자리를 찾아간다. 

2. 디테일 페이지 진입 시에 이미지 하단 부분이 뿅 하고 나오는 형태

3. 이미지 하단 썸네일들이 뾰로롱 순서대로 등장함

4. 찜하기를 눌렀을 때 별이 띠용띠용 커졌다가 줄어듦

 

이렇게 적용했습니다.

각자에 적용한 라이브러리들이 다릅니다. 하나씩 소개하겠습니다. 

 


1. React-navigation-shared-element

https://github.com/IjzerenHein/react-navigation-shared-element

 

네비게이션이랑 연결해서 간단하게 사용할 수 있는 라이브러리입니다. 

1) 설치와 사용법

 

설치는 이렇게 합니다.

$ yarn add react-navigation-shared-element react-native-shared-element

 

그런데 이것만 설치하면 에러가 나고 

react-navigation/stack

react-native-gesture-handler

이거 두개를 추가로 설치해주어야합니다. 

 

docs에서 나온 적용법은 아주 간단합니다. 

Stack을 react-navigation-shared-element에서 가져온 스택으로 바꾸어 주고,

디테일 페이지에 sharedElements를 등록해줍니다. 

여러개 등록할 수 있습니다. 

영역을 지정할 컴포넌트에 같은 id값을 넘기면 됩니다. 

 

// App.js
import { NavigationContainer } from '@react-navigation/native';
import { createSharedElementStackNavigator } from 'react-navigation-shared-element';

const Stack = createSharedElementStackNavigator();

const App = () => {
  return (
    <NavigationContainer>
      <Stack.Navigator initialRouteName="List">
        <Stack.Screen name="List" component={ListScreen} />
        <Stack.Screen
          name="Detail"
          component={DetailScreen}
          sharedElements={(route, otherRoute, showing) => {
            const { item } = route.params;
            return [`item.${item.id}.photo`];
          }}
        />
      </Stack.Navigator>
    </NavigationContainer>
  );
};

// ListScreen.js
import { SharedElement } from 'react-navigation-shared-element';

class ListScreen extends React.Component {
  renderItem(item) {
    const { navigation } = this.props;
    return (
      <TouchableOpacity onPress={() => navigation.push('Detail', { item })}>
        <SharedElement id={`item.${item.id}.photo`}>
          <Image source={item.photoUrl} />
        </SharedElement>
      </TouchableOpacity>
    );
  }
}

// DetailScreen.js
const DetailScreen = props => {
  const { item } = props.route.params;
  return (
    <SharedElement id={`item.${item.id}.photo`}>
      <Image source={item.photoUrl} />
    </SharedElement>
  );
};

 

저는 이렇게 이미지영역만 적용을 하였습니다. 

만약에 디테일 페이지 전체영역을 같은 아이디로 지정을 하면 이미지가 전체 영역까지 커졌다가 디테일 페이지가 렌더링이 완료되면 디테일 페이지가 나옵니다. 

여기서 SharedElement로 감싸주지 않은 그 아래 영역은 원래대로 슬라이드 하면서 나타나는 문제가 있었습니다. 

이것도 글씨 부분을 ShareElement로 감싸주면 되겠으나 react-native-animatable를 사용했습니다. 

그것은 아래에서 설명 하도록 하겠습니다.

 

2) 해결한 이슈

그런데 이 간단한 걸 적용하는데 아주 오랜 시간이 걸렸습니다. 

일단 이 서비스의 스택이 저 example처럼 간단하지 않았습니다. 

예제에는 Stack 네비게이터 안에 같은 뎁스로 list페이지와 detail페이지가 있는데

우리의 화면을 구성하는 것은 Stack ->Tab -> Tab의 스크린이 리스트 페이지였고

디테일 페이지는 Stack의 스크린입니다. 뎁스가 다르게 적용이 되어있었습니다.

결론적으로는 뎁스가 달라도  사용하고 있는 Stack을 react-navigation-shared-element에서 가져온 스택으로 바꿔주고 디테일 스크린에서 shareElement를 등록해주면 똑같이 적용이 됩니다. 

 

 

두번째로 에러사항은 커질때는 적용이 안되고 줄어들때는 적용이 되었다는 것이었습니다.

결론적으로 이유는 .. 타입스크립트를 쓰고 있었는데 

api에서 내려주는 데이터의 타입과 지정해놓은 타입이 다르게 내려오면 적용이 되지 않습니다.

디테일 api에서 data 로 내려오는 실제 데이터와 내려올 것이라고 지정해놓은 타입이 다르면 적용이 안됩니다. 

 

 

 


2. React-native-animatable

이미지 하단의 영역은 띠용하고 올라오는 느낌을 주고 싶었습니다.

https://github.com/oblador/react-native-animatable

동영상을 볼 때 이 부분을 유심히 봐주시길 바랍니다. 

이부분이 바운스 되면서 올라오는 효과이고 썸네일 이미지들이 순서대로 올라옵니다.

이 라이브러리는 적용이 아주 쉽습니다.

 

1) 설치 및 사용법

$ npm install react-native-animatable --save

import * as Animatable from 'react-native-animatable';

 {goodsImages?.map((image, idx) => (
          <Animatable.View
            key={image.giIdx}
            animation="bounceIn"
            delay={300 + idx * 100}
            style={{ flex: 1 }}>
            <Thumbnail>
            ...
            </Thumbnail>
          </Animatable.View>
        ))}

 

 

애니메이션을 적용하고 싶은 컴포넌트를 Animatable.View로 감싸고 animation과 delay를 지정해줍니다.

docs에 들어가면 애니메이션이 다 나와있는데 거기나온 텍스트를 그냥 animation에 넣어주면 됩니다. 아주 쉽습니다.

찜하기 버튼도 이걸로 적용할 수 있겠으나 저는 react-native-reanimated로 적용을 하였습니다.

 


3. React-native-reanimated

찜하기 버튼이 커졌다가 작아지는 효과를 이것으로 구현했습니다.

https://docs.swmansion.com/react-native-reanimated/docs/fundamentals/installation/

1) 설치 및 사용법

yarn add react-native-reanimated

  const starScale = useSharedValue(1);
  const animatedStyle = useAnimatedStyle(() => {
    return {
      transform: [
        {
          scale: starScale.value,
        },
      ],
    };
  });

useShareValue의 값을 조정해서 애니메이션을 적용합니다. 

저는 애니메이션 스타일을 scale을 적용할 것이기에 애니메이션 스타일을 위와같이 지정해주었습니다.

 

 <S.IconPressable
            type="white"
            onPress={() => {
              starScale.value = withSequence(
                withSpring(1),
                withRepeat(withTiming(1.7, { duration: 200 }), 1, true),
                withSpring(1.1)
              );
              onLikePress();
            }}>

 

그리고 버튼을 눌렀을 때 액션을 지정해주었습니다.

저는 스프링처럼 띠용 거렸으면 좋겠고, 늘었다가 다시 줄었으면 좋겠으므로 1-> 1.7->1.1 로 크기를 조정해주었습니다.

import Animated, {
  useAnimatedStyle,
  useSharedValue,
  withRepeat,
  withSequence,
  withSpring,
  withTiming,
} from 'react-native-reanimated';

<Animated.View style={[animatedStyle]}>
                  <StarFilledIcon
                    color={theme.colors.blue['80']}
                    width={32}
                    height={32}
                  />
                </Animated.View>

 

마지막으로 적용하고 싶은 컴포넌트를 react-native-reanimated에서 가져온 Animated.View로 감싸줍니다.

 

 


후기..

이렇게 간단한 애니메이션을 적용하는데 사실은 매우 오래걸렸다는 것이 함정입니다.

일단 빌드부터가 너무 오래걸렸고..

처음에 react-navigation-shared-element가 적용이 왜 안되는지를 찾기위해 코드를 하나씩 다 지워보면서 어디서 문제가 생기는지 찾아야했습니다. 타입스크립트를 쓰다보니 타입이 정확해야하나봅니다. 이유는 모르겠음.

다른 애니메이션들은 사용도 간단하고 바로바로 바뀌니 아주 간편했습니다. 

직접 애니메이션을 구현해보고 싶기도 합니다. 🤓

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

728x90