본문 바로가기
Android Jetpack Compose/Navigation, Route

Bottom Navigation 구현방법 총정리 # Route

by Developer88 2023. 5. 29.
반응형

오늘은 BottomNavigation 의 구현방법에 대해서 정리해 보도록 하겠습니다.

 

BottomNavigation을 구현하기 위해서는,

Jetpack Compose Navigation 구현방법에 대해서 알고 있으면 도움이 되는데요.

이에 관해서는 아래 글을 참조해 주세요.

>> Navigation 구현 방법 총정리 # Route Jetpack Compose

 

1.  Route 정의

먼저 BottomNavigation 에서 사용할 Route 들을 sealed class 로 정의해 주겠습니다.

badge count 를 활용하기 위해서, badgeCount 를 넣어주었습니다.

 

sealed class Screen(
    val route: String, 
    val title: String, 
    val icon: ImageVector, 
    val badgeCount: Int
) {
    object Home : Screen("home", "Home", Icons.Outlined.Home, 3)
    object Profile : Screen("profile", "Profile", Icons.Outlined.AccountCircle, 5)
    object Settings : Screen("settings", "Settings", Icons.Outlined.Settings, 2)
}

 

 

아래와 같이 enum으로 생성해서 사용해도 됩니다.

 

enum class Screen(
    val route: String, 
    val title: String, 
    val icon: ImageVector, 
    var badgeCount: Int
) {
    Home("home", "Home", Icons.Outlined.Home, 3),
    Profile("profile", "Profile", Icons.Outlined.AccountCircle, 5),
    Settings("settings", "Settings", Icons.Outlined.Settings, 2)
}

 

2. BottomNavigation 

2-1. BottomNavigation 구현

BottomNavigation을 구현하기 위해서,

Scaffold의 bottomBar 블록BottomNavigation을 작성해서 넣어주겠습니다.

 

Scaffold(
    bottomBar = {
        BottomNavigation( ...)
    }
){
 ...
}

 

위에서 작성했던 route들의 리스트들은 navItems 로 만들어주고요.

 

val navItems = listOf(Screen.Home, Screen.Profile, Screen.Settings)

 

 

이렇게 생성한 navItems는 forEach로 BottomNavigationItem객체를 만들어서,

BottomNavigation 블록에 넣어줍니다.

 

BottomNavigationItem의 selected 는,

item의 해당 route가 현재의 route(currentRoute)가 같은지를 결정하기위해서 필요한 것인데요.

currentRoute는 아래와 같이 BackStackEntry에서 얻어와서, 이를 해당 item의 route와 비교해서 결정하게 됩니다.

 

val navBackStackEntry = navController.curretBackStackEntryAsState
val currentRoute = navBackStackEntry?.destination?.route

 

전체 코드를 보면서 이해해 보겠습니다.

 

@Composable
fun App() {
    val navController = rememberNavController()
    val navItems = listOf(Screen.Home, Screen.Profile, Screen.Settings)

    Scaffold(
        bottomBar = {
            BottomNavigation(
                backgroundColor = MaterialTheme.colors.surface,
                contentColor = MaterialTheme.colors.onSurface,
            ) {
                val navBackStackEntry = navController.curretBackStackEntryAsState
                val currentRoute = navBackStackEntry?.destination?.route

                navItems.forEach { screen ->
                    BottomNavigationItem(
                        icon = { 
                            BadgeBox(badgeContent = { 
                                Text("${screen.badgeCount}") 
                            }) {
                                Icon(screen.icon, contentDescription = null)
                            }                            
                        },
                        label = { Text(screen.title) },
                        selected = currentRoute == screen.route,
                        onClick = {
                            navController.navigate(screen.route) {
                                popUpTo(navController.graph.startDestinationId)
                                launchSingleTop = true
                            }
                        },
                        // Colors for selected and unselected items
                        selectedContentColor = Color.White,
                        unselectedContentColor = Color.Blue
                    )
                }
            }            
        } //bottomBar 블록 끝
    ) {
    	//Scaffold 블록시작
        NavHost(navController, startDestination = Screen.Home.route) {
            composable(Screen.Home.route) {
                HomeScreen()
            }
            composable(Screen.Profile.route) {
                ProfileScreen()
            }
            composable(Screen.Settings.route) {
                SettingsScreen()
            }
        }
    }
}

@Composable
fun HomeScreen() { /*...*/ }

@Composable
fun ProfileScreen() { /*...*/ }

@Composable
fun SettingsScreen() { /*...*/ }

 

 

한가지 주의할 점은, 

사용하는 Material버전을 맞추어 주어야 한다는 점 입니다.

Material3가 나오면서, Material 라이브러리와 같이 implement 해서 사용하는 경우가 있는데요.

이 때, Material3와 Material버전을 혼용해서 쓸 경우, 

예를 들어서, Scaffold는 Materail인데, BottomNavigationItem의 Text는 Material3일 경우,

BottomNavigaionItem 의 SelectedContentColor 가  Selected 상태에 따라서 동작하지 않는 경우도 있었습니다.

혹시, SelectedContentColor가 동작하지 않는다면, 잘못된 버전이 import 되지 않았는지 살펴보면 도움이 됩니다.

 

2-2. popUpTo()의 사용

위의 코드에서 보면 아래와 같이 popUpTo()를 사용한 것을 볼 수 있습니다.

이것은 Stack에서 인자로 들어온 route까지 모두 pop 시켜주는 역할을 합니다.

 

즉, NavigationGraph의 가장 처음으로 돌아가게 해 주는 것 입니다.

BottomNavigation이 대부분 화면의 가장 처음이기 때문에 이렇게 해 주는 것 이지요.

 

navController.navigate(screen.route) {
    popUpTo(navController.graph.startDestination) { inclusive = true }
    launchSingleTop = true
}

 

 

그런데 BottomNavigation이 NavigationGraph 의 루트에 있지 않은 경우도 있을 수 있습니다.

예를 들어서, 아래와 같은 경우인데요.

Music 라우트에서 BottomNavigation을 사용하고 있는데,

onClick시에 위와 같은 코드를 사용할 경우, Home 으로 가버리게 됩니다.

Music에 머물러 있어야 하는데 말이지요.

 

MainNavGraph
├─ Home
├─ Settings
└─ Music (BottomNavigation)
   ├─ Tracks
   ├─ Artists
   └─ Albums

 

이런 경우는 BottomNavigation중 하나를 클릭한 경우 아래와 같이 지정을 해 주어야 합니다.

popUpTo(navController.graph.startDestination) 를 사용할 경우, Home 으로 이동해버리기 때문입니다.

 

navController.navigate("music") {
    popUpTo("music") { inclusive = false }
    launchSingleTop = true
}

 

이상으로 BottomNavigation의 구현방법에 대해서 정리해 보았습니다.

 

728x90

댓글