코틀린/컴포즈

[코틀린 컴포즈] BottomNavigationBar 구현

  • -
반응형

갑자기 코틀린을 해볼 기회가 생겼다.
그래서 플러터와 비슷해보이는 "코틀린 컴포즈" 라는 라이브러리을 이용해
실제 운영할 앱을 개발해보면서 경험한 것들을 올리려고 한다.

오늘은 BottomNavigationBar을 구현해보자

처음하는 사람이 알아보기 쉽게 적어놓은 블로그가 없어서 많이 힘들었다.
아마 내가 문제인 것 같다..
다른 초보자들은 내가 코딩한 걸 보고 따라할 수 있도록 최대한 쉽게 적어놔야겠다.

의존성 추가

우선 먼저 관련 의존성을 추가해줘야 한다.
다른 블로그나 공식문서에서는 하나만 추가하면 된다고 했는데...

나는 하다보니까 많은 의존성을 추가하라고 요구했다.
초보이기 때문에 일단 하라는 대로 해보자...

dependencies {
    implementation("androidx.compose.material:material:1.3.1")
    implementation("androidx.navigation:navigation-compose:2.7.6")
    implementation("androidx.navigation:navigation-common:2.7.6")
    implementation("androidx.navigation:navigation-runtime:2.7.6")
    implementation("androidx.navigation:navigation-common-ktx:2.7.6")
    implementation("androidx.navigation:navigation-compose:2.7.6")
    implementation("androidx.navigation:navigation-runtime-ktx:2.7.6")
    implementation("androidx.navigation:navigation-runtime-ktx:2.7.6")
    implementation("androidx.activity:activity-ktx:1.8.2")
    implementation("androidx.activity:activity-compose:1.8.2")
    implementation("androidx.emoji2:emoji2:1.4.0")
}

기존에 있던건 지우지말고, 위에 적혀있는 걸 추가하면 된다.

파일 구성

구성할 파일은 총 5개이다.
너무 많다고 생각했는데, 내용을 보니 어렵지 않다.

하나하나씩 한번 살펴보자.

NavRoute.kt

enum class NavRoute(val routeName:String, val description:String) {
    //routeName : 해당 페이지의 경로
    //description : BottomBar에 들어갈 이름
    HomePage("HOME", "홈"),
    FeedPage("FEED", "피드"),
    MyInfoPage("MYINFO", "마이 페이지"),
}

BottomBarItem.kr

sealed class BottomBarItem(
    val title: String, val icon: Int, val pageRoute: String
) {
    //title : BottomBar에 들어갈 이름 연결
    //icon :BottomBar에 들어갈 아이콘 연결
    //pageRoute : BottomBar 해당 페이지의 경로 연결
    object Home : BottomBarItem(
        NavRoute.HomePage.description,
        R.drawable.ic_home, NavRoute.HomePage.routeName)
    object Feed : BottomBarItem(
        NavRoute.FeedPage.description,
        R.drawable.ic_feed, NavRoute.FeedPage.routeName)
    object MyInfo : BottomBarItem(
        NavRoute.MyInfoPage.description,
        R.drawable.ic_person, NavRoute.MyInfoPage.routeName)
}

BottomBarController.kt

@Composable
fun BottomBarController(navController: NavHostController) {
    NavHost(navController = navController, startDestination = NavRoute.HomePage.routeName) {
        composable(NavRoute.HomePage.routeName) {
            Box(
                modifier = Modifier.fillMaxSize(),
                contentAlignment = Alignment.Center
            ) {
                Text(text = "HomePage")
            }
        }
        composable(NavRoute.FeedPage.routeName) {
            Box(
                modifier = Modifier.fillMaxSize(),
                contentAlignment = Alignment.Center
            ) {
                Text(text = "FeedPage")
            }
        }
        composable(NavRoute.MyInfoPage.routeName) {
            Box(
                modifier = Modifier.fillMaxSize(),
                contentAlignment = Alignment.Center
            ) {
                Text(text = "MyInfoPage")
            }
        }
    }
}

BottomBar.kt

@Composable
fun BottomBar(navController: NavController) {
    //상태 관리
    val navBackStackEntry by navController.currentBackStackEntryAsState()
    //현재 페이지가 무엇인지 저장하는 변수
    val currentRoute = navBackStackEntry?.destination?.route
    BottomNavigation(
        //배경 색상 설정
        backgroundColor = Color.White,
        //BottomBar 아이템의 기본 색상 설정
        contentColor = Color(0xFFA0A0A0),
    ){
        BottomNavigationItem(
            icon = {
                Icon( // 아이콘 설정
                    painter = painterResource(id = BottomBarItem.Home.icon),
                    contentDescription = BottomBarItem.Home.title,
                    modifier = Modifier.size(28.dp)
                )
            },
            // BottomBar에 들어갈 이름 설정
            label = { Text(BottomBarItem.Home.title, style = MaterialTheme.typography.labelSmall) },
            // 현재 페이지가 해당 메뉴인지 확인
            selected = currentRoute == BottomBarItem.Home.pageRoute,
            // 해당 메뉴가 선택되었을 때 컬러 설정
            selectedContentColor = Color.Black,
            // 해당 메뉴가 선택되지 않았을 때 컬러 설정
            unselectedContentColor = Color(0xFFA0A0A0),
            // true = 항상 메뉴 이름을 띄운다. false = 선택되어 있을 때만 메뉴 이름을 띄운다.
            alwaysShowLabel = false,
            // 클릭 시, currentRoute 변수에 현재 페이지 루트를 저장한다.
            onClick = {
                navController.navigate(BottomBarItem.Home.pageRoute) {
                    navController.graph.startDestinationRoute?.let {
                        popUpTo(it) { saveState = true }
                    }
                    //화면 인스턴스 하나만 만들어지게 하는 옵션
                    launchSingleTop = true
                    //이전 상태가 남아있게 하는 옵션
                    restoreState = true
                }
            }
        )
        BottomNavigationItem(
            icon = {
                Icon( // 아이콘 설정
                    painter = painterResource(id = BottomBarItem.Feed.icon),
                    contentDescription = BottomBarItem.Feed.title,
                    modifier = Modifier.size(28.dp)
                )
            },
            // BottomBar에 들어갈 이름 설정
            label = { Text(BottomBarItem.Feed.title, style = MaterialTheme.typography.labelSmall) },
            // 현재 페이지가 해당 메뉴인지 확인
            selected = currentRoute == BottomBarItem.Feed.pageRoute,
            // 해당 메뉴가 선택되었을 때 컬러 설정
            selectedContentColor = Color.Black,
            // 해당 메뉴가 선택되지 않았을 때 컬러 설정
            unselectedContentColor = Color(0xFFA0A0A0),
            // true = 항상 메뉴 이름을 띄운다. false = 선택되어 있을 때만 메뉴 이름을 띄운다.
            alwaysShowLabel = false,
            // 클릭 시, currentRoute 변수에 현재 페이지 루트를 저장한다.
            onClick = {
                navController.navigate(BottomBarItem.Feed.pageRoute) {
                    navController.graph.startDestinationRoute?.let {
                        popUpTo(it) { saveState = true }
                    }
                    //화면 인스턴스 하나만 만들어지게 하는 옵션
                    launchSingleTop = true
                    //이전 상태가 남아있게 하는 옵션
                    restoreState = true
                }
            }
        )
        BottomNavigationItem(
                icon = {
                    Icon( // 아이콘 설정
                        painter = painterResource(id = BottomBarItem.MyInfo.icon),
                        contentDescription = BottomBarItem.MyInfo.title,
                        modifier = Modifier.size(28.dp)
                    )
                },
        // BottomBar에 들어갈 이름 설정
        label = { Text(BottomBarItem.MyInfo.title, style = MaterialTheme.typography.labelSmall) },
        // 현재 페이지가 해당 메뉴인지 확인
        selected = currentRoute == BottomBarItem.MyInfo.pageRoute,
        // 해당 메뉴가 선택되었을 때 컬러 설정
        selectedContentColor = Color.Black,
        // 해당 메뉴가 선택되지 않았을 때 컬러 설정
        unselectedContentColor = Color(0xFFA0A0A0),
        // true = 항상 메뉴 이름을 띄운다. false = 선택되어 있을 때만 메뉴 이름을 띄운다.
        alwaysShowLabel = false,
        // 클릭 시, currentRoute 변수에 현재 페이지 루트를 저장한다.
        onClick = {
            navController.navigate(BottomBarItem.MyInfo.pageRoute) {
                navController.graph.startDestinationRoute?.let {
                    popUpTo(it) { saveState = true }
                }
                //화면 인스턴스 하나만 만들어지게 하는 옵션
                launchSingleTop = true
                //이전 상태가 남아있게 하는 옵션
                restoreState = true
            }
        }
        )
    }
}

MainActivity.kt

class MainActivity : ComponentActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContent {
                BottomBarScreen()
        }
    }
}

 

설명이 필요한 코드는 주석을 달아 놓았다.
나도 나중에 다시 와서 봐야지!
코틀린... 정말 어려운 언어다...

반응형
Contents

포스팅 주소를 복사했습니다

이 글이 도움이 되었다면 구독, 공감 부탁드립니다.