LifeSports Application(ReactNative & Nest.js) - 4. BottomNavigation

yellow_note·2021년 9월 2일
0

#1 BottomNavigation

BottomNavigation은 하단에 버튼을 두어 버튼 클릭 시 원하는 화면으로 넘어갈 수 있게끔 하는 네비게이션입니다.

위의 플래시 이미지가 바텀네비게이션입니다.

#2 BottomNavigation 구현

저는 BottomNavigation을 메인페이지에 둘 것이니 LoginScreen, RegisterScreen이 있는 StackNavigation에 HomeScreen 대신 BottomNavigation을 두도록 하겠습니다.

다음의 명령어로 패키지를 설치하겠습니다. 그리고 아이콘 패키지도 설치하겠습니다.

npm install --save @react-navigation/bottom-tabs
npm install --save react-native-vector-icons/Ionicons

그리고 다음의 navigator디렉토리에 BottomNavigation.js를 만들어 네비게이션을 구현하겠습니다.

  • /navigator/BottomNavigation
import React from 'react';
import { createBottomTabNavigator } from '@react-navigation/bottom-tabs';
import Icon from 'react-native-vector-icons/Ionicons';
import palette from '../styles/palette';
import HomeScreen from '../pages/home/HomeScreen';
import PostScreen from '../pages/post/PostScreen';
import MapScreen from '../pages/map/MapScreen';
import MyPageScreen from '../pages/user/MyPageScreen';

const Tab = createBottomTabNavigator();

const BottomNavigation = ({ route }) => {
    return(
        <Tab.Navigator
            screenOptions={({ route }) => ({
                tabBarIcon: ({ focused, color }) => {
                    var iconName;
                    var iconSize;

                    if(route.name === "Home") {
                        iconName = focused ? 'ios-home' : 'ios-home-outline';
                        iconSize = focused ? 32 : 24;
                    } else if (route.name === 'Board') {
                        iconName = focused ? 'ios-reader' : 'ios-reader-outline';
                        iconSize = focused ? 32 : 24;
                    } else if (route.name === 'Map'){
                        iconName = focused ? 'ios-location' : 'ios-location-outline';
                        iconSize = focused ? 32 : 24;
                    } else if (route.name === 'Setting'){
                        iconName = focused ? 'ios-person' : 'ios-person-outline';
                        iconSize = focused ? 32 : 24;
                    }

                    return (
                        <Icon 
                            size={ iconSize }
                            name={ iconName } 
                            color={ color }
                        />
                    )
                }
            })}

            tabBarOptions = {{
                activeTintColor: palette.blue[4],
                inactiveTintColor: palette.gray[5],
            }}
        >
            <Tab.Screen 
                name="Home"
                children={
                    () => <HomeScreen />
                }
            />
            <Tab.Screen 
                name="Post"
                children={
                    () => <PostScreen />
                }
            />
            <Tab.Screen 
                name="Map"
                children={
                    () => <MapScreen />
                }
            />
            <Tab.Screen 
                name="MyPage"
                children={
                    () => <MyPageScreen />
                }
            />
        </Tab.Navigator>
    );
};

export default BottomNavigation;
  • /MainNavigation
import React from 'react';
import 'react-native-gesture-handler';
import { createStackNavigator } from '@react-navigation/stack';
import { NavigationContainer } from '@react-navigation/native';
import LoginScreen from '../pages/auth/LoginScreen';
import RegisterScreen from '../pages/auth/RegisterScreen';
import BottomNavigation from './BottomNavigation';

const Stack = createStackNavigator();

const StackNavigatior = () => {
    return(
        <NavigationContainer>
            <Stack.Navigator>
            
                ...
                
                <Stack.Screen
                    name="Tab"
                    component={ BottomNavigation }
                    options={{
                        headerShown: false
                    }}
                />
            </Stack.Navigator>
        </NavigationContainer>
    )
}

export default StackNavigatior;
  • LoginForm
import React from 'react';
import { View, StyleSheet } from 'react-native';
import StyledBorderButton from '../../../styles/common/StyledBorderButton';
import StyledFullButton from '../../../styles/common/StyledFullButton';
import StyledTextInput from '../../../styles/common/StyledTextInput';
import palette from '../../../styles/palette';

const LoginForm = ({ navigation }) => {
    ...

    const onPressMainNavigator = e => {
        e.preventDefault();

        navigation.navigate('Tab');
    };

    return(
        <View style={ styles.loginBox }>
            ...
            <StyledFullButton 
                onPress={ onPressMainNavigator }
                text="Sign in"
            />
            ...
        </View>
    );
};

...

export default LoginForm;

위와 같이 코드를 작성하고 테스트 화면을 보겠습니다.



아이콘과 바텀네비게이션이 잘 작동하는 모습을 보실 수 있습니다.

그러면 HomeScreen의 화면을 구현해보도록 하겠습니다. HomeScreen에서는 종목별로 카테고리를 만들어 종목 아이콘을 클릭하면 그와 관련된 운동 장소를 맵에 띄울 수 있도록 하는 기능을 가지고 있습니다.

  • /home/components/HomeContent.js
import React from 'react';
import { 
    StyleSheet,
    View
} from 'react-native';
import CategoryIcon from '../../../styles/common/CategoryIcon';
import ImageIcon from '../../../styles/common/ImageIcon';
import palette from '../../../styles/palette';

const HomeContent = () => {
    return(
        <View style={ styles.container }>
            <View style={ styles.categoryLine }>
                <CategoryIcon
                    name={ "ios-baseball-outline" }
                    text={ "야구" }
                    type_nm={ "야구장" }
                />
                <CategoryIcon
                    name={ "ios-basketball-outline" }
                    text={ "농구" }
                    type_nm={ "생활체육관" }
                />
                <CategoryIcon
                    name={ "ios-basketball" }
                    text={ "야외농구" }
                    type_nm={ "농구장" }
                />
                <CategoryIcon
                    name={ "ios-football-outline" }
                    text={ "축구" }
                    type_nm={ "축구장" }
                />
            </View>
            <View style={ styles.categoryLine }>
                <ImageIcon
                    name={ "arrow" }
                    text={ "양궁" }
                    type_nm={ "국궁장" }
                />
                <ImageIcon
                    name={ "badminton" }
                    text={ "배드민턴" }
                    type_nm={ "배드민턴장" }
                />
                <ImageIcon
                    name={ "climb" }
                    text={ "클라이밍" }
                    type_nm={ "클라이밍장" }
                />
                <ImageIcon
                    name={ "gateball" }
                    text={ "게이트볼" }
                    type_nm={ "게이트볼장" }
                />
            </View>
            <View style={ styles.categoryLine }>
                <ImageIcon
                    name={ "ice_skate" }
                    text={ "스케이트" }
                    type_nm={ "빙상장" }
                />
                <ImageIcon
                    name={ "inline_skate" }
                    text={ "인라인" }
                    type={ "인라인스케이트장" }
                />
                <ImageIcon
                    name={ "pingpong" }
                    text={ "탁구" }
                    type_nm={ "탁구장" }
                />
                <ImageIcon
                    name={ "sepak_takraw" }
                    text={ "족구" }
                    type_nm={ "족구장" }
                />
            </View>
            <View style={ styles.categoryLine }>
                <ImageIcon
                    name={ "shoot" }
                    text={ "사격" }
                    type_nm={ "사격장" }
                />
                <ImageIcon
                    name={ "swim" }
                    text={ "수영" }
                    type_nm={ "수영장" }
                />
                <ImageIcon
                    name={ "volleyball" }
                    text={ "배구" }
                    type_nm={ "구기체육관" }
                />
                <ImageIcon
                    name={ "volleyball" }
                    text={ "야외배구" }
                    type_nm={ "배구장" }
                />
            </View>
            <View style={ styles.categoryLine }>
                <CategoryIcon
                    name={ "ios-bicycle" }
                    text={ "자전거" }
                    type_nm={ "산악자전거장" }
                />
                <CategoryIcon
                    name={ "ios-school" }
                    text={ "학교체육" }
                    type_nm={ "학교체육시설" }
                />
                <CategoryIcon
                    name={ "ios-football" }
                    text={ "풋살" }
                    type_nm={ "풋살장" }
                />
                <ImageIcon
                    name={ "tennis" }
                    text={ "테니스" }
                    type_nm={ "테니스장" }
                />
            </View>
        </View>
    );
};

const styles = StyleSheet.create({
    container: {
        flexDirection: 'column',
        justifyContent: 'center',
        alignItems: 'center',
        marginTop: 30,
        width: 420,
        backgroundColor: palette.white[0],
    },
    containerTitle: {
        alignItems: 'center',
        justifyContent: 'center',
    },
    textTitle: {
        fontWeight: 'bold',
        fontSize: 20,
    },
    categoryLine: {
        flexDirection: 'row',
        width: 420,
        height: 70,
        marginBottom: 20,
    },
});

export default HomeContent;

카테고리 아이콘들이 들어갈 컴포넌트입니다. 아이콘들을 만들어 보도록 하겠습니다.

  • /styles/common/CategoryIcon.js
import React from 'react';
import { 
    StyleSheet, 
    View, 
    Text, 
    TouchableOpacity 
} from 'react-native';
import Icon from 'react-native-vector-icons/Ionicons';
import palette from '../palette';

const CategoryIcon = ({ 
    name, 
    text 
}) => {
    return(
        <View style={ styles.container }>
            <TouchableOpacity>
                <Icon 
                    size={ 48 }
                    name={ name }
                    color={ palette.blue[4] }
                />
            </TouchableOpacity>
            <Text style={ styles.font }>
                { text }
            </Text>
        </View>
    );
};

const styles = StyleSheet.create({
    container: {
        alignItems: 'center',
        justifyContent: 'center',
        width: 60,
        height: 60,
        margin: 20,
    },
    font: {
        fontWeight: 'bold',
    },
});

export default CategoryIcon;
  • /styles/common/ImageIcon.js
import React from 'react';
import {
    StyleSheet,
    View,
    TouchableOpacity,
    Image,
    Text
} from 'react-native';
import palette from '../palette';
import arrow from '../../assets/img/arrow.png';
import badminton from '../../assets/img/badminton.png';
import climb from '../../assets/img/climb.png';
import gateball from '../../assets/img/gateball.png';
import ice_skate from '../../assets/img/ice_skate.png';
import inline_skate from '../../assets/img/inline_skate.png';
import pingpong from '../../assets/img/pingpong.png';
import sepak_takraw from '../../assets/img/sepak_takraw.png';
import shoot from '../../assets/img/shoot.png';
import swim from '../../assets/img/swim.png';
import volleyball from '../../assets/img/volleyball.png';
import tennis from '../../assets/img/tennis.png';

const ImageIcon = ({ name, text }) => {
    if(name === "arrow") name = arrow
    else if(name === "badminton") name = badminton
    else if(name === "climb") name = climb 
    else if(name === "gateball") name = gateball 
    else if(name === "ice_skate") name = ice_skate
    else if(name === "inline_skate") name = inline_skate
    else if(name === "pingpong") name = pingpong
    else if(name === "sepak_takraw") name = sepak_takraw
    else if(name === "shoot") name = shoot
    else if(name === "swim") name = swim
    else if(name === "volleyball") name = volleyball
    else if(name === 'tennis') name = tennis;

    return(
        <View style={ styles.container }>
            <TouchableOpacity>
                <Image
                    style={ styles.icon }
                    source={ name }
                />
            </TouchableOpacity>
            <Text style={ styles.font }>
                { text }
            </Text>
        </View>
    );
};

const styles = StyleSheet.create({
    container: {
        alignItems: 'center',
        justifyContent: 'center',
        width: 60,
        height: 60,
        margin: 20,
    },
    icon: {
        tintColor: palette.blue[4],
    }, 
    font: {
        fontWeight: 'bold',
    },
});

export default ImageIcon;

ImageIcon은 직접 이미지 파일을 다운받아 만들어 놓은 것이기 때문에 따로 만들었습니다. 이미지, 폰트에 대한 파일들은 깃에 올려놓겠습니다.
https://github.com/biuea3866/MyLifeSports

그리고 HomeScreen의 헤더 부분을 다음과 같이 변경하겠습니다.

  • BottomNavigation.js
import React from 'react';
import { createBottomTabNavigator } from '@react-navigation/bottom-tabs';
import Icon from 'react-native-vector-icons/Ionicons';
import palette from '../styles/palette';
import HomeScreen from '../pages/home/HomeScreen';
import PostScreen from '../pages/post/PostScreen';
import MapScreen from '../pages/map/MapScreen';
import MyPageScreen from '../pages/user/MyPageScreen';

const Tab = createBottomTabNavigator();

const BottomNavigation = ({ route }) => {
    return(
        ...
            <Tab.Screen 
                name="Home"
                options={{
                    title: 'Life Sport',
                    tabBarLabel: 'Home',
                    headerStyle: {
                        backgroundColor: palette.blue[4],
                    },
                    headerTintColor: palette.white[0],
                    headerTitleStyle: {
                        fontWeight: 'bold'
                    },
                }}
                children={
                    () => <HomeScreen />
                }
            />
            ...
};

export default BottomNavigation;

결과 화면입니다.

잘 나온 모습을 볼 수 있습니다. 이런 방식으로 게시글 페이지, 맵 페이지, 마이 페이지 부분을 만들어 보도록 하겠습니다.

0개의 댓글

관련 채용 정보