๊ฐœ๋ฐœ ์ผ์ง€ ๐Ÿ’ป/Nutri Capture

Nutri Capture ํ”„๋ก ํŠธ์—”๋“œ - NavigationBar

interfacer_han 2024. 9. 28. 17:20

Nutri Capture ํ”„๋ก ํŠธ์—”๋“œ ๊ฒŒ์‹œ๊ธ€ ์‹œ๋ฆฌ์ฆˆ


#1 ๊ฐœ์š”

#1-1 NavigationBar์— ๋„ฃ์„ ์•„์ด์ฝ˜ ๊ฐ€์ ธ์˜ค๊ธฐ

 

Material Symbols and Icons - Google Fonts

Material Symbols are our newest icons consolidating over 2,500 glyphs in a single font file with a wide range of design variants.

fonts.google.com

NavigationBar์„ ๊ตฌํ˜„ํ•˜๊ธฐ ์œ„ํ•ด์„  ๋จผ์ € ์•„์ด์ฝ˜์ด ํ•„์š”ํ•˜๋‹ค. ์œ„ ํŽ˜์ด์ง€์—์„œ SVG ์•„์ด์ฝ˜์„ ๋‹ค์šด๋กœ๋“œํ–ˆ๋‹ค. ์ด์ „ ๊ฒŒ์‹œ๊ธ€์—์„œ ๋งŒ๋“  NavHost์˜ Destination์€ 3๊ฐœ์ด๋ฏ€๋กœ ๊ฐ๊ฐ์˜ Destination์— ์–ด์šธ๋ฆฌ๋Š” ์•„์ด์ฝ˜ 3๊ฐœ๋ฅผ ๊ณจ๋ผ ๋‹ค์šด๋กœ๋“œ ํ–ˆ๋‹ค (์ฐธ์กฐ: ์ฒซ๋ฒˆ์งธ ์•„์ด์ฝ˜, ๋‘๋ฒˆ์งธ ์•„์ด์ฝ˜, ์„ธ๋ฒˆ์งธ ์•„์ด์ฝ˜).

 

#1-2 NavigationBar ๊ตฌํ˜„

 

[Android] Jetpack Compose - Scaffold

#1 ๊ฐœ์š”#1-1 Scaffold์˜ ์‚ฌ์ „์  ์˜๋ฏธScaffold๋Š” ๋น„๊ณ„((๊ฑด์„ค) ๋†’์€ ๊ณณ์—์„œ ๊ณต์‚ฌ๋ฅผ ํ•  ์ˆ˜ ์žˆ๋„๋ก ์ž„์‹œ๋กœ ์„ค์น˜ํ•œ ๊ฐ€์„ค๋ฌผ)๋ผ๋Š” ๋‹จ์–ด๋กœ ๋ฒˆ์—ญ๋œ๋‹ค. #1-2 Scaffold in Jetpack Compose Jetpack Compose  |  Android Developers์ด

kenel.tistory.com

์œ„ ๊ฒŒ์‹œ๊ธ€์˜ #4-3์—์„œ ์–ธ๊ธ‰ํ–ˆ๋˜ NavigationBar ๋ฐ NavigationItem์„ ๊ตฌํ˜„ํ•œ๋‹ค.

 

#2 ์ฝ”๋“œ

#2-1 SVG ์ด๋ฏธ์ง€๋ฅผ Vector ์ด๋ฏธ์ง€๋กœ ๋ณ€๊ฒฝ

[res] โ†’ [drawable] โ†’ [๋งˆ์šฐ์Šค ์˜ค๋ฅธ์ชฝ ๋ฒ„ํŠผ ํด๋ฆญ] โ†’ [New] โ†’ [Vector Asset]์—์„œ #1-1์—์„œ ๋‹ค์šด๋กœ๋“œํ–ˆ๋˜ 3๊ฐœ์˜ SVG ํŒŒ์ผ์„ Vector ํ˜•ํƒœ๋กœ ๋ณ€ํ™˜ํ•˜์—ฌ ํ”„๋กœ์ ํŠธ์— ์ถ”๊ฐ€ํ•œ๋‹ค.

 

#2-2 Destination ์ •๋ณด๋ฅผ ๋‹ด๋Š” sealed class ์„ ์–ธ

...

class MainActivity : ComponentActivity() {
    ...
    override fun onCreate(savedInstanceState: Bundle?) {
        ...
        setContent {
            NutricapturenewTheme {
                ...

                Scaffold(
                    ...
                ) { innerPadding ->
                    // (2) NavHost ์ˆ˜์ •
                    NavHost(
                        navController = navController,
                        startDestination = Destination.NutrientInputScreen.route,
                        modifier = Modifier.padding(innerPadding)
                    ) {
                        composable(route = Destination.NutrientInputScreen.route) {
                            NutrientInputScreen(
                                scope = scope,
                                snackbarHostState = snackbarHostState
                            )
                        }
                        composable(route = Destination.StatisticsScreen.route) {
                            StatisticsScreen(
                                scope = scope,
                                snackbarHostState = snackbarHostState
                            )
                        }
                        composable(route = Destination.UserInfoScreen.route) {
                            UserInfoScreen(
                                scope = scope,
                                snackbarHostState = snackbarHostState
                            )
                        }
                    }
                }
            }
        }
    }
}

// (1) sealed class ์„ ์–ธ
sealed class Destination(val route: String, val title: String, val iconId: Int) {
    data object NutrientInputScreen : Destination("nutrientInputScreen", "์บก์ฒ˜", R.drawable.pan_tool_alt)
    data object StatisticsScreen : Destination("statisticsScreen", "ํ†ต๊ณ„", R.drawable.stacked_line_chart)
    data object UserInfoScreen : Destination("userInfoScreen", "๋‚ด ์ •๋ณด", R.drawable.manage_accounts)
}

@Composable
fun SampleContent( ... ) { ... }

(1) sealed class ์„ ์–ธ

3๊ฐœ์˜ Destination์„ ์‚ฌ์šฉํ•  ๊ฒƒ์ด๋ฏ€๋กœ 3๊ฐœ์˜ ์ž์‹ ํด๋ž˜์Šค๋ฅผ ๋‘”๋‹ค.

 

(2) NavHost ์„ ์–ธ

ํ•˜๋“œ ์ฝ”๋”ฉํ•ด๋‘์—ˆ๋˜ startDestination ํ”„๋กœํผํ‹ฐ์˜ ๊ฐ’ ๊ทธ๋ฆฌ๊ณ  ๊ฐ Destination์˜ route ์†์„ฑ์˜ ๊ฐ’์„ (1)์—์„œ ๋งŒ๋“  sealed class๋“ค์˜ ๊ฐ’์œผ๋กœ ์ˆ˜์ •ํ•œ๋‹ค.

 

#2-3 NavigationBar ์„ ์–ธ (ํ‹€ ์žก๊ธฐ)

...

class MainActivity : ComponentActivity() {
    ...
    override fun onCreate(savedInstanceState: Bundle?) {
        ...
        setContent {
            NutricapturenewTheme {
                ...

                Scaffold(
                    ...
                    bottomBar = {
                        MainNavigationBar(navController)
                    },
                    ...
                ) { innerPadding ->
                    ...
                }
            }
        }
    }

    @Composable
    private fun MainNavigationBar(navController: NavHostController) {
        /* TODO */
    }
}

sealed class Destination(val route: String, val title: String, val iconId: Int) {
    ...
}

@Composable
fun SampleContent( ... ) { ... }

์ž„์‹œ ์ฑ„์›Œ๋„ฃ๊ธฐ์šฉ์œผ๋กœ Scaffold์˜ bottomBar์— ๋„ฃ์–ด์ฃผ์—ˆ๋˜ BottomAppBar()๋ฅผ ์‚ญ์ œํ•˜๊ณ  ๊ทธ ๋Œ€์‹  ์‚ฌ์šฉ์ž ์ •์˜ ์ปดํฌ์ €๋ธ” ํ•จ์ˆ˜ MainNavigationBar()๋ฅผ ๋„ฃ์–ด์ฃผ์—ˆ๋‹ค. ์ด ํ•จ์ˆ˜์—์„œ NavigationBar๋ฅผ ๊ตฌํ˜„ํ•  ๊ฒƒ์ด๋‹ค (TODO ์ฃผ์„ ๋ถ€๋ถ„). ๋”ฐ๋ผ์„œ Navigation ๋™์ž‘์˜ ์ฃผ์ฒด์ธ NavHostController๋กœ ์ธ์ˆ˜๋กœ ๋ฐ›๊ฒŒ ๋งŒ๋“ ๋‹ค.

 

์•„๋ž˜ ์ฝ”๋“œ๋Š” TODO ์ฃผ์„ ๋ถ€๋ถ„์„ ๊ตฌํ˜„ํ•œ ๊ฒƒ์ด๋‹ค.

 

#2-4 NavigationBar ๋ฐ NavigationBarItem ๊ตฌํ˜„

// (1) NavigationBar์— ๋“ค์–ด๊ฐˆ ์•„์ดํ…œ ๋ฆฌ์ŠคํŠธ
val items = listOf(
    Destination.NutrientInputScreen,
    Destination.StatisticsScreen,
    Destination.UserInfoScreen
)

NavigationBar {
    // (2) ํ˜„์žฌ ๋ณด์—ฌ์ง€๊ณ  ์žˆ๋Š” Destination ์ •๋ณด์— ๋Œ€ํ•œ Getter
    val currentRoute = navController.currentDestination?.route

    items.forEach { item ->
        NavigationBarItem(
            selected = (item.route == currentRoute),
            onClick = {
                navController.navigate(item.route) {
                    // (3) popUpTo ๋™์ž‘() ์˜ต์…˜ (์„ค๋ช… ์ฐธ์กฐ)
                    popUpTo(navController.graph.findStartDestination().id) {
                        saveState = true
                    }
                    // ๋ฐฑ์Šคํƒ์— ๋™์ผํ•œ Destination์ด ํ˜น์‹œ ์žˆ๋‹ค๋ฉด ๊ทธ๊ฑธ ์žฌ์‚ฌ์šฉํ•˜๊ฒŒ ๋งŒ๋“œ๋Š” ์˜ต์…˜
                    launchSingleTop = true
                    // ์ด์ „์— ํ•ด๋‹น Destination์— ์ €์žฅํ–ˆ๋˜ ์ƒํƒœ(์ž…๋ ฅํ–ˆ๋˜ ๋ฌธ์ž์—ด, ์Šคํฌ๋กค ์œ„์น˜ ๋“ฑ)๊ฐ€ ์žˆ๋‹ค๋ฉด ๋ถˆ๋Ÿฌ์˜ค๊ฒŒ ๋งŒ๋“œ๋Š” ์˜ต์…˜
                    restoreState = true
                }
            },
            icon = {
                Icon(
                    painterResource(id = item.iconId),
                    contentDescription = item.title
                )
            }
            // (4) label์€ ๋””์ž์ธ์  ์ด์œ ๋กœ ์ œ๊ฑฐํ•จ (์„ค๋ช… ์ฐธ์กฐ)
//          label = { Text(text = item.title) }
        )
    }
}

(1) NavigationBar์— ๋“ค์–ด๊ฐˆ ์•„์ดํ…œ ๋ฆฌ์ŠคํŠธ

Destination ํด๋ž˜์Šค์˜ ์ž์‹ ํด๋ž˜์Šค๋“ค์„ ๋„ฃ์—ˆ๋‹ค. 

 

(2) ํ˜„์žฌ ๋ณด์—ฌ์ง€๊ณ  ์žˆ๋Š” Destination ์ •๋ณด์— ๋Œ€ํ•œ Getter

ํ˜„์žฌ Navigation์ด ์–ด๋–ค Destination ์ •๋ณด๋ฅผ ๋‹ด๊ณ  ์žˆ๋Š” ์ง€ ์•Œ๋ ค์ฃผ๋Š” ๋ณ€์ˆ˜๋‹ค. ์ด ๋ณ€์ˆ˜๋Š” NavigationBarItem์˜ selected ํ”„๋กœํผํ‹ฐ์˜ ๊ฐ’์„ ๊ฒฐ์ •ํ•  ๋•Œ ์‚ฌ์šฉ๋œ๋‹ค. selected๊ฐ€ true์ผ๋•Œ, NavigationBaItem์˜ ์•„์ด์ฝ˜์„ ๋” ๊ตต๊ฒŒ ํ‘œ์‹œํ•˜๋Š” ์‹์œผ๋กœ ํ™œ์šฉ๊ฐ€๋Šฅํ•œ ํ”„๋กœํผํ‹ฐ๋‹ค.

 

(3) popUpTo() ๋™์ž‘ ์˜ต์…˜

NavigationBar์˜ ์•„์ดํ…œ์ด 1, 2, 3์œผ๋กœ ์ด 3๊ฐœ ์žˆ๊ณ , startDestination์€ 1์ด๋ผ๊ณ  ๊ฐ€์ •ํ•œ๋‹ค. ํ˜„์žฌ ์„ ํƒ๋œ ์•„์ดํ…œ์€ 3์ด๊ณ , 3์—์„œ ์ด๋Ÿฐ ์ €๋Ÿฐ ๊ณผ์ •์„ ํ†ตํ•ด, 3', 3'', 3'''์˜ Destination์— ์œ„์น˜ํ•œ ์ƒํƒœ๋‹ค. ์ด ์ƒํƒœ์—์„œ NavigationBar์˜ ์•„์ดํ…œ 2๋ฅผ ํด๋ฆญํ•ด์„œ ํ•ด๋‹น Destination์— ๋ฐฉ๋ฌธํ•˜๋ ค๊ณ  ํ•œ๋‹ค.

 

์ด๋•Œ popUpTo() ์˜ต์…˜์ด ์ ์šฉ๋œ NavController.navigate()๊ฐ€ ์ˆ˜ํ–‰๋˜๋ฉด 2๊นŒ์ง€ ๋Œ์•„๊ฐ€๊ธฐ ์œ„ํ•ด ๋˜๋Œ์•„๊ฐ„ ๊ฒฝ๋กœ์˜ ๋ชจ๋“  Destination์„ ๋ฐฑ์Šคํƒ(์ด ๊ฒŒ์‹œ๊ธ€์˜ #2-2 ์ฐธ์กฐ)์—์„œ ์‚ญ์ œํ•œ๋‹ค. ์ด ๋•Œ, popUpTo()์˜ ์ธ์ˆ˜๋กœ ๋“ค์–ด๊ฐ€๋Š” Destination์˜ id๊ฐ’์€ ๋ฐฑ์Šคํƒ์„ ์–ด๋”” ๋ฏธ๋งŒ๊นŒ์ง€ ์‚ญ์ œํ•˜๋Š” ์ง€์˜ ๊ธฐ์ค€์ด ๋œ๋‹ค. ์œ„ ์ฝ”๋“œ์ฒ˜๋Ÿผ navController.graph.findStartDestination().id๋ฅผ ๊ธฐ์ค€ ์‚ผ์œผ๋ฉด startDestination๊นŒ์ง€์˜ ๋ชจ๋“  Destination์ด ์ œ๊ฑฐ๋˜๋Š”๋ฐ, startDestination == ์ตœ์ƒ์œ„ Destination์ด๋ฏ€๋กœ ์ตœ์ƒ์œ„ Destination์„ ์ œ์™ธํ•˜๊ณ  ์ „๋ถ€ ์ œ๊ฑฐํ•˜๊ฒ ๋‹ค๋Š” ์˜๋ฏธ๊ฐ€ ๋œ๋‹ค.

(4) label์€ ๋””์ž์ธ์  ์ด์œ ๋กœ ์ œ๊ฑฐํ•จ

#4-1์— ์ด label ์†์„ฑ์„ ์ •์˜ํ•ด์ค€ ๋ฒ„์ „๊ณผ ์ƒ๋žตํ•œ ๋ฒ„์ „ ๋‘˜ ๋‹ค ํ™•์ธํ•  ์ˆ˜ ์žˆ๋‹ค. ๋‚˜๋Š” ์•„์ด์ฝ˜๋งŒ์œผ๋กœ Destination์— ๋Œ€ํ•ด ์ถฉ๋ถ„ํžˆ ์ง๊ด€์  ์„ค๋ช…์ด ๊ฐ€๋Šฅํ•˜๋‹ค๊ณ  ์ƒ๊ฐํ•œ๋‹ค. ๋”ฐ๋ผ์„œ ์ƒ๋žตํ–ˆ๋‹ค. ๋‚˜์ค‘์— ์‚ฌ์šฉ์ž ํ”ผ๋“œ๋ฐฑ์„ ๋ฐ›์•„, label์ด ์žˆ๋Š” ํŽธ์ด ์‚ฌ์šฉ์ž ๊ฒฝํ—˜์—์„œ ๊ณต๋ฆฌ์ ์ธ ์ด๋“์ด ์žˆ๋‹ค๊ณ  ์ƒ๊ฐ๋œ๋‹ค๋ฉด ๋‹ค์‹œ ์ถ”๊ฐ€ํ•˜๊ฒ ๋‹ค.

 

#3 ์š”์•ฝ

NavigationBar๋ฅผ ๋งŒ๋“ค์—ˆ๋‹ค.

 

#4 ์™„์„ฑ๋œ ์•ฑ

#4-1 ์Šคํฌ๋ฆฐ์ƒท

 

#4-2 ์ด ๊ฒŒ์‹œ๊ธ€ ์‹œ์ ์˜ Commit

 

GitHub - Kanmanemone/nutri-capture-new

Contribute to Kanmanemone/nutri-capture-new development by creating an account on GitHub.

github.com

 

#4-3 ๋ณธ ํ”„๋กœ์ ํŠธ์˜ ๊ฐ€์žฅ ์ตœ์‹  Commit

 

GitHub - Kanmanemone/nutri-capture-new

Contribute to Kanmanemone/nutri-capture-new development by creating an account on GitHub.

github.com