React Native Animated로 스크롤 시 Header Title을 Fade In, Fade Out 구현하기

2022. 1. 13. 17:33·React Native

Bitfolio ios App

이 포스터에서는 Animated API를 활용하여 Navigation Header Title 값을 설정하는 동시에 특정 높이에 도달하면 Title을 Fade In, Fade Out 동작 Custom Hook을 만들어 볼 것입니다.

해당 Hook을 scrollable component(ScrollView)에 적용하기까지 공유합니다.

 

이번에 작성될 코드는 여기서 확인하실 수 있습니다. 

전제 조건

  • React Navigation version >= 5.x.x 라이브러리를 이용해 navigation stack을 구성했다고 가정합니다.

이번 포스터의 핵심은 Custom Hook 만들기 이기 때문에 화면 구성은 최대한 간단히 하겠습니다.

화면 구성

App.tsx 파일에 @react-navigation/stack으로부터 createStackNavigator를 가져와 stack navigator를 만들어 주고 ScrollView 컴포넌트로 감싼 HomeScreen이라는 이름의 화면을 하나 만들어주겠습니다.

// src/App.tsx
import * as React from 'react';
import { View, Text, ScrollView } from 'react-native';
import { NavigationContainer } from '@react-navigation/native';
import { createStackNavigator } from '@react-navigation/stack';

function HomeScreen() {
  return (
    <ScrollView>
      <View style={{ flex: 1, height: 1000 }}>
        <Text style={{ fontSize: 20, fontWeight: 'bold', marginLeft: 16, paddingTop: 30 }}>
          Home
        </Text>
      </View>
    </ScrollView>
  );
}

const Stack = createStackNavigator();

function App() {
  return (
    <NavigationContainer>
      <Stack.Navigator initialRouteName="HomeScreen">
        <Stack.Screen name="HomeScreen" component={HomeScreen} />
      </Stack.Navigator>
    </NavigationContainer>
  );
}

export default App;

Custom Hook만들기

hooks폴더를 만들고, 그 안에 useAnimatedHeaderTitle.ts 파일에 작성하겠습니다.

useAnimatedHeaderTitle은 새롭게 Header Title이 될title 인수와 Fade In이 동작할 스크롤 높이를 triggerPoint로 받게 됩니다.title은 문자열 또는 ReactNode 타입으로 받으며 undefined의 경우 화면 초기 구성을 아래와 같이 하였으므로 name 값인 "HomeScreen"이 됩니다.

<Stack.Screen name="HomeScreen" component={HomeScreen} />

 

우선 y축을 기준으로 한 contentOffset 값을 매핑할 Animated.Value가 필요하기 때문에 상수 scrollY 이름으로 useRef를 이용해 초기값을 0으로 만들어줍니다.

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

 

그리고 useLayoutEffect를 이용하여 navigation options의 title을 인수로 받은 title값으로 치환합니다.

useLayoutEffect(() => {
    if(title) {
      navigation.setOptions({
        title
      })
    }
  }, [navigation, title])

 

그다음 useEffect를 이용하여 Interpolated styles을 담당하는 headerStyleInterpolator에 scrollY값 변화에 따라 opacity를 변화시켜 줄 Animate코드를 작성하고 textStyle에 할당시켜줍니다.

useEffect(() => {
    navigation.setOptions({
      headerStyleInterpolator: () => {
        const opacity = scrollY.interpolate({
          inputRange: [triggerPoint, triggerPoint + 20],
          outputRange: [0, 1]
        });

        return {
          titleStyle: { opacity }
        }
      }
    })
  }, [navigation, scrollY])

 

header title의 값을 치환할 때 useEffect가 아닌 useLayoutEffect를 사용한 이유를 간단하게 설명한다면 다음과 같습니다.

우선 useEffect의 경우 render과 paint 된 후 실행됩니다. 따라서 기존에 screen name을 "HomeScreen"으로 설정하였기 때문에 header title로는 "HomeScreen"문자가 보인 후 인자 title로 변환되게 됩니다.

반면 useLayoutEffect의 경우 render 다음 실행되고, 그 후 paint 되기 때문에 title 문자 깜빡임을 경험하지 않을 수 있습니다.

 

useLayoutEffect와 useEffect의 차이를 모른다면 찾아보시길 권장드립니다.  

 

이제 onScroll이벤트를 통해 Y축 contentOffset값을 매핑할 scrollY(Animated.Value)를 리턴하는 훅을 완성했습니다.

custom hook의 전체 코드는 다음과 같습니다.

// src/hooks/useAnimatedHeaderTitle.ts
import { useLayoutEffect, useEffect, useRef } from 'react';
import { Animated } from 'react-native';
import { useNavigation } from '@react-navigation/native';

type HeaderTitleProps = {
  title?: string | React.ReactNode;
  triggerPoint: number;
}

const useAnimatedHeaderTitle = ({ title,  triggerPoint }: HeaderTitleProps) => {
  const scrollY = useRef(new Animated.Value(0)).current;
  const navigation = useNavigation();

  useLayoutEffect(() => {
    if(title) {
      navigation.setOptions({
        title
      })
    }
  }, [navigation, title])

  useEffect(() => {
    navigation.setOptions({
      headerStyleInterpolator: () => {
        const opacity = scrollY.interpolate({
          inputRange: [triggerPoint, triggerPoint + 20],
          outputRange: [0, 1]
        });

        return {
          titleStyle: { opacity }
        }
      }
    })
  }, [navigation, scrollY])

  return { scrollY };
}

export default useAnimatedHeaderTitle;

적용하기

적용하기는 굉장히 간단합니다.

처음 작성했던 App.tsx에 HomeScreen 컴포넌트를 수정해주겠습니다.

useAnimatedHeaderTitle를 작성했던 파일로부터 불러와 치환할 title과 triggerPoint를 인자로 넘기고 scrollY값을 받아옵니다.

그리고 ScrollView 컴포넌트의 onScroll 이벤트에 훅으로부터 받아온 scrollY를 매핑시켜줍니다.

이때 ScrollView 컴포넌트의 이벤트 빈도를 제어하는 scrollEventThrottle를 16으로 설정해주었습니다.

 

import { View, Text, ScrollView, Animated } from 'react-native';
import useAnimatedHeaderTitle from './hooks/useAnimatedHeaderTitle';

const HomeScreen = () => {
  const { scrollY } = useAnimatedHeaderTitle({ title: 'Home', triggerPoint: 30 });

  const handleScroll = Animated.event(
    [ { nativeEvent: { contentOffset: { y: scrollY } } } ], 
    { useNativeDriver: false }
  )

  return (
    <ScrollView 
      onScroll={ handleScroll }
      scrollEventThrottle={16}
    >
      <View style={{ flex: 1, height: 1000 }}>
        <Text style={{ fontSize: 20, fontWeight: 'bold', marginLeft: 16, paddingTop: 30 }}>
          Home
        </Text>
      </View>
    </ScrollView>
  );
}

 

이제 잘 작동하는 모습을 보실 수 있습니다.

이렇게 컴포넌트의 로직을 분리시켜 어디서든 간단하게 재사용할 수 있도록 Custom Hook으로 만들어 보았습니다.

useAnimatedHeaderTitle 훅은 scrollable component에 활용할 수 있습니다.

 

저작자표시 (새창열림)

'React Native' 카테고리의 다른 글

react navigation "material top tabs" scrollable하게 커스터마이징하기.  (2) 2022.05.13
'React Native' 카테고리의 다른 글
  • react navigation "material top tabs" scrollable하게 커스터마이징하기.
heesu
heesu
개발하면서 겪었던 경험을 공유합니다.
  • heesu
    heesu dev
    heesu
  • 전체
    오늘
    어제
    • 분류 전체보기
      • Nodejs로 알고리즘 박살내기
      • 사이드 프로젝트
        • Bitfolio
      • React Native
      • Etc
      • HTML
      • NextJS
      • Javascript
  • 블로그 메뉴

    • 홈
    • 태그
    • 방명록
  • 링크

  • 공지사항

  • 인기 글

  • 태그

    크루스칼 알고리즘
    Dijkstra
    크루스칼
    Animated
    lv4
    asyncstorage
    최소 스패닝
    정렬
    redux-persist
    UnionFind
    react-native
    React Native
    union-find
    백준
    Nodejs
    BFS
    우선순위 큐
    완전 탐색
    cleancode
    투 포인터
    dfs
    시맨틱 태그
    학습
    letsencrypt
    벨만포드 알고리즘
    certbot
    알고리즘
    부분합
    프로그래머스
    JavaScript
  • 최근 댓글

  • 최근 글

  • hELLO· Designed By정상우.v4.10.3
heesu
React Native Animated로 스크롤 시 Header Title을 Fade In, Fade Out 구현하기
상단으로

티스토리툴바