본문 바로가기
Android/Kotlin

Android 배워보자 Compose! -(5) Scaffold / BottomNavigation

by wannagohome97 2024. 2. 14.

Scaffold

직역하면 "발판" 이라는 뜻인데 기존에도 사용되던 용어이지만 Android 에선 Compose 로 넘어오면서 생긴 요소입니다.

이름에 걸맞게 레이아웃에서 Top / Bottom Bar 부터 시작해서 SnackBar, FloatingButton, Drawer 와 같이

소위 미리 깔아두는 요소들을 세팅하고 , PaddingValues 를 후행 람다에서 수신하여

안에 들어갈 레이아웃과 겹치거나 하지 않도록 지원해주는 Composable 입니다.

 

그 중에서도 모바일에서 자주 사용되는 하단 네비게이션 바(Bottom Navigation Bar) 가 들어간 Screen 을 Scaffold 를 이용해서 그려봅시다.

 

BottomNavigation

@Composable
fun BottomNavigation(

modifier: Modifier = Modifier,
backgroundColor: Color = MaterialTheme.colors.primarySurface,
contentColor: Color = contentColorFor(backgroundColor),
elevation: Dp = BottomNavigationDefaults.Elevation,
content: @Composable RowScope.() -> Unit

)

 

기본적으로 색상과 elevation 을 지정해 줄 수 있고 , 후행 람다에서 Row 를 채우는 형태입니다.

 

그리고 이 Row 안에 넣을 수 있는 BottomNavigationItem 을 지원해줍니다.

@Composable
fun RowScope.BottomNavigationItem(

selected: Boolean,
onClick: () -> Unit,
icon: @Composable () -> Unit,
modifier: Modifier = Modifier,
enabled: Boolean = true,
label: @Composable (() -> Unit)? = null,
alwaysShowLabel: Boolean = true,
interactionSource: MutableInteractionSource = remember { MutableInteractionSource() },
selectedContentColor: Color = LocalContentColor.current,
unselectedContentColor: Color = selectedContentColor.copy(alpha = ContentAlpha.medium)

)

 

 Selected 는 이전에 공부했던 대로 State 로써 넣어줄 수 있을 것이고,

클릭 이벤트도 자체적으로 구현되어있어서 onClick 파라미터에 후행 람다로 이벤트를 넣어주면 됩니다.

 

일단 더 긴 설명보다도 간단한 예시로 한번 구현해보도록 하겠습니다.

 

data class BottomItem(
    val route: String,
    val labelStringId: Int,
    val iconDrawableId: Int
)
object Destinations{
    const val HOME_ROUTE = "home"
    const val MENU_ROUTE_1 = "menu_1"
    const val MENU_ROUTE_2 = "menu_2"
}

 

BottomNavigationItem 의 Parameter 를 채워줄 data class 와

Navigation 의 Destinations 를 임의로 만들어줍니다.

그리고 이 BottomItem 을 이용하여 하단 메뉴 리스트를 만들어주고

val items = listOf(
    BottomItem(
        route = Destinations.HOME_ROUTE,
        labelStringId = R.string.label_home,
        iconDrawableId = R.drawable.ic_home
    ),
    BottomItem(
        route = Destinations.MENU_ROUTE_1,
        labelStringId = R.string.label_menu_1,
        iconDrawableId = R.drawable.ic_menu_1
    ),
    BottomItem(
        route = Destinations.MENU_ROUTE_2,
        labelStringId = R.string.label_menu_2,
        iconDrawableId = R.drawable.ic_menu_2
    )
)

 

이 메뉴리스트를 이용해서 아래와 같이 BottomBar 에 들어갈 BottomNavigation 을 구현해줍니다.

@Composable
fun BottomBar(navController: NavHostController, items: List<BottomItem>){
    val navBackStackEntry by navController.currentBackStackEntryAsState()
    val currentRoute = navBackStackEntry?.destination?.route
    BottomNavigation(
        backgroundColor = Color.Black,
        contentColor = Color.White
    ) {
        items.forEach {
            BottomNavigationItem(
                modifier = Modifier.size(56.dp),
                selected = currentRoute == it.route,
                onClick = {navController.navigate(it.route) },
                icon = { Icon(painter = painterResource(id = it.iconDrawableId),
                    contentDescription = "") },
                label = { Text(text = stringResource(id = it.labelStringId)) }
            )
        }
    }
}

 

그리고 이 Composable 을 처음 설명했던 Scaffold 의 bottomBar 에 넣어주고,

후행 lambda(Content) 안에 NavHost 를 컴포저블로 감싼 뒤,

Modifier 의 padding 을 Scaffold 의 PaddingValue 로 설정해주면 됩니다.

 

@Composable
fun MainScreen(){
    val navHostController = rememberNavController()
    Scaffold(bottomBar = {
        BottomBar(navController = navHostController, items = items)
    }){
        Box(modifier = Modifier.padding(it)){
            NavHost(navController = navHostController,
                startDestination = Destinations.HOME_ROUTE){
                composable(Destinations.HOME_ROUTE){
                    ResultScreen(result = "홈 화면")
                }
                composable(Destinations.MENU_ROUTE_1){
                    ResultScreen(result = "첫 번째 메뉴 화면")
                }
                composable(Destinations.MENU_ROUTE_2){
                    ResultScreen(result = "두 번째 메뉴 화면")
                }
            }
        }
    }
}

 

 

여기에 사용된 ResultScreen 은 임의로 구현하시면 되지만 , 일단 아래와 같이 간단히 구현했습니다

 

@Composable
fun ResultScreen(result: String){
    Box(modifier = Modifier.fillMaxSize()){
        Text(
            modifier = Modifier.align(Alignment.Center),
            text = result,
            fontSize = 30.sp,
            fontWeight = FontWeight.SemiBold)
    }
}

 

 

아무튼 위에서 구현된 MainScreen 을 Composable 의 Content에서 사용해주시면 됩니다.

override fun onCreate(savedInstanceState: Bundle?) {
    super.onCreate(savedInstanceState)
    setContent {
        ComposableTheme {
            MainScreen()
        }
    }
}

 

아래는 실행 영상 입니다.