[Compose] BottomNavigation 구현하기 (1/2) - 기본편

코코아의 개발일지·2024년 3월 28일
0

✍🏻 요구사항 분석

최근에 Android 스터디를 하면서, Codelab 과정을 따라 컴포즈를 공부하고 있다.
스터디 7주차에 접어들면서 기존에 하던 우테코 프리코스 문제 코드 리뷰가 끝나서 Toss 클론 코딩을 하기로 했다!
항상 xml로만 작업하다가 컴포즈로 작업해보긴 처음이다.
그럼 모든 앱의 기본이 되는 BottomNavigation부터 만들어보기로 하자.

ㄴ 토스에서 캡쳐해온 사진
지금부터 이걸 만들어볼 것이다.

💻 코드 작성

1️⃣ item 이미지 추가

xml을 이용한 코딩과 마찬가지로, res > drawable 폴더에 BottomNavigation 아이템의 이미지를 추가한다.
아이콘 파일은 Figma Community에서 Shin HeeBeen님의 'Toss 모바일 어플리케이션 따라그리기_20230727'에서 따왔고, 현재 토스와 다른 토스페이 부분의 아이콘은 내가 직접 만들었다.

2️⃣ 아이템 클래스 정의 (BottomNavItem.kt)

sealed class BottomNavItem(
    val route: String,
    val title: Int, // 표시될 이름
    val icon: Int // 표시될 아이콘
){
    data object Home: BottomNavItem(
        route = "home",
        title = R.string.tab_home,
        icon = R.drawable.ic_home
    )
    data object Benefit: BottomNavItem(
        route = "benefit",
        title = R.string.tab_benefit,
        icon = R.drawable.ic_benefit
    )
    data object Pay: BottomNavItem(
        route = "pay",
        title = R.string.tab_pay,
        icon = R.drawable.ic_pay
    )
    data object Stock: BottomNavItem(
        route = "stock",
        title = R.string.tab_stock,
        icon = R.drawable.ic_stock
    )
    data object Whole: BottomNavItem(
        route = "whole",
        title = R.string.tab_whole,
        icon = R.drawable.ic_whole
    )
}

아이템에 대한 정보를 포함하는 부분이다.

3️⃣ 화면 이동 정의하기 (BottomNavGraph.kt)

@Composable
fun BottomNavGraph(navController: NavHostController) {
    NavHost(
        navController = navController,
        startDestination = BottomNavItem.Home.route
    ) {
        composable(route = BottomNavItem.Home.route){
            HomeScreen()
        }
        composable(route = BottomNavItem.Benefit.route){
            BenefitScreen()
        }
        composable(route = BottomNavItem.Pay.route){
            PayScreen()
        }
        composable(route = BottomNavItem.Stock.route){
            StockScreen()
        }
        composable(route = BottomNavItem.Whole.route){
            WholeScreen()
        }
    }
}

startDestination는 토스를 켰을 때 기본적으로 나타나는 화면이 홈 화면이므로, BottomNavItem.Home.route로 설정해준다.
각 아이템의 route가 될 스크린도 넣어준다.

@Composable
fun HomeScreen() {
    Box(
        modifier = Modifier
            .fillMaxSize()
            .background(MaterialTheme.colorScheme.background),
        contentAlignment = Alignment.Center
    ) {
        Text(text = "Home Screen", fontSize = 50.sp, fontWeight = FontWeight.Bold, color = Color.Black)
    }
}

우선 위의 코드처럼 흰색 배경에 스크린 이름만 나타나도록 구현해두었다.

4️⃣ 화면에 BottomNavigation 표시하기 (MainScreen.kt)

@Composable
fun MainScreen() {
    val navController = rememberNavController()
    Scaffold(
        bottomBar = { BottomBar(navController = navController)}
    ) {
        Box(Modifier.padding(it)){
            BottomNavGraph(navController = navController)
        }
    }
}

@Composable
fun BottomBar(navController: NavHostController) {
    val screens = listOf<BottomNavItem>(
        BottomNavItem.Home,
        BottomNavItem.Benefit,
        BottomNavItem.Pay,
        BottomNavItem.Stock,
        BottomNavItem.Whole
    )
    val navBackStackEntry by navController.currentBackStackEntryAsState()
    val currentDestination = navBackStackEntry?.destination

    BottomNavigation(
        backgroundColor = MaterialTheme.colorScheme.primaryContainer
    ) {
        screens.forEach { screens ->
            AddItem(item = screens, currentDestination = currentDestination, navController =navController )
        }
    }
}


@Composable
fun RowScope.AddItem(
    item: BottomNavItem,
    currentDestination: NavDestination?,
    navController: NavHostController
) {
    BottomNavigationItem(
        label = { Text(text = stringResource(item.title), fontSize = 10.sp) },
        icon = {
            Icon(
                painter = painterResource(item.icon),
                contentDescription = item.route,
            )
        },
        selected = currentDestination?.hierarchy?.any {
            it.route == item.route
        } == true,
        selectedContentColor = MaterialTheme.colorScheme.onPrimaryContainer,
        unselectedContentColor = MaterialTheme.colorScheme.onSurface,
        onClick = {
            navController.navigate(item.route){
                popUpTo(navController.graph.findStartDestination().id)
                launchSingleTop =true
            }
        }
    )
}

여기까지 하고 빌드를 해보면 BottomNavigation이 이렇게 나타난다!
img


😣 겪었던 어려움

나는 BottomNavigation을 구현하는 부분에서 BottomNavigationItem을 클릭하더라도 택스트 & 색이 바뀌지 않던 이슈가 있었다.

분명 HomeScreen이 탭된 상태인데, 아이콘이 변하지 않는 것이다. 심지어 svg의 배경색으로 설정된 색깔도 아니었고, selectedContentColor를 빨간색이나, 다른 색으로 변경해도 통 색이 바뀌지 않았다.

처음에는 설마 기본 아이콘을 쓰지 않고 svg 이미지를 써서 그런가? 라는 생각이 스쳐갔었는데, 역시나 그럴 리가 없지. import가 잘못 되어있어서 그런 것이었다.
MainScreen.kt

import androidx.compose.material3.Icon
import androidx.compose.material3.Text

이 import를 지워주고 나니 선택한 색상이 정상적으로 변경되었다!! 이걸 못 찾아서 얼마나 고생했는지,, 허무한 이슈였다.

import androidx.compose.material.*

material3 말고 meterial의 Text와 Icon을 써야한다!

📱 완성 화면

gif

클릭한 아이템으로 다른 스크린을 보여주는 코드는 잘 완성됐다.
토스처럼 커스텀하는 방법은 다음 편에서 마저 알아보자.

📚 참고 자료

profile
우당탕탕 성장하는 개발자

0개의 댓글