๋ณธ๋ฌธ ๋ฐ”๋กœ๊ฐ€๊ธฐ
๐Ÿ“ฑ| Android/๐Ÿš€ | Jetpack

[Android, Kotlin] Compose UI ์ •๋ฆฌ(6)

by immgga 2023. 4. 7.

์ด๋ฒˆ ํฌ์ŠคํŒ…์—์„œ๋Š” compose ui๋กœ bottom navigation์„ ๋งŒ๋“œ๋Š” ์˜ˆ์ œ๋ฅผ ์ •๋ฆฌํ•ด ๋ณผ ๊ฒƒ์ด๋‹ค.

 

์ด์ „ ํฌ์ŠคํŒ…

https://rkdrkd-history.tistory.com/46

 

[Android, Kotlin] Compose UI ๊ธฐ๋ก(5)

์ด๋ฒˆ ํฌ์ŠคํŒ…์—์„œ๋Š” compose UI ๊ณต๋ถ€ํ–ˆ๋˜ ๋‚ด์šฉ์„ ๋„์ ์—ฌ๋ณด๊ฒ ๋‹ค. ์ด์ „ ํฌ์ŠคํŒ… https://rkdrkd-history.tistory.com/44 [Android, Kotlin] Compose UI ์ •๋ฆฌ(4) ์ง€๋‚œ ํฌ์ŠคํŒ… https://rkdrkd-history.tistory.com/43 [Android, Kotlin] Compose U

rkdrkd-history.tistory.com

 

compose๋กœ bottom navigation์„ ๋งŒ๋“œ๋Š” ๋ฐฉ๋ฒ•์€ ์—ฌ๋Ÿฌ ๊ฐ€์ง€๊ฐ€ ์žˆ์ง€๋งŒ, ํ•„์ž๋Š” navigation compose์—์„œ ์ œ๊ณต๋˜๋Š” BottomNavigation์„ ์‚ฌ์šฉํ•ด ๋งŒ๋“ค์–ด ๋ณด๊ฒ ๋‹ค.


1. ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ ์ถ”๊ฐ€

app ๋‹จ์œ„์˜ gradle์— ๋‹ค์Œ๊ณผ ๊ฐ™์€ ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ๋ฅผ ์ถ”๊ฐ€ํ•œ๋‹ค.

implementation "androidx.navigation:navigation-compose:2.5.3"

 

 

2. ๋ณธ๊ฒฉ์ ์œผ๋กœ ๊ตฌํ˜„ํ•˜๊ธฐ

์ผ๋‹จ ํ•„์ž๋Š” 3๊ฐœ์˜ ํƒญ(๊ฒ€์ƒ‰, ํ™ˆ, ํ”„๋กœํ•„)์œผ๋กœ ๊ตฌ์„ฑ๋œ bottom navigation์„ ๋งŒ๋“ค์–ด ๋ณด๊ฒ ๋‹ค.

 

1. ํ™”๋ฉด์„ ๋‹ด์•„๋†“๋Š” ํด๋ž˜์Šค

๊ฐ€์žฅ ๋จผ์ € ํ•ด์•ผ ํ•  ๊ฒƒ์€

bottom navigation์— ๋“ค์–ด๊ฐˆ screen(ํ™”๋ฉด)์„ ๋‹ด์•„๋†“๋Š” ํด๋ž˜์Šค๋ฅผ ๋งŒ๋“ค์–ด์•ผ ํ•œ๋‹ค.

sealed class BottomNavItem(
    val icon: Int, val screenRoute: String
) {
    object Search: BottomNavItem(R.drawable.search_icon, Screen.SearchScreen.route)
    object Home: BottomNavItem(R.drawable.home_icon, Screen.HomeScreen.route)
    object Profile: BottomNavItem(R.drawable.profile_icon, Screen.ProfileScreen.route)
}

sealed class๋ฅผ ์“ฐ๋Š” ์ด์œ ?

  • kotlin์˜ sealed class๋Š” ์ถ”์ƒ ํด๋ž˜์Šค๋กœ ์ƒ์†๋ฐ›๋Š” ์ž์‹ ํด๋ž˜์Šค์˜ ์ข…๋ฅ˜๋ฅผ ์ œํ•œํ•˜๋Š” ์—ญํ• ์„ ํ•˜๊ธฐ ๋•Œ๋ฌธ์— ์ปดํŒŒ์ผ๋Ÿฌ์—์„œ sealed class์˜ ์ž์‹ ํด๋ž˜์Šค๊ฐ€ ๋ช‡์ด๋‚˜ ๋˜๋Š”์ง€ ์•Œ ์ˆ˜ ์žˆ๊ธฐ ๋•Œ๋ฌธ์— ๋‚˜์ค‘์— ์“ธ ์ฝ”๋“œ์—์„œ when๋ฌธ์„ ์‚ฌ์šฉํ•  ๋•Œ, else๋ฅผ ๋„ฃ์ง€ ์•Š์•„๋„ ๋˜๋Š” ์žฅ์ ์ด ์žˆ๋‹ค.

์ž์‹ ํด๋ž˜์Šค์˜ ๊ตฌ์„ฑ ์š”์†Œ๋กœ๋Š” ์•„์ด์ฝ˜, route๊ฐ€ ์žˆ๋‹ค(title์„ ์ถ”๊ฐ€ํ•˜๊ณ  ์‹ถ์œผ๋ฉด ๋ถ€๋ชจ ํด๋ž˜์Šค์— title ํŒŒ๋ผ๋ฏธํ„ฐ๋ฅผ ๋งŒ๋“ค์–ด์„œ ๊ตฌํ˜„ํ•˜๋ฉด ๋œ๋‹ค).

์—ฌ๊ธฐ์„œ route๋Š” ํ˜„์žฌ ์Šคํฌ๋ฆฐ์˜ ์ฃผ์†Œ๋ผ๊ณ  ์ƒ๊ฐํ•˜๋ฉด ๋  ๊ฒƒ ๊ฐ™๋‹ค.

(route๋Š” ์ถ”ํ›„์— ๊ตฌํ˜„ํ•  ๋•Œ ์ž์ฃผ ์“ฐ์ด๊ธฐ ๋•Œ๋ฌธ์— ๋ณ€์ˆ˜๋กœ ๋งŒ๋“ค์–ด์„œ ์ €์žฅํ•ด๋†“๋Š”๊ฒŒ ์ข‹์„ ๋“ฏํ•˜๋‹ค.)

 

์„œ๋ก ์ด ๊ธธ์—ˆ์ง€๋งŒ ์ผ๋‹จ ํ•„์ž๋Š” 3๊ฐœ์˜ screen์˜ bottom navigation์„ ๋งŒ๋“ค ๊ฒƒ์ด๊ธฐ ๋•Œ๋ฌธ์— 3๊ฐœ์˜ ์ž์‹ ํด๋ž˜์Šค(object class)๋“ค์„ ๋งŒ๋“ค์–ด์ฃผ์—ˆ๋‹ค.

 

 

2. Screen ๊ฐ„์˜ ์ด๋™์„ ์œ„ํ•œ Navigation๊ตฌํ˜„ํ•˜๊ธฐ

@Composable
fun NavigationGraph(navController: NavHostController) {
    NavHost(
        navController = navController,
        startDestination = BottomNavItem.Home.screenRoute
    ) {
        composable(BottomNavItem.Search.screenRoute) {
            SearchScreen()
        }
        composable(BottomNavItem.Home.screenRoute) {
            HomeScreen()
        }
        composable(BottomNavItem.Profile.screenRoute) {
            ProfileScreen()
        }
    }
}

navigation์€ compose์—์„œ ํ™”๋ฉด์„ ์ด๋™ํ•˜๊ธฐ ์œ„ํ•œ ์žฅ์น˜๋ผ ํ•  ์ˆ˜ ์žˆ๋‹ค.

NavHost์—์„œ startDestination์€ ์•ฑ์ด ์‹œ์ž‘๋˜์—ˆ์„ ๋•Œ ์ฒ˜์Œ์œผ๋กœ ๋‚˜์˜ค๋Š” screen์ด๋ผ๊ณ  ๋ณด๋ฉด ๋˜๊ฒ ๋‹ค.

NavHost์—์„œ ํ˜„์žฌ route์— ๋”ฐ๋ผ์„œ ํŠ€์–ด๋‚˜์˜ฌ(?) screen๋“ค์„ ๊ด€๋ฆฌํ•œ๋‹ค.(composable() ์•ˆ์˜ screen ํ•จ์ˆ˜๋Š” ํ•„์ž๊ฐ€ ๋งŒ๋“  composable ํ•จ์ˆ˜์ด๊ธฐ์— ์ด ๊ธ€์„ ๋ณด์‹œ๋Š” ๋ถ„๋“ค์€ ์ง์ ‘ ๋งŒ๋“ค์–ด๋ณด์‹œ๊ธธ ๋ฐ”๋ž€๋‹ค.)

 

 

3. ์ง„์งœ ๋ณธ๊ฒฉ์ ์œผ๋กœ ๊ตฌํ˜„ํ•˜๊ธฐ(?)

์ด์ œ ์ง„์งœ๋กœ bottom navigation์„ ๊ตฌํ˜„ํ•ด ๋ณด์ž.

@Composable
fun BottomNavigationView(navController: NavController) {
    val items = listOf(
        BottomNavItem.Search,
        BottomNavItem.Home,
        BottomNavItem.Profile
    )
    val navBackStackEntry by navController.currentBackStackEntryAsState()
    val currentRoute = navBackStackEntry?.destination?.route

    BottomNavigation(
        backgroundColor = Color.White,
        contentColor = Color.Black
    ) {
        items.forEach { item ->
            BottomNavigationItem(
                icon = {
                    Icon(
                        painter = painterResource(id = item.icon),
                        contentDescription = null,
                        modifier = Modifier.size(30.dp)
                    )
                },
                selected = currentRoute == item.screenRoute,
                onClick = {
                    navController.navigate(item.screenRoute) {
                        navController.graph.startDestinationRoute?.let {
                            popUpTo(it) { saveState = true }
                        }
                        launchSingleTop = true
                        restoreState = true
                    }
                }
            )
        }
    }
}

๋Œ€์ถฉ ์ฝ”๋“œ์˜ ์—ญํ• ์„ ์„ค๋ช…ํ•˜์ž๋ฉด

  • BottomNavigation ํŒŒ๋ผ๋ฏธํ„ฐ: backgroundColor(๋ฐฐ๊ฒฝ์ƒ‰), contentColor(์•„์ดํ…œ์ด ํด๋ฆญ๋˜์—ˆ์„ ๋•Œ์˜ ์ƒ‰)
  • items: ํ˜„์žฌ ์Šคํฌ๋ฆฐ๋“ค์„ ๋ชจ์•„๋†“์€ ๋ฆฌ์ŠคํŠธ๋กœ, forEach ๋ฌธ์—์„œ ์‚ฌ์šฉํ•˜๊ธฐ ์œ„ํ•ด ๋งŒ๋“  ๋ณ€์ˆ˜์ด๋‹ค.
    (ํ•„์ž์˜ ๊ฒฝ์šฐ์—๋Š” screen๋งˆ๋‹ค ๊ตฌํ˜„ํ•  ๋กœ์ง์ด ๊ฐ™๊ธฐ ๋•Œ๋ฌธ์— list๋กœ ๋งŒ๋“ค์–ด ๊ตฌํ˜„ํ–ˆ์ง€๋งŒ, ๊ทธ๋ ‡์ง€ ์•Š์€ ๊ฒฝ์šฐ์—๋Š” BottomNavigationItem์„ ๋”ฐ๋กœ๋”ฐ๋กœ ๋งŒ๋“ค์–ด ๊ตฌํ˜„ํ•ด ๋ณด์ž)
  • icon: bottom navigation์˜ ์•„์ด์ฝ˜์ด ์œ„์น˜ํ•˜๋Š” ๊ณณ์ด๋‹ค.
  • selected: ํ•ด๋‹น ์•„์ดํ…œ์ด ํด๋ฆญ๋˜์—ˆ์„ ๋•Œ, ํ•ด๋‹น screen์˜ route๋ฅผ currentRoute๋กœ ์„ค์ •ํ•œ๋‹ค.
  • onClick: ํ•ด๋‹น ์•„์ดํ…œ์ด ํด๋ฆญ๋˜์—ˆ์„ ๋•Œ์˜ ๋กœ์ง์„ ์ž‘์„ฑํ•˜๋Š” ๊ณณ์ด๋‹ค.
    onclick ์•ˆ์—๋Š” navigate(route)๋ฅผ ์‚ฌ์šฉํ•ด ํ•ด๋‹น ์Šคํฌ๋ฆฐ์„ ๋งจ ์œ„๋กœ ์˜ฌ๋ ค์„œ(popUpTo()) ์•ฑ์˜ ํ™”๋ฉด์— ๋ณด์ด๊ฒŒ ๋งŒ๋“ค์—ˆ๋‹ค.

 

4. ๋งŒ๋“  ๊ฑฐ ๋ถ™์ด๊ธฐ(?)

์ด์ œ ๋งŒ๋“  bottom navigation์„ activity์— ๋ถ™์—ฌ๋ณด๊ฒ ๋‹ค.

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

@SuppressLint("UnusedMaterial3ScaffoldPaddingParameter")
@OptIn(ExperimentalMaterial3Api::class)
@Composable
fun SetBottomNav() {
    val navController = rememberNavController()

    Scaffold(
        modifier = Modifier.fillMaxSize().systemBarsPadding(),
        bottomBar = {
            BottomNavigationView(navController = navController)
        }
    ) {
        NavigationGraph(navController = navController)
    }
}

Scaffold์˜ bottomBar ํŒŒ๋งˆ๋ฆฌํ„ฐ๋ฅผ ์‚ฌ์šฉํ•ด ๊ตฌํ˜„ํ–ˆ๋‹ค.

๋˜ํ•œ Scaffold ๋ณธ๋ฌธ์— ์•„๊นŒ ์ •์˜ํ•œ navigation์„ ๋‹ฌ์•˜๋‹ค(?)

 

 

5. ๊ฒฐ๊ณผ

search screen

 

home screen

 

profile screen

 


์ •๋ฆฌ

  • xml bottom navigation view์— ์ต์ˆ™ํ•ด์ ธ ์žˆ๋˜ ํ•„์ž์—๊ฒŒ๋Š” compose๋กœ ๊ตฌํ˜„ํ•˜๋Š” ๊ฒƒ์€ ํฐ ๊ณผ์ œ์˜€๋‹ค(ใ… ใ… ).
  • xml ๋•Œ์™€๋Š” ๋‹ค๋ฅธ ๊ฒŒ navigation์„ ์ด์šฉํ•ด bottom navigation์„ ๊ตฌํ˜„ํ•ด์•ผ ํ•œ๋‹ค๋Š” ๊ฒƒ์ด ์ƒ๋‹นํžˆ ํž˜๋“ค์—ˆ์ง€๋งŒ, ์—ญ์‹œ ์™„์„ฑ๋œ ๊ฒฐ๊ณผ๋ฅผ ๋ณด๋ฉด์„œ ๋ฟŒ๋“ฏํ•œ ๊ฐ์ •์„ ์ฐธ์„ ์ˆ˜๊ฐ€(?) ์—†์—ˆ๋‹ค.

 

๋‹ค์Œ ํฌ์ŠคํŒ…์—์„œ๋Š” ํ•„์ž๊ฐ€ search screen, home screen์— ๊ตฌํ˜„๋˜์–ด ์žˆ๋˜ compose๋กœ grid recyclerview ๋งŒ๋“ค๊ธฐ๋ฅผ ์˜ฌ๋ฆด ์˜ˆ์ •(?)์ด๋‹ค.

728x90