이 프로젝트에서는 BottomNavigation 컴포넌트를 이용해 내비게이션 바를 액티비티에 추가하고 Scaffold 컴포넌트를 간단히 이용해 머티리얼 테마 가이드라인을 준수하는 표준 화면 레이아웃을 만들어본다.
완성된 프로젝트에는 세개의 화면이 존재하고 하단 내비게이션 바를 통해 이동할 수 있다. 먼저 Sealed class로 3개의 목적지에 대한 경로를 추가한다.
sealed class NavRoutes(val route: String) {
data object Home: NavRoutes("home")
data object Contacts: NavRoutes("contacts")
data object Favorites: NavRoutes("favorites")
}
내비게이션 바에 표시되는 각 아이템은 제목, 아이콘, 아이템이 클릭되었을 때 앱이 이동할 경로 정보를 가져야 한다. MainActivity 파일을 가능한 한 단순하게 유지하기 위해, 바 아이템 클래스를 다른 파일에 선언하고 코드를 작성한다.
data class BarItem(
val title: String,
val image: ImageVector,
val route: String
)
BarItem 클래스를 만들었으므로, 이를 이용해 3개의 바 아이템을 포함하는 리스트를 만든다.
object NavBarItems {
val BarItems = listOf(
BarItem(
title = "Home",
image = Icons.Filled.Home,
route = "home"
),
BarItem(
title = "Contacts",
image = Icons.Filled.Face,
route = "contacts"
),
BarItem(
title = "Favorites",
image = Icons.Filled.Favorite,
route = "favorites"
)
)
}
내장 머티리얼 테마 아이콘 이미지를 사용하여 디자인하였다. 이 아이콘들을 이용하면 그래픽을 빠르고 쉽게 프로젝트에 추가할 수 있다.
Home.kt
@Composable
fun Home() {
Box(
modifier = Modifier.fillMaxSize()
) {
Icon(
imageVector = Icons.Filled.Home,
contentDescription = "home",
tint = Color.Blue,
modifier = Modifier
.size(150.dp)
.align(Alignment.Center)
)
}
}
Contacts.kt
@Composable
fun Contacts() {
Box(
modifier = Modifier.fillMaxSize()
) {
Icon(
imageVector = Icons.Filled.Face,
contentDescription = "contacts",
tint = Color.Blue,
modifier = Modifier
.size(150.dp)
.align(Alignment.Center)
)
}
}
Favorite.kt
@Composable
fun Favorites() {
Box(
modifier = Modifier.fillMaxSize()
) {
Icon(
imageVector = Icons.Filled.Favorite,
contentDescription = "favorites",
tint = Color.Blue,
modifier = Modifier
.size(150.dp)
.align(Alignment.Center)
)
}
}
MainActivity에 내비게이션 컨트롤러(NavHostController)와 내비게이션 호스트(NavHost) 인스턴스를 만든다.
@Composable
fun MainScreen() {
val navController = rememberNavController()
}
@Composable
fun NavigationHost(navController: NavHostController) {
NavHost(
navController = navController,
startDestination = NavRoutes.Home.route
) {
composable(NavRoutes.Home.route) {
Home()
}
composable(NavRoutes.Contacts.route) {
Contacts()
}
composable(NavRoutes.Favorites.route) {
Favorites()
}
}
}
하단 내비게이션 바는 BottomNavBar라는 이름의 별도 컴포저블 안에 구현한다. 내비게이션 바는 NavSetUp 함수에서 만드는 내비게이션 컨트롤러 인스턴스로 전달된다. 내비게이션 바는 하나의 BottomNavigation 컴포넌트와 3개의 목적지 화면에 대한 BottomNavigationItem 자식으로 구성된다. BottomNavBar는 MainActivity에 추가한다.
@Composable
fun BottomNavBar(navController: NavHostController) {
BottomNavigation {
val backStackEntry by navController.currentBackStackEntryAsState()
val currentRoute = backStackEntry?.destination?.route
NavBarItems.BarItems.forEach { navItem ->
BottomNavigationItem(
selected = currentRoute == navItem.route,
onClick = {
navController.navigate(navItem.route) {
popUpTo(navController.graph.findStartDestination().id) {
saveState = true
}
launchSingleTop = true
restoreState = true
}
},
icon = {
Icon(
imageVector = navItem.image,
contentDescription = navItem.title,
)
},
label = {
Text(text = navItem.title)
}
)
}
}
}
BottomNavigation 호출 안에서 현재 선택된 내비게이션 목적지 경로를 식별할 수 있어야 한다. 이를 위해 내비게이션 컨트롤러의 currentBackStackEntryAsState() 메서드를 호출해 현재 백 스택을 얻는다. 이를 활용해 경로에 접근할 수 있다.
마지막으로 MainScreen 함수에서 Scaffold 컴포넌트를 이용하여 레이아웃을 완성한다. 이 컴포넌트는 템플릿 레이아웃 구조를 제공하며, 이를 이용해 표준 머티리얼 화면 레이아웃을 만들 수 있다. Scaffold는 상단 바, 콘텐츠 영역, 하단 바, 플로팅 액션 버튼, 스낵바, 내비게이션 서랍을 포함한 표준 레이아웃 요소를 위한 슬롯을 갖는다.
@Composable
fun MainScreen() {
val navController = rememberNavController()
Scaffold(
topBar = { TopAppBar(title = { Text("Bottom Navigation Demo")})},
bottomBar = { BottomNavBar(navController = navController) }
) { paddingValue ->
Box(
modifier = Modifier
.fillMaxSize()
.padding(
top = paddingValue.calculateTopPadding(),
bottom = paddingValue.calculateBottomPadding()
)
) {
NavigationHost(navController = navController)
}
}
}
상단바에는 Text 컴포저블을 표시하도록 설정된 TopAppBar 컴포넌트를 이용하고, 화면 콘텐츠 영역에서는 NavigationHost 컴포저블을 이용한다. 마지막으로, 하단 바 위치에는 BottomNavBar를 이용한다. 앱을 실행하면 Contacts 아이템을 여러 차례 클릭한 후 뒤로 가기 버튼을 클릭해 백 스택 항목 중복을 방지하는 코드가 잘 돌아가는 것을 확인할 수 있다.