본문 바로가기
웹 프론트엔드/React-Native

React Native : Animated, Interpolation를 이용하여 Loading Spinner 만들기

by 번데기 개발자 2023. 5. 1.
반응형

 

 

React Native를 공부하던 중 Loading Spinner가 필요하여서 구현 방법에 대해 조사를 해보았습니다.

 

React Native에서는 기본적으로 제공하는 Loading Spinner인 ActivityIndicator라는 컴포넌트가 있기는 합니다.

 

ActivityIndicator · React Native

Displays a circular loading indicator.

reactnative.dev


하지만 React Native에서도 간단한 애니메이션을 구현해보고 이해해 보기 위해서 React Native에서 제공하는 Animated를 이용하여 한번 Loading Spinner를 만들어보도록 하겠습니다.

 

먼저 간단하게 React Native의 Animated에 대해 알아보도록 하겠습니다.

 

 

Animated 컴포넌트

 

React Native에서는 Animated라고 하는 API 제공하는데요, Animated를 사용하여 여러 가지 애니메이션을 쉽게 구현할 수 있습니다.

 

Animated는 애니메이션에 기본적으로 사용할 수 있는 6가지의 컴포넌트를 제공합니다.

  • Animated.View
  • Animated.Text
  • Animated.Image (예제 만들어보기)
  • Animated.ScrollView
  • Animated.FlatList
  • Animated.SesctionList

위와 같은 6가지 Component를 이용해서 자신이 원하는 Component에 Animation 기능을 추가할 수 있습니다.

 

 

 

Animated Core 함수

 

그리고 기본적으로 애니메이션을 구동시키는 3가지의 Core API들이 있습니다.

 

Animated.timing (예제 만들어보기)

  • 시간이 지남에 따라 값을 애니메이션(animates) 합니다.
  • Css Animation 과 비슷한 기능을 제공하는 api입니다.
const animationValue = useRef(new Animated.Value(0)).current;

useEffect(() => {
  Animated.timing(
    animationValue,
    {
      toValue: 1, // 어느값으로 어디까지 변화시킬지 (현재는 0 => 1로 변화됨)
      duration: 300, // 애니메이션 
      ease: Easing.inout(), // Easing 함수 선택 (시간에 따른 매개변수의 변화율)
      delay: 200, // 애니메이션 시작 전 delay 값
    }            
  ).start();     
}

 

 

Animated.decay

  • 초기속도로 시작해서, 감속률에 따라서 감속되는 애니메이션을 지정합니다.
const animationValue = useRef(new Animated.Value(0)).current;

useEffect(() => {
  Animated.decay(
    animationValue, {
      toValue: 200, // 최종 값 
      velocity: 0.95, // 초기 속도
      deceleration: 0.998, // 감속 값을 지정합니다. (감쇄율)
      duration: 2000, // 소요 시간
    }).start();
}, []);

 

 

Animated.Spring

  • Spring 물리 모델을 사용하여 애니메이션을 동작시킵니다.
  • 즉 실제 스프링의 물리적 움직임을 비슷하게 구현할 수 있습니다.
const animationValue = useRef(new Animated.Value(0)).current;

useEffect(() => {
  Animated.spring(
    animationValue,
    {
      toValue: 250,
      duration: 2000,
      friction: 1, // 마찰 계수
      tension: 20 // 스프링의 에너지
    }
  ).start();
}, []);

 

좀 더 많은 옵션을 참고하고 싶으시면 공식문서를 참고해 주시면 됩니다. 

 

Animated · React Native

The Animated library is designed to make animations fluid, powerful, and painless to build and maintain. Animated focuses on declarative relationships between inputs and outputs, configurable transforms in between, and start/stop methods to control time-ba

reactnative.dev

 

 

Animated의 Interpolator

 

Interpolator는 Animated.Value를 통해서 새로운 값을 만들어 내는 함수입니다. 

 

Interpolator를 통해서 하나의 애니메이션을 기준으로 여러 효과를 낼 수 있습니다.

 

예를 들어 opacity가 0 => 1 로 변화하는 도중 물체를 한 바퀴 돌리고 싶다면 어떻게 해야 할까요?

 

Animated 함수 중에 sequance 함수와 seaunce 함수 내부의  parallel 함수를 이용하면 위와 같은 동작을 수행할 수 있습니다.

 

Animated.sequence([
  Animated.parallel([
    Animated.timing(.., {}),
    Animated.timing(.., {})
  ])
]).start()  // 동시에 2가지 애니메이션을 진행

 

하지만 interpolate를 사용하면 간단하게 하나의 애니메이션을 기준으로 여러 효과를 만들 수 있습니다.

 

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

const rotate = opacity.interpolator({
    inputRange: [0, 0.5, 1],
    outputRange: ['0deg', '180deg', '360deg'],
  })
return (
  <Animated.View style={
    {transform: [{
        rotate: rotate
      }]},
    opacity: opacity}}>
  <Animated.View />
)

 

즉 기존에 있던 opacity에 대한 값을 이용해서 rotate에 대한 값을 변화시킬수 있습니다. 

 

예를 들어 위의 코드는 opacity가 0, 0.5, 1 일때 ['0deg', '180deg', '360deg'] 에 매핑된다는 뜻이고 위의 경우에는 y=ax의 그래프가 그려지게 됩니다.

 

정확하게는 interpolate 함수를 사용하면 결과값으로 AnimatedInterpolation(rotate)) 타입의 데이터를 반환하여 줍니다. 해당 값을 Interpolation 결과값으로 부르겠습니다. Interpolation 결과값은 원래의 Animated.Value(opacity)의 값이 변화할 때 이에 해당하는 값(deg)으로 바뀌어 저장되고 해당 값을 Animated.View나 다른 Animated 컴포넌트에서 사용할 수 있습니다.

 

직선형이 아닌 곡선형도 가능합니다.

 

const rotate = opacity.interpolator({
  inputRange: [0, 0.99, 1],
  outputRange: ['0deg', '180deg', '360deg'],
})

위와 같은 형태일때는 0 => 0.99일 때 '0deg' => '180deg' 이고 마지막에 급격하게 회전하는 형태로 애니메이션이 동작하게 됩니다.

 

 

Animated를 통해 Loading Spinner 실제로 만들어보기

 

그럼 이제 실제 동작하는 코드를 한번 구현해보도록 하겠습니다.

 

구현 방법은 다음과 같습니다.

 

 

1. Animated와 Interpolate 를 이용하여 회전에 대한 Interpolation 결과값 을 생성합니다.

 

2. 정의된 Interpolation 결과값을 View에 적용하여 해당 Loading Spinner 이미지를 회전시키도록 설정합니다.

 

 

 

1) Animated와 Interpolate 정의

 

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

  const startAnimation = useCallback(() => {
    Animated.loop(
      Animated.timing(animationValue, {
        toValue: 1, // 애니매이션의 100%일때의 값을 추출
        duration: 700, // 애니메이션이 진행되는 시간
        useNativeDriver: true,
      }),
    ).start();
  }, [animationValue]);

  useEffect(() => {
    startAnimation();
  }, [startAnimation]);

  // inputRange : animationValue의 변화값
  // outputRange : 해당 변화값에 대한 매칭되는 deg 값
  const RotateData = animationValue.interpolate({
    inputRange: [0, 1],
    outputRange: ['0deg', '360deg'],
  });

 

  • 먼저 0 -> 1 로 변하는 AnimatedValue를 지정하고 Animated.loop를 통해 반복적으로 0 -> 1 로 Animated.Value를 변화시킵니다.
  • 해당 AnimatedValue의 interpolate 함수를 이용하여 AnimatedVlaue가 0 ->1 로 변할 때  0deg => 360deg 로 변화하는 Interpolation 결과값 을 생성합니다.

 

2)  Interpolation 결과값을 View에 적용

  return (
    <View style={styles.container}>
      <Animated.Image
        style={{
          transform: [{ rotate: RotateData }],
          width: 50,
          height: 50,
        }}
        source={{
          uri: 'https://res.cloudinary.com/df9jsefb9/image/upload/c_scale,h_84,q_auto/v1501869525/assets/idc-loading-t_3x.png',
        }}
      />
    </View>
  );

 

  • 이후 Animated.Image 의 Style에 Interpolation 결과값 을 적용시켜서 rotation에 대한 애니메이션을 적용시킵니다.

 

전체 코드

 

import React, { useCallback, useEffect, useRef } from 'react';
import { View, StyleSheet, Animated } from 'react-native';

const LoadingSpinner = () => {
  const animationValue = useRef(new Animated.Value(0)).current;

  const startAnimation = useCallback(() => {
    Animated.loop(
      Animated.timing(animationValue, {
        toValue: 1, // 애니매이션의 100%일때의 값을 추출
        duration: 700, // 애니메이션이 진행되는 시간
        useNativeDriver: true,
      }),
    ).start();
  }, [animationValue]);

  useEffect(() => {
    startAnimation();
  }, [startAnimation]);

  // inputRange : animationValue의 변화값
  // outputRange : 해당 변화값에 대한 매칭되는 deg 값
  const RotateData = animationValue.interpolate({
    inputRange: [0, 1],
    outputRange: ['0deg', '360deg'],
  });

  return (
    <View style={styles.container}>
      <Animated.Image
        style={{
          transform: [{ rotate: RotateData }],
          width: 50,
          height: 50,
        }}
        source={{
          uri: 'https://res.cloudinary.com/df9jsefb9/image/upload/c_scale,h_84,q_auto/v1501869525/assets/idc-loading-t_3x.png',
        }}
      />
    </View>
  );
};

export default LoadingSpinner;

const styles = StyleSheet.create({
  container: {
    flex: 1,
    backgroundColor: 'white',
    alignItems: 'center',
    justifyContent: 'center',
  },

  spinnerImage: {
    width: 50,
    height: 50,
  },
});

 

출력 화면 

 

아래는 제가 만들고 있는 사이드프로젝트에서의 <LoadingSpinner/> 컴포넌트 출력 화면입니다.

 

 

 

 

 

마무리

 

오늘은 Animated와 Interpolate를 이용해서 간단히 Loading Spinner 애니메이션을 한번 만들어보고 개념에 대해 알아보았습니다.

 

React Native에서는 애니메이션 부분이 만들면서도 이해가 잘 가지 않았었는데 다시 한번 정리해서 보니 저도 한번 개념을 정리할 수 있어서 좋았던 것 같습니다.

 

앞으로도 React Native에서 공부하다가 정리할 내용이 생기면 꾸준히 업로드하도록 하겠습니다.

 

감사합니다.

 

 

 

 

참고 

 

React-native Animation 1편 (KOR 한글) 

 

 

 

반응형