본문 바로가기
React

React native - progress bar 만들기

by 윤-찬미 2022. 4. 16.

🧩 아래 처럼 생긴 progress bar 를 만들어보자.

progress bar 가 필요했는데, 여러 라이브러리를 살펴봤지만 맘에 드는게 없기도 했고, 이정도는 내가 개발할 수 있는 정도라 그냥 직접 만들었다. 실제로도 별거 없다.

 

📍준비물

🔨구현

해당 컴포넌트를 다양한 곳에서 사용할 수 있도록, step(count) 값은 외부에서 받도록 변경했다.

컨트롤할 ref 정의 하기

ref 와 같이 쓰는 이유는 값이 변경 되었을 때 렌더링 되지 않고 값을 유지시켜, 애니메이션이 되고 원래 위치로 돌아가는 것을 방지하는 데 사용한다. 즉, 리렌더링 시 값이 초기화 되는 것을 막기 위해서 사용한다.

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

 

load 함수 만들기

가장 기본적인 Animated.timing 를 사용하여 애니메이션을 구현할 수 있다.

css Animation 과 비슷한 기능을 제공하는 api 이다.

toValue 값에는 우리가 받은 count 값이 1, 2, 3 이런식으로 들어올 예정이므로 퍼센테이지로 변경해주기 위해

(count / totalStep) * 100로 값을 정의했다.

그럼 count 가 1 , totalStep 이 10일 경우 toValue 는 10이 된다.

 

useNativeDriver 는 브릿지를 거쳐 실행되는 애니메이션에 관련된 JS코드를 네이티브에 넘길지 말지 인데,

layout 프로퍼티(width, top, flex 등)에는 적용할 수 없음으로 false 로 적용한다.

(non-layout 프로퍼티에만 적용가능 (transform, opacity 등))

const load = (count: number) => {
  Animated.timing(loaderValue, {
    toValue: (count / totalStep) * 100,
    duration: 500, // 애니메이션이 진행되는 시간
    useNativeDriver: false,
  }).start();
};

 

step 이 변경 될때마다 load 함수 실행

useEffect(() => {
  load(nowStep)
}, [nowStep]);

 

interpolate 사용하기

Animated.Value의 변화에 따라 종속적으로 값이 변하도록 하는 기능 이다.

그냥 loaderValue ref의 value 를 가져와서 % 로 변환 후 직접 달아 줄 수도 있으나,

interpolate를 사용하면 Animated.Value 값들을 훨씬 깔끔하게 관리하고 변경할 수 있다..

🫧 extrapolate의 값은 아래와 같다.

  • extend (디폴트값)
  • 말그대로 input이 늘어나는 대로 늘어난다. 이 값이 디폴트 값이다.
  • clamp
  • input값이 범위밖을 넘어도 늘어나지 않고 최대치에서 머물러있다.
  • identity
  • 범위밖을 벗어나면 input 값과 동일해진다.
const width = loaderValue.interpolate({
  inputRange: [0, 100], // 0부터 100까지의 값이 들어오면
  outputRange: ["0%", "100%"], // 0% ~ 100%로 변경한다.
  extrapolate: "clamp" //extrapolate은 clamp 으로 설정한다.
});

이후 컴포넌트에는 아래처럼 적용할 수 있다.

<Animated.View
  style={{
    backgroundColor: "#AAC9CE",
    width, // 이렇게!
    height: 3,
    borderTopRightRadius:2,
    borderBottomRightRadius:2
}} />

 

🎨 스타일 적용

스타일 까지 적용한 전체 코드는 아래와 같다.

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

interface IStep {
  totalStep: number;
  nowStep: number;
}

function ProgressBar({ totalStep, nowStep }: IStep) {
  const loaderValue = useRef(new Animated.Value(0)).current;

  const load = (count: number) => {
    Animated.timing(loaderValue, {
      toValue: (count / totalStep) * 100,
      duration: 500,
      useNativeDriver: false,
    }).start();
  };

  const width = loaderValue.interpolate({
    inputRange: [0, 100],
    outputRange: ["0%", "100%"],
    extrapolate: "clamp"
  });

  useEffect(() => {
    load(nowStep)
  }, [nowStep]);

  return (
    <View>
     <View style={styles.bar}>
      <Animated.View
        style={{
          backgroundColor: "#AAC9CE",
          width,
          height: 3,
          borderTopRightRadius:2,
          borderBottomRightRadius:2
        }} />
      </View> 
      <Text style={styles.step}>{nowStep}/{totalStep}</Text>
    </View>
    
  )
}
const styles = StyleSheet.create({
  bar: {
    width: '100%',
    height: 1,
    backgroundColor: '#F0F0F0'
  },
  step: {
    color: '#AAC9CE',
    fontWeight: '400',
    fontSize: 22,
    padding: 22,
    lineHeight: 22 * 1.3,
    textAlign: 'center'
  }
});
export default ProgressBar

 

 

🐥 사용하기

사용은 아래처럼 할 수 있다.

import React, { useState } from 'react'
import { Button, View } from 'react-native';
import ProgressBar from '../common/ProgressBar';
import BasicLayout, { PageType } from '../layout/BasicLayout';

function Test() {
  const [count, setCount] = useState(1);
  return (
    <View>
      <ProgressBar totalStep={12} nowStep={count} />
      <Button onPress={() => setCount((prev) => ++prev)} title="1증가"/>
      <Button onPress={() => setCount((prev) => --prev)} title="1감소"/>
    </View>
  )
}

export default Test