이번 예제에서는 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 를 받아서 서로 다른 화면을 여러개 만들어 줄겁니다.
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 를 만드는 것과 같다고 보시면 됩니다.
@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() 메서드로 어떤 경로에서 어떤 화면을 보여줄지 결정하게됩니다
@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() 을 넣어주면 완성입니다!