[Compose UI] BottomNavigation

LeeEunJae·2022년 12월 26일
0

📌 결과 화면

이번 예제에서는 BottomNavigation 을 통한 화면 전환을 구현 해보겠습니다.

📌 개발 환경 설정

dependencies {
    def nav_version = "2.5.3"

    implementation("androidx.navigation:navigation-compose:$nav_version")
}

📌 테스트용 화면 만들기

@Composable
fun NavScreen(
    text: String,
    backgroundColor: Color
){
    Box(
        modifier = Modifier
            .fillMaxSize()
            .background(backgroundColor)
    ) {
        Text(
            text = text,
            style = MaterialTheme.typography.h1,
            textAlign = TextAlign.Center,
            color = Color.White,
            modifier = Modifier.align(Alignment.Center)
        )
    }
}

매개변수로 text와 backgroundColor 를 받아서 서로 다른 화면을 여러개 만들어 줄겁니다.

BottomNavigation Item

sealed class BottomNavItem(
    val title: String, val icon: Int, val screenRoute: String
){
    object Home: BottomNavItem("홈", R.drawable.ic_home, HOME)
    object Message: BottomNavItem("메세지", R.drawable.ic_message, MESSAGE)
    object Gallery: BottomNavItem("갤러리", R.drawable.ic_image, GALLERY)
    object Profile: BottomNavItem("프로필", R.drawable.ic_person, PROFILE)
}
const val HOME = "HOME"
const val MESSAGE = "MESSAGE"
const val GALLERY = "GALLERY"
const val PROFILE = "PROFILE"

기존방식의 menu 를 만드는 것과 같다고 보시면 됩니다.

📌 NavHost 만들기

@Composable
fun NavigationGraph(navController: NavHostController){
    // NavController : 대상을 이동시키는 요소 - NavHost 내에서 사용
    NavHost(
        navController = navController,
        startDestination = BottomNavItem.Home.screenRoute
    ){
        composable(BottomNavItem.Home.screenRoute){
            NavScreen(text = BottomNavItem.Home.title, backgroundColor = MaterialTheme.colors.primary)
        }
        composable(BottomNavItem.Message.screenRoute){
            NavScreen(text = BottomNavItem.Message.title, backgroundColor = MaterialTheme.colors.primaryVariant)
        }
        composable(BottomNavItem.Gallery.screenRoute){
            NavScreen(text = BottomNavItem.Gallery.title, backgroundColor = MaterialTheme.colors.surface)
        }
        composable(BottomNavItem.Profile.screenRoute){
            NavScreen(text = BottomNavItem.Profile.title, backgroundColor = MaterialTheme.colors.background)
        }
    }
}

NavController 는 앱의 화면과 각 화면 상태를 구성하는 컴포저블의 백 스택을 추적합니다. 즉, 이녀석이 화면 전환을 담당합니다.
컴포저블 계층 구조에서 NavController 를 만드는 위치는 이를 참조해야 하는 모든 컴포저블이 액세스 할 수 있는 곳이어야 합니다. (state Hoisting)

NavHost 의 매개변수로 navController 와 startDestination(그래프 시작 경로) 를 지정해줍니다.
NavHost 는 NavGraphBuilder 를 생성하고, 그안에서 composable() 메소드를 사용해서 탐색 구조에 추가할 수 있습니다.
쉽게 말해 composable() 메서드로 어떤 경로에서 어떤 화면을 보여줄지 결정하게됩니다

BottomNavigationView

@Composable
fun BottomNavigationView(navController: NavHostController){
    val menuItems = listOf(
        BottomNavItem.Home,
        BottomNavItem.Message,
        BottomNavItem.Gallery,
        BottomNavItem.Profile,
    )
    BottomNavigation(
        backgroundColor = Color.White,
        contentColor = Color(0xff3f414e)
    ) {
        // navBackStackEntry 를  가져와서 목적지의 route 를 가져옴
        val navBackStackEntry by navController.currentBackStackEntryAsState()
        val currentRoute = navBackStackEntry?.destination?.route
        
        menuItems.forEach { item->
            BottomNavigationItem(
                icon = {
                       Icon(
                           painter = painterResource(id = item.icon),
                           contentDescription = item.title,
                           modifier = Modifier
                               .width(26.dp)
                               .height(26.dp)
                       )
                }, 
                label = { Text(text = item.title, fontSize = 9.sp) },
                selectedContentColor = MaterialTheme.colors.primary,
                unselectedContentColor = Color(0xFFC2C2C2),
                selected = currentRoute == item.screenRoute,
                alwaysShowLabel = false,
                onClick = {
                    navController.navigate(item.screenRoute) { // 선택된 item 의 route 로 이동
                        // 사용자가 item 을 선택할 때, 백스택에 계속 쌓이지 않도록 graph의 startDestinationRoute 에서 popup
                        // saveState=true 와 restoreState=true 를 통해 화면 전환시 항목의 상태와 백스택이 올바르게 저장되고 복원됨.
                        navController.graph.startDestinationRoute?.let {
                            popUpTo(it) {
                                saveState = true // 상태 저장 }
                            }
                            launchSingleTop = true // 화면 인스턴스 하나만 만들어지게 함
                            restoreState = true // 상태 복원
                        }
                    }
                }
            )
        }
    }
}

BottomNavigationView 를 그리는 Composable 입니다.

BottomNavigationItem 은 item이 선택된 상태인지 아닌지를 알아야 하는데 어떻게 알 수 있을까요?

selected = currentRoute == item.screenRoute,

navController의 currentBackStackEntryAsState() 를 통해 백스택을 가져오고, 백스택에 존재하는 현재 route(경로) 를 가져와서 item의 route와 비교하면 됩니다.

📌 최종 화면 만들기

@Composable
fun MainScreen(){
    val navController = rememberNavController()
    Scaffold(
        bottomBar = { BottomNavigationView(navController = navController)}
    ) {
        Box(modifier = Modifier.padding(it)){
            NavigationGraph(navController = navController)
        }
    }
}

위에서 말했듯이 navController를 참조하는 모든 컴포저블이 액세스 할 수 있는 곳에서 navController 를 생성해야한다고 했습니다.
rememberNavController()를 통해 생성해줍니다.

Scaffold 는 기본 Material design ui 를 구현할 수 있게 해주는 요소 입니다.
bottomBar 를 위에서 만든 bottomNavigationView 컴포저블로 넣어줍니다.

마지막으로 setContent에 MainScreen() 을 넣어주면 완성입니다!

📌 참고자료

안드로이드 공식문서 - navigation

profile
매일 조금씩이라도 성장하자

0개의 댓글