[Android] 하단 앱 바 내비게이션 구현 방법 (Jetpack Compose)

알린·2025년 1월 27일
0

Android

목록 보기
19/21

Jetpack Compose 기반 하단바 내비게이션 구현

내가 구현하고 싶은 내비게이션의 조건은 다음과 같다.

  1. 아이콘 상태 구분: 선택된 탭과 선택되지 않은 탭의 아이콘이 다르게 표시
  2. 상태 복원 및 인스턴스 관리: 이전 화면의 상태를 복원하며, 새로운 인스턴스를 생성하지 않도록 관리
  3. 공간 활용 최적화: 선택된 탭은 가로로 길게 공간을 활용

구성 요소 관리: 데이터를 BottomNavItem으로 통합하여 재사용성 강화.
상태 기반 UI: 선택 상태에 따라 아이콘과 텍스트 스타일 변경.
효율적 화면 전환: popUpTo와 restoreState를 활용해 상태 복원 및 인스턴스 중복 생성 방지.

피그마에서 디자인한 하단 내비게이션 바

0. Jetpack Compose 사용 이유

  • 코드의 간결성
  • UI 관리의 효율성

1. 하단바 내비게이션의 구성 요소

Scaffold를 사용해 기본 레이아웃을 구성하며, 주요 컴포넌트는 다음과 같다.

  • topBar: 화면 상단에 위치한 툴바
  • bottomBar: 화면 하단의 내비게이션 바
  • NavHost: 화면 간 전환을 관리하는 네비게이션 호스트

2. NavHost를 사용한 화면 관리

Jetpack Compose의 NavHostNavController를 활용하여 여러 화면을 연결하며, 각 화면은 composable로 정의된다.
다음 코드는 MainActivity에서 호출되는 Main 함수 내에 작성한다.

💡 주요 구현 포인트

  • startDestination: 앱의 첫 시작 화면 정의

MainActivity.kt

val navController = rememberNavController()

Scaffold(
    bottomBar = { BottomAppBar(navController = navController) }
) { padding ->
    NavHost(
        navController = navController,
        startDestination = "home",
        modifier = Modifier.padding(padding)
    ) {
        composable("home") { HomeScreen(navController) }
        composable("schedule") { ScheduleScreen(navController) }
        composable("add") { AddScreen(navController) }
        composable("my") { MyScreen(navController) }
    }
}

3. 내비게이션 바 컴포넌트 구현

각 내비게이션 아이템을 담는 전체적인 바 컴포넌트 구현
popUpTo와 restoreState를 활용해 상태 복원 및 인스턴스 중복 생성 방지

💡 주요 구현 포인트

  • currentRoute: 현재 선택된 탭 확인
  • popUpTo: 이전 화면 상태 복원
  • weight: 선택된 탭의 크기를 상대적으로 크게 설정
@Composable
fun BottomAppBar(navController: NavController) {
    val items = listOf(
        BottomNavItem("home", R.drawable.ic_home, R.drawable.ic_home_selected, "Home"),
        BottomNavItem("schedule", R.drawable.ic_schedule, R.drawable.ic_schedule_selected, "Schedule"),
        BottomNavItem("add", R.drawable.ic_add, R.drawable.ic_add_selected, "Add"),
        BottomNavItem("my", R.drawable.ic_my, R.drawable.ic_my_selected, "My")
    )

    val navBackStackEntry = navController.currentBackStackEntryAsState()
    val currentRoute = navBackStackEntry.value?.destination?.route ?: "home"

    Row(
        modifier = Modifier
            .fillMaxWidth()
            .height(80.dp)
            .background(MaterialTheme.colorScheme.tertiary),
        verticalAlignment = Alignment.CenterVertically
    ) {
        items.forEach { item ->
        	// currentRoute: 현재 선택된 탭 확인
            val isSelected = currentRoute == item.route
            BottomNavItem(
                item = item,
                isSelected = isSelected,
                onClick = {
                    navController.navigate(item.route) {
	                    // popUpTo: 이전 화면 상태 복원
                        popUpTo(navController.graph.startDestinationId) { saveState = true }
                        launchSingleTop = true
                        restoreState = true
                    }
                },
                modifier = Modifier.weight(if (isSelected) 2f else 1f) // 선택된 탭은 더 넓게 표시
            )
        }
    }
}

4. 아이템 컴포넌트 구현

item 데이터를 BottomNavItem으로 통합하여 재사용성 강화

💡 주요 구현 포인트

  • 선택 상태 스타일링: 선택된 아이템은 배경색, 텍스트 스타일, 아이콘 색상이 변경
  • 텍스트 표시 조건: 선택된 상태일 때만 라벨 텍스트가 표시
@Composable
fun BottomNavItem(
    item: BottomNavItem,
    isSelected: Boolean,
    onClick: () -> Unit,
    modifier: Modifier = Modifier
) {
    Row(
        modifier = modifier
            .clickable(onClick = onClick)
            .padding(horizontal = 8.dp, vertical = 8.dp)
            .background(
                color = if (isSelected) Color(0x1AFFC700) else Color.Transparent,
                shape = RoundedCornerShape(12.dp)
            )
            .height(48.dp)
            .padding(horizontal = 16.dp, vertical = 8.dp),
        verticalAlignment = Alignment.CenterVertically,
        horizontalArrangement = Arrangement.Center
    ) {
        Icon(
            painter = painterResource(id = if (isSelected) item.selectedIcon else item.icon),
            contentDescription = null,
            tint = if (isSelected) Color(0xFFFFC107) else BottomItemColor,
            modifier = Modifier.size(24.dp)
        )

        if (isSelected) {
            Spacer(modifier = Modifier.width(8.dp))
            Text(
                text = item.label,
                color = Color(0xFFFFC107),
                fontSize = 15.sp,
                fontWeight = androidx.compose.ui.text.font.FontWeight.Bold
            )
        }
    }
}

결과물

피그마에서 디자인한 대로 구현 완료


참고

👉 하단 앱 바 BottomAppBar 구현 안드로이드 공식 문서

profile
Android 짱이 되고싶은 개발 기록 (+ ios도 조금씩,,👩🏻‍💻)

0개의 댓글