Development/Android

[Android / Kotlin] Compose에서 NavigationBar를 활용한 하단 Tab 구현

SeungYong.Lee 2024. 10. 1. 22:11
반응형

컴포즈에서 하단에 4칸짜리 탭을 구현해 보겠습니다.

먼저 gradle에 아래 navigation 의존성 추가가 필요합니다.

implementation("androidx.navigation:navigation-compose:2.8.1")
implementation("androidx.compose.material3:material3:1.2.1")

 

탭을 구성하기 위해서는 NavigationBar, NavigationGraph, 각 탭 별 Screen Class가 필요합니다.

그리고 화면에서 NavController를 통해 등록된 화면 간의 이동을 제어하게 됩니다.

 

먼저 필요한 화면에 대한 기본 데이터(route, title, icon....)들을 sealed class로 정의해 줍니다.

sealed class Screen(val route: String, val title: String, val icon: ImageVector) {
    data object Home : Screen("home", "Home", Icons.Default.Home)
    data object Notes : Screen("notes", "Notes", Icons.Default.Search)
    data object Daily : Screen("daily", "Daily", Icons.Default.Check)
    data object Settings : Screen("settings", "Settings", Icons.Default.Settings)
}

 

그리고 sealed class가 선언된 해당 파일에서 NavigationBar, NavigationGraph도 마저 구성해 줍니다.

@Composable
fun BottomNavBar(navController: NavController) {
    val items = listOf(
        Screen.Home,
        Screen.Notes,
        Screen.Daily,
        Screen.Settings
    )

    NavigationBar {
        val navBackStackEntry by navController.currentBackStackEntryAsState()
        val currentRoute = navBackStackEntry?.destination?.route

        items.forEach { screen ->
            NavigationBarItem(
                icon = {
                    Icon(
                        imageVector = screen.icon,
                        contentDescription = screen.title
                    )
                },
                label = { Text(screen.title) },
                selected = currentRoute == screen.route,
                onClick = {
                    navController.navigate(screen.route) {
                        popUpTo(navController.graph.startDestinationId) { saveState = true }
                        launchSingleTop = true
                        restoreState = true
                    }
                }
            )
        }
    }
}

각 NavigationBarItem을 클릭하면 controller는 선택된 screen의 고유 id 값인 route를 통해 탭을 전환하게 됩니다.

 

@Composable
fun NavigationGraph(
    navController: NavHostController,
    dataViewModel: DataViewModel,
    paddingValues: PaddingValues) {
    NavHost(navController, startDestination = Screen.Home.route) {
        composable(Screen.Home.route) { HomeScreen(dataViewModel, paddingValues) }
        composable(Screen.Notes.route) { NotesScreen() }
        composable(Screen.Daily.route) { DailyScreen() }
        composable(Screen.Settings.route) { SettingsScreen() }
    }
}

NavGraph에서는 실제로 필요한 화면 컴포저블을 NavHost에 등록합니다. 
NavHost는 Controller가 작동할 때, route에 따라 화면 전환을 제공하게 됩니다.

 

@Composable
fun HomeScreen(viewModel: DataViewModel, paddingValues: PaddingValues) {
    Box(
        modifier = Modifier
            .fillMaxSize()
            .padding(paddingValues),
        contentAlignment = Alignment.Center
    ) {
        MindBankTheme {
            Surface(
                modifier = Modifier.fillMaxSize(),
                color = MaterialTheme.colorScheme.background
            ) {
                MainScreen(viewModel)
            }
        }
    }
}

@Composable
fun NotesScreen() {
    Box(modifier = Modifier.fillMaxSize(), contentAlignment = Alignment.Center) {
        Text("Notes Screen")
    }
}

....

Screen 화면을 목적에 맞게 구성해 줍니다.

참고로 paddingValues를 인자로 넘겨받으면 하단 탭 높이만큼의 padding을 각 화면에 전달하여 컴포넌트가 가려지지 않도록 할 수 있습니다!

 

이제 Navigation을 적용해 줄 Activity에 아래와 같이 구성하면 완료입니다.

@AndroidEntryPoint
class MainActivity : ComponentActivity() {

    private val dataViewModel: DataViewModel by viewModels()

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContent {
            val navController = rememberNavController()

            Scaffold(
                bottomBar = { BottomNavBar(navController = navController) }
            ) { paddingValues ->
                NavigationGraph(navController, dataViewModel, paddingValues)
            }
        }
    }
}

반응형