프로젝트/할일목록 앱 (RN)

13. 스택 네비게이터를 이용하여 네비게이션하기

syleemomo 2023. 10. 26. 11:58
728x90
/**
 * @format
 */

import {AppRegistry} from 'react-native';
import stackRouter from './stackRouter';
import {name as appName} from './app.json';

AppRegistry.registerComponent(appName, () => stackRouter);

루트 디렉토리의 index.js 파일을 위와 같이 수정한다. LandingScreen 대신에 stackRouter 컴포넌트를 화면에 보여준다. 

import React, { useState, useEffect } from 'react'
import { View, Text, StyleSheet, ActivityIndicator } from 'react-native'
import { NavigationContainer } from '@react-navigation/native';
import { createNativeStackNavigator } from '@react-navigation/native-stack';

import LandingScreen from './screens/LandingScreen';
import App from './App'

const Stack = createNativeStackNavigator()

function stackRouter(){
    return (
        <NavigationContainer>
            <Stack.Navigator 
                initialRouteName = "Landing" 
                screenOptions={{headerShown: false}} // 탭메뉴 헤더와 겹치지 않도록 함
            >
                <Stack.Screen name="Landing" component={LandingScreen}/>
                <Stack.Screen name="App" component={App}/>
            </Stack.Navigator>
        </NavigationContainer>
    )
}
export default stackRouter

루트 디렉토리에 stackRouter 컴포넌트를 생성하고 위와 같이 작성한다. 

import LandingScreen from './screens/LandingScreen';
import App from './App'

랜딩페이지와 App 컴포넌트를 임포트한다.

<NavigationContainer>
    <Stack.Navigator 
        initialRouteName = "Landing" 
        screenOptions={{headerShown: false}} // 탭메뉴 헤더와 겹치지 않도록 함
    >
        <Stack.Screen name="Landing" component={LandingScreen}/>
        <Stack.Screen name="App" component={App}/>
    </Stack.Navigator>
</NavigationContainer>

랜딩페이지와 App 컴포넌트는 스택 네비게이터를 이용하여 페이지 전환을 할 수 있도록 설정해준다. 

import React from 'react'
import { View, Button, StyleSheet } from 'react-native'

function LoginButton({navigation}){
    const gotoHome = () => {
        navigation.navigate('App')
    }
    return (
        <View style={styles.buttonWrapper}>
            <Button title="로그인" onPress={gotoHome}/>
        </View>
    )
}
export default LoginButton

const styles = StyleSheet.create({
    buttonWrapper: {
        position: 'absolute',
        left: 0, right: 0,
        bottom: 100,
    }
})

components > LoginButton.js 파일을 생성하고 위와 같이 작성한다. 로그인 버튼을 화면에 보여주고, 소셜로그인 기능을 수행할 수 있는 컴포넌트이다. 로그인 버튼을 터치하면 LandingScreen 컴포넌트로부터 전달받은 navigation 객체를 이용하여 탭메뉴가 있는 App 컴포넌트로 이동한다. 이때 navigate 메서드로 전달되는 문자열은 Stack.Screen 컴포넌트에 설정된 name 과 동일한 값이다. 로그인 버튼은 position 을 'absolute' 로 설정하여 화면 바닥에서 100dp 떨어진 위치에 보여준다. 

import React, { useState } from 'react'
import { SafeAreaView, View, Text, StyleSheet, StatusBar, ScrollView, Dimensions, ImageBackground  } from 'react-native'
import LandingPage from '../components/LandingPage'
import landingData from '../data/landingData'
import LoginButton from '../components/LoginButton';

function LandingScreen({navigation}){
    const { width, height } = Dimensions.get('window')
    const [currentPageIndex, setCurrentPageIndex] = useState(0)
    console.log('페이지 번호: ', currentPageIndex)

    const setCurrentPage = (e) => {
        const { x } = e.nativeEvent.contentOffset // x : 스크롤 위치
        console.log("스크롤 위치: ", x, "화면너비: ", width)
        const nextPageIndex = Math.ceil(x / width) // x / width : 스크롤 위치 / 화면너비 -> 페이지번호
        console.log(nextPageIndex)
        if(nextPageIndex !== currentPageIndex){
            setCurrentPageIndex(nextPageIndex)
        }
    }
    return (
        <>
            <StatusBar hidden></StatusBar>
            <SafeAreaView style={styles.block}>
                <ScrollView
                    style={{ flex: 1 }}
                    horizontal={true} // 수평 스크롤링
                    scrollEventThrottle={16} // 스크롤 이벤트 감지하는 시간간격 (ms)
                    pagingEnabled={true} // 스크롤시 페이지네이션
                    showsHorizontalScrollIndicator={false} // 스크롤바 숨기기
                    onScroll={setCurrentPage}
                >
                    {landingData.map((page, index) => (
                        <LandingPage
                            width={width}
                            height={height}
                            {...page}
                            key={index}
                        />
                    ))}
                </ScrollView>

                <View style={styles.scrollIndicatorWrapper}>
                    {Array(3).fill(0).map((_, index) => (
                        <View key={index} style={[styles.scrollIndicator, { opacity: currentPageIndex === index ? 1: 0.3}]}></View>
                    ))}
                </View>

                <LoginButton navigation={navigation}/>
            </SafeAreaView>
        </>
    )
}

const styles = StyleSheet.create({
  block: {
    flex: 1
  },
  scrollIndicatorWrapper:{
    position: 'absolute',
    left: 0, right: 0,
    bottom: 50,
    flexDirection: 'row',
    justifyContent: 'center',
    alignItems: 'center'
  },
  scrollIndicator: {
    height: 10,
    width: 10,
    borderRadius: 10 / 2,
    backgroundColor: '#aaa',
    marginLeft: 10,
  },

})
export default LandingScreen

screens > LandingScreen.js 파일을 위와 같이 수정한다.

import LoginButton from '../components/LoginButton';

랜딩페이지에서 사용자가 로그인할 수 있도록 loginButton 컴포넌트를 임포트한다.

<LoginButton navigation={navigation}/>

loginButton 컴포넌트에서 navigation 객체를 이용할 수 있도록 props 로 내려준다. LoginButton 컴포넌트는 스택 네비게이터에 정의되어 있지 않기 때문에 기본적으로 navigation 객체를 전달받지 못한다. 그래서 사용하려면 LandingScreen 컴포넌트에서 전달받아서 내려줘야 한다. 

export default function App({navigation}) {
 // 중략
}

App.js 파일에 해당 부분을 수정한다. App 컴포넌트는 스택 네비게이터에 정의되어 있으므로 navigation 객체를 전달받고 사용할 수 있다. 

<NavigationContainer>
	// 중략
</NavigationContainer>

App.js 의 해당 부분을 아래와 같이 Fragment 컴포넌트로 변경한다. 이렇게 하는 이유는 이미 스택 네비게이터를 사용할때 NavigationContainer 로 감싸주었기 때문에 App 컴포넌트에서 다시 NavigationContainer 를 사용하면 에러가 발생한다.

<>
	// 중략
</>

 

* 페이지 전환 테스트하기

랜딩페이지의 로그인 버튼을 클릭하면 스택 네비게이터에 정의된 App 컴포넌트로 이동한다.

랜딩페이지 로그인버튼
랜딩페이지 로그인 버튼 클릭시 화면전환

 

728x90