๊นจ์•Œ ๊ฐœ๋… ๐Ÿ“‘/Android

[Android] Jetpack Compose - Scaffold

interfacer_han 2024. 9. 5. 18:52

#1 ๊ฐœ์š”

#1-1 Scaffold์˜ ์‚ฌ์ „์  ์˜๋ฏธ

์ด๋ฏธ์ง€ ์ถœ์ฒ˜: https://en.dict.naver.com/#/entry/enko/f85a34c157c64847ad1a1a407d9720a9

Scaffold๋Š” ๋น„๊ณ„((๊ฑด์„ค) ๋†’์€ ๊ณณ์—์„œ ๊ณต์‚ฌ๋ฅผ ํ•  ์ˆ˜ ์žˆ๋„๋ก ์ž„์‹œ๋กœ ์„ค์น˜ํ•œ ๊ฐ€์„ค๋ฌผ)๋ผ๋Š” ๋‹จ์–ด๋กœ ๋ฒˆ์—ญ๋œ๋‹ค.

 

#1-2 Scaffold in Jetpack Compose

 

Jetpack Compose  |  Android Developers

์ด ํŽ˜์ด์ง€๋Š” Cloud Translation API๋ฅผ ํ†ตํ•ด ๋ฒˆ์—ญ๋˜์—ˆ์Šต๋‹ˆ๋‹ค. ์ปฌ๋ ‰์…˜์„ ์‚ฌ์šฉํ•ด ์ •๋ฆฌํ•˜๊ธฐ ๋‚ด ํ™˜๊ฒฝ์„ค์ •์„ ๊ธฐ์ค€์œผ๋กœ ์ฝ˜ํ…์ธ ๋ฅผ ์ €์žฅํ•˜๊ณ  ๋ถ„๋ฅ˜ํ•˜์„ธ์š”. Scaffold ๋จธํ‹ฐ๋ฆฌ์–ผ ๋””์ž์ธ์—์„œ ์Šค์บํด๋“œ๋Š” ์Šค์บํด๋“œ๋กœ

developer.android.com

Jetpack Compose์—๋„ Scaffold๋ผ๋Š” ์ด๋ฆ„์˜ ๊ฐ์ฒด๊ฐ€ ์กด์žฌํ•œ๋‹ค. ์ด Scaffold์— ๋Œ€ํ•ด ๋ˆ„๊ตฐ๊ฐ€ ๋ฌด์—‡์„ ์œ„ํ•œ ๊ฐ€์„ค๋ฌผ์ด๋ƒ๊ณ  ๋ฌป๋Š”๋‹ค๋ฉด, ๋ฐ”๋กœ ๋‹ค์–‘ํ•œ UI ์š”์†Œ๋ฅผ ์œ„ํ•œ ๊ฐ€์„ค๋ฌผ์ด๋ผ๋Š” ๋Œ€๋‹ต์„ ๋ฐ›๊ฒŒ๋  ๊ฒƒ์ด๋‹ค. Scaffold๋Š” UI์„ ์œ„ํ•œ ๊ฐ€์„ค๋ฌผ์ด๋‹ค. ํ˜น์€ ์ผ์ข…์˜ UI์  ๋„๊ตฌ๋“ค์˜ ๋ชจ์ž„์ด๋ผ๊ณ ๋„ ๋งํ•  ์ˆ˜ ์žˆ๋‹ค.

 

#2 Scaffold ์‚ฌ์šฉํ•˜๊ธฐ

#2-1 ๊ธฐ์ดˆ (๊ฐ€์„ค๋ฌผ ์„ธ์šฐ๊ธฐ)

// package com.example.scaffold

import android.os.Bundle
import androidx.activity.ComponentActivity
import androidx.activity.compose.setContent
import androidx.compose.foundation.background
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.padding
import androidx.compose.material3.Scaffold
import androidx.compose.material3.Text
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.unit.sp

class MainActivity : ComponentActivity() {

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContent {
            Scaffold(
                modifier = Modifier.fillMaxSize(),
                /*
                โ†“ โ†“ โ†“
                Scaffold์— '์„ค์น˜'ํ•  UI ์ปดํฌ๋„ŒํŠธ๋“ค์„ ์„ ์–ธํ•  ์œ„์น˜
                โ†‘ โ†‘ โ†‘
                */
            ) { innerPadding -> // Scaffold๊ฐ€ ๊ณ„์‚ฐํ•œ, content๊ฐ€ Scaffold์˜ ๋‹ค๋ฅธ UI ์š”์†Œ๋“ค๊ณผ ๊ฒน์น˜์ง€ ์•Š์„ ์ •๋„์˜ Padding๊ฐ’
                Box(
                    modifier = Modifier
                        .padding(innerPadding)
                        .fillMaxSize()
                        .background(Color.LightGray)
                ) {
                    Text(
                        text = "Content Text",
                        modifier = Modifier.align(Alignment.Center),
                        fontSize = 30.sp
                    )
                }
            }
        }
    }
}

Scaffold์˜ ๋ผˆ๋Œ€๋งŒ ๊ตฌํ˜„ํ–ˆ๋‹ค. #3 ~ #6์—์„œ ์ด ๋ผˆ๋Œ€์— UI ๊ตฌ์„ฑ ์š”์†Œ๋ฅผ ํ•˜๋‚˜์”ฉ ์ถ”๊ฐ€ํ•ด๋ณด๊ฒ ๋‹ค.

 

#2-2 ์ž‘๋™ ํ™•์ธ

์•„๋ฌด๋Ÿฐ UI ๊ตฌ์„ฑ ์š”์†Œ ์—†์ด ๋ผˆ๋Œ€๋งŒ ๊ตฌํ˜„ํ–ˆ๊ธฐ ๋•Œ๋ฌธ์—, Scaffold๊ฐ€ ์—†๋Š” ๊ฒƒ์ฒ˜๋Ÿผ ๋ณด์ธ๋‹ค.

 

#2-3 ์„ธ๋ถ€ ๊ตฌํ˜„ (UI์  ๋„๊ตฌ๋“ค)

@Composable
public fun Scaffold(
    modifier: Modifier = Modifier,
    topBar: @Composable () -> Unit = {}, // #3์—์„œ ๋‹ค๋ฃธ
    bottomBar: @Composable () -> Unit = {}, // #4์—์„œ ๋‹ค๋ฃธ
    snackbarHost: @Composable () -> Unit = {}, // #5์—์„œ ๋‹ค๋ฃธ
    floatingActionButton: @Composable () -> Unit = {}, // #6์—์„œ ๋‹ค๋ฃธ
    floatingActionButtonPosition: FabPosition = FabPosition.End, // #6์—์„œ ๋‹ค๋ฃธ
    containerColor: Color = MaterialTheme.colorScheme.background, // #7์—์„œ ๋‹ค๋ฃธ
    contentColor: Color = contentColorFor(containerColor), // #7์—์„œ ๋‹ค๋ฃธ
    contentWindowInsets: WindowInsets = ScaffoldDefaults.contentWindowInsets, // #7์—์„œ ๋‹ค๋ฃธ
    content: @Composable (PaddingValues) -> Unit
): Unit

(์ฐธ์กฐ) Scaffold์— ๋Œ€ํ•œ ๊ณต์‹ ๋ฌธ์„œ.

 

์ด์ œ, ์ด ๋ผˆ๋Œ€(Scaffold)์— '์„ค์น˜'ํ•  ์ˆ˜ ์žˆ๋Š” UI ์š”์†Œ๋“ค์— ๋Œ€ํ•ด ์‚ดํŽด๋ณด๊ฒ ๋‹ค.

 

#3 topBar

#3-1 ๊ธฐ์ดˆ

...
import androidx.compose.material3.TopAppBar

class MainActivity : ComponentActivity() {

    @OptIn(ExperimentalMaterial3Api::class) // androidx.compose.material3.TopAppBar ๋ฒ„์ „์ด 2024๋…„ 8์›” ๊ธฐ์ค€ ์•„์ง ์•ˆ์ •ํ™”๋˜์ง€ ์•Š์•„์„œ, ์ด ๊ฒฝ๊ณ ์šฉ ์–ด๋…ธํ…Œ์ด์…˜์„ ์š”๊ตฌํ•œ๋‹ค.
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContent {
            Scaffold(
                modifier = Modifier.fillMaxSize(),
                topBar = {
                    TopAppBar(
                        title = { Text("TopBar Text") }
                    )
                }
            ) { innerPadding ->
                ... (#2-1 ์ฐธ์กฐ)
            }
        }
    }
}

Text ํ•˜๋‚˜๋งŒ ์žˆ๋Š” ๊ฐ„๋‹จํ•œ TopAppBar.

 

#3-2 ์ž‘๋™ ํ™•์ธ

 

#3-3 ์„ธ๋ถ€ ๊ตฌํ˜„

@ExperimentalMaterial3Api
@Composable
public fun TopAppBar(
    title: @Composable () -> Unit, // ์ œ๋ชฉ
    modifier: Modifier = Modifier,
    navigationIcon: @Composable () -> Unit = {}, // ์ œ๋ชฉ ์™ผ์ชฝ ๋ฒ„ํŠผ
    actions: @Composable() (RowScope.() -> Unit) = {}, // ์ œ๋ชฉ ์˜ค๋ฅธ์ชฝ ๋ฒ„ํŠผ
    windowInsets: WindowInsets = TopAppBarDefaults.windowInsets, // ์‹œ์Šคํ…œ UI๋ฅผ ๊ณ ๋ คํ•œ ์•”์‹œ์  ํŒจ๋”ฉ ์„ค์ •
    colors: TopAppBarColors = TopAppBarDefaults.topAppBarColors(), // TopAppBar ์† ์ปดํฌ๋„ŒํŠธ๋“ค์˜ ์ƒ‰ ์ •์˜
    scrollBehavior: TopAppBarScrollBehavior? = null // ์Šคํฌ๋กค ์‹œ ๋™์ž‘ ์ •์˜
): Unit

(์ฐธ์กฐ) TopAppBar์— ๋Œ€ํ•œ ๊ณต์‹ ๋ฌธ์„œ.

 

title: @Composable () -> Unit
์ œ๋ชฉ. ์ธ์ˆ˜์˜ ๋ฐ์ดํ„ฐํ˜• @Composable () -> Unit๋ผ๋Š” ๊ฒƒ์—์„œ ๋ณด๋“ฏ ๋‹ค์–‘ํ•œ ์ปดํฌ์ €๋ธ”๋“ค์„ ๋„ฃ์„ ์ˆ˜ ์žˆ๋‹ค. ์ฃผ๋กœ Text ์ปดํฌ์ €๋ธ”์ด ๋“ค์–ด๊ฐ„๋‹ค.

navigationIcon: @Composable () -> Unit
TopAppBar์˜ ์ขŒ์ธก์— ๋ฐฐ์น˜๋  ๋„ค๋น„๊ฒŒ์ด์…˜ ์•„์ด์ฝ˜์„ ์ •์˜. ์ฃผ๋กœ ํ–„๋ฒ„๊ฑฐ ๋ฉ”๋‰ด ์•„์ด์ฝ˜์ด๋‚˜ ๋’ค๋กœ๊ฐ€๊ธฐ ๋ฒ„ํŠผ ๋“ฑ์„ ๋ฐฐ์น˜๋œ๋‹ค.

actions: @Composable() (RowScope.() -> Unit)
TopAppBar์˜ ์šฐ์ธก์— ๋ฐฐ์น˜๋  ์•ก์…˜ ๋ฒ„ํŠผ๋“ค์„ ์ •์˜.

windowInsets: WindowInsets
WindowInsets์€ ์•ˆ๋“œ๋กœ์ด๋“œ ์‹œ์Šคํ…œ UI์— ๋Œ€ํ•œ ์ •๋ณด๋‹ค. ์ด๋ฅผ TopAppBar์— ์ œ๊ณตํ•ด TopAppBar๊ฐ€ ์ด ์˜ฌ๋ฐ”๋ฅธ ์˜์—ญ์— ๊ฒน์ณ์ง ์—†์ด ์ž๋ฆฌ์žก๊ฒŒ ๋งŒ๋“ ๋‹ค. ๊ธฐ๋ณธ๊ฐ’์€ WindowInsets.systemBars๋กœ, ์•ˆ๋“œ๋กœ์ด๋“œ ์‹œ์Šคํ…œ์˜ ์ƒํƒœ๋ฐ” ๋ฐ Navigation Bar(๋’ค๋กœ๊ฐ€๊ธฐ ๋ฒ„ํŠผ, ํ™ˆ ๋ฒ„ํŠผ, ์•ฑ ๋ชฉ๋ก ๋ณด๊ธฐ ๋ฒ„ํŠผ์ด ์žˆ๋Š” ๋ฐ”)๋ฅผ ํ”ผํ•ด์„œ ์ž๋ฆฌ์žก๋„๋ก ๋งŒ๋“œ๋Š” ์˜ต์…˜์ด๋‹ค.

colors: TopAppBarColors

public constructor TopAppBarColors(
    val containerColor: Color, // ๋ฐฐ๊ฒฝ์ƒ‰ ์ง€์ •
    val scrolledContainerColor: Color, // ์‚ฌ์šฉ์ž๊ฐ€ ํ™”๋ฉด์„ ์Šคํฌ๋กคํ•  ๋•Œ TopAppBar์˜ ์ƒ‰ ์ง€์ •
    val navigationIconContentColor: Color, // navigation ๋ฒ„ํŠผ๋“ค์˜ ์ƒ‰ ์ง€์ •
    val titleContentColor: Color, // title ์† content๋“ค์˜ ์ƒ‰ ์ง€์ •
    val actionIconContentColor: Color // action ๋ฒ„ํŠผ๋“ค์˜ ์ƒ‰ ์ง€์ •
)

TopAppBar ์† ๋‹ค์–‘ํ•œ ๊ตฌ์„ฑ ์š”์†Œ๋“ค์˜ ์ƒ‰์„ ์ •์˜. scrolledContainerColor: Color๋Š” ์‚ฌ์šฉ์ž๊ฐ€ ํ™”๋ฉด ์˜์—ญ(Scaffold์˜ ๋ณธ๋ฌธ ์˜์—ญ)์„ ์Šคํฌ๋กคํ•  ๋•Œ TopAppBar๊ฐ€ ์–ด๋–ค ์ƒ‰์œผ๋กœ ๋ณ€ํ• ์ง€๋ฅผ ์ •์˜ํ•œ๋‹ค. ์˜ˆ๋ฅผ ๋“ค์–ด, ์›๋ž˜๋Š” ํˆฌ๋ช…ํ–ˆ๋˜ TopAppBar๊ฐ€ ์Šคํฌ๋กคํ•จ์— ๋”ฐ๋ผ ํŠน์ • ์ƒ‰์„ ๋„๊ฒŒํ•˜๋Š” ์˜ˆ์‹œ๊ฐ€ ๋Œ€ํ‘œ์ ์ด๋‹ค. ๊ธฐ๋ณธ๊ฐ’์€ TopAppBarDefaults.topAppBarColors()๋‹ค.

scrollBehavior: TopAppBarScrollBehavior?

์‚ฌ์šฉ์ž๊ฐ€ ํ™”๋ฉด ์˜์—ญ(Scaffold์˜ ๋ณธ๋ฌธ ์˜์—ญ)์„ ์Šคํฌ๋กคํ•  ๋•Œ TopAppBar๊ฐ€ ์–ด๋–ค ๋™์ž‘์„ ์ˆ˜ํ–‰ํ•  ์ง€ ์ •์˜ํ•œ๋‹ค. ์˜ˆ๋ฅผ ๋“ค์–ด, ์Šคํฌ๋กค์— ๋”ฐ๋ผ TopAppBar๊ฐ€ ์ˆจ๊ฒจ์ง€๊ฑฐ๋‚˜ ๋‹ค์‹œ ๋‚˜ํƒ€๋‚˜๋Š” ์• ๋‹ˆ๋ฉ”์ด์…˜์ด ๋Œ€ํ‘œ์ ์ด๋‹ค. ๊ธฐ๋ณธ๊ฐ’์€ null(= ์ •์˜๋˜์ง€ ์•Š์Œ)์ด๋‹ค.

 

#4 bottomBar

#4-1 ๊ธฐ์ดˆ

...
import androidx.compose.material3.BottomAppBar

class MainActivity : ComponentActivity() {
    
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContent {
            Scaffold(
                modifier = Modifier.fillMaxSize(),
                bottomBar = {
                    BottomAppBar(
                        content = { Text("BottomBar Text") }
                    )
                }
            ) { innerPadding ->
                ... (#2-1 ์ฐธ์กฐ)
            }
        }
    }
}

Text ํ•˜๋‚˜๋งŒ ์žˆ๋Š” ๊ฐ„๋‹จํ•œ BottomAppBar. NavigationBar๋ฅผ BottomAppBar ์ž๋ฆฌ์— ๋„ฃ์„ ์ˆ˜๋„ ์žˆ๋‹ค (#4-3 ์ฐธ์กฐ).

 

 

#4-2 ์ž‘๋™ ํ™•์ธ

 

#4-3 ์„ธ๋ถ€ ๊ตฌํ˜„

@OptIn(markerClass = {androidx. compose. material3.ExperimentalMaterial3Api::class})
@Composable
public fun BottomAppBar(
    modifier: Modifier = Modifier,
    containerColor: Color = BottomAppBarDefaults.containerColor, // ๋ฐฐ๊ฒฝ์ƒ‰ ์ง€์ •
    contentColor: Color = contentColorFor(containerColor), // BottomAppBar ์† ์ปดํฌ๋„ŒํŠธ๋“ค์˜ ๊ธฐ๋ณธ์ƒ‰ ์ง€์ •
    tonalElevation: Dp = BottomAppBarDefaults.ContainerElevation, // ์ƒ‰์„ ํ†ตํ•œ ์‹œ๊ฐ์  ๊ณ„์ธตํ™”์˜ ์ •๋„๊ฐ’
    contentPadding: PaddingValues = BottomAppBarDefaults.ContentPadding, // ๋ช…์‹œ์  ํŒจ๋”ฉ ์„ค์ •
    windowInsets: WindowInsets = BottomAppBarDefaults.windowInsets, // ์‹œ์Šคํ…œ UI๋ฅผ ๊ณ ๋ คํ•œ ์•”์‹œ์  ํŒจ๋”ฉ ์„ค์ •
    content: @Composable() (RowScope.() -> Unit)
): Unit

(์ฐธ์กฐ) BottomAppBar์— ๋Œ€ํ•œ ๊ณต์‹ ๋ฌธ์„œ.

 

containerColor: Color
BottomAppBar์˜ ๋ฐฐ๊ฒฝ ์ƒ‰์ƒ ์„ค์ •. ๊ธฐ๋ณธ๊ฐ’์€ BottomAppBarDefaults.containerColor์ด๋‹ค.


contentColor: Color
BottomAppBar ์† content๋“ค์˜ ๊ธฐ๋ณธ ์ƒ‰์ƒ์„ ์ง€์ •. ๊ธฐ๋ณธ๊ฐ’์€ contentColorFor(containerColor)๋‹ค.

tonalElevation: Dp
UI ์š”์†Œ๊ฐ€ 2๊ฐœ ์ด์ƒ ์ค‘์ฒฉ๋˜์–ด์žˆ์„ ๋•Œ ์ƒ‰(tone)์„ ๊ฐ•์กฐํ•จ์œผ๋กœ์จ UI๋“ค์„ ์‹œ๊ฐ์ ์œผ๋กœ ๊ณ„์ธตํ™”ํ•œ๋‹ค. Dp ๋‹จ์œ„๋กœ ๊ฐ’์„ ์„ค์ •ํ•œ๋‹ค. ๊ธฐ๋ณธ๊ฐ’์€ BottomAppBarDefaults.ContainerElevation๋‹ค.

contentPadding: PaddingValues
BottomAppBar ๋‚ด๋ถ€์˜ Padding(์—ฌ๋ฐฑ)์„ ์„ค์ •. ๊ธฐ๋ณธ๊ฐ’์€ BottomAppBarDefaults.ContentPadding์ด๋‹ค.

windowInsets: WindowInsets
WindowInsets์€ ์•ˆ๋“œ๋กœ์ด๋“œ ์‹œ์Šคํ…œ UI์— ๋Œ€ํ•œ ์ •๋ณด๋‹ค. ์ด๋ฅผ BottomAppBar์— ์ œ๊ณตํ•ด BottomAppBar๊ฐ€ ์ด ์˜ฌ๋ฐ”๋ฅธ ์˜์—ญ์— ๊ฒน์ณ์ง ์—†์ด ์ž๋ฆฌ์žก๊ฒŒ ๋งŒ๋“ ๋‹ค. ๊ธฐ๋ณธ๊ฐ’์€ WindowInsets.systemBars๋กœ, ์•ˆ๋“œ๋กœ์ด๋“œ ์‹œ์Šคํ…œ์˜ ์ƒํƒœ๋ฐ” ๋ฐ Navigation Bar(๋’ค๋กœ๊ฐ€๊ธฐ ๋ฒ„ํŠผ, ํ™ˆ ๋ฒ„ํŠผ, ์•ฑ ๋ชฉ๋ก ๋ณด๊ธฐ ๋ฒ„ํŠผ์ด ์žˆ๋Š” ๋ฐ”)๋ฅผ ํ”ผํ•ด์„œ ์ž๋ฆฌ์žก๋„๋ก ๋งŒ๋“œ๋Š” ์˜ต์…˜์ด๋‹ค.

 

@Composable
public fun NavigationBar(
    modifier: Modifier = Modifier,
    containerColor: Color = NavigationBarDefaults.containerColor, // ๋ฐฐ๊ฒฝ์ƒ‰ ์ง€์ •
    contentColor: Color = MaterialTheme.colorScheme.contentColorFor(containerColor), // NavigationBar ์† ์ปดํฌ๋„ŒํŠธ๋“ค์˜ ๊ธฐ๋ณธ์ƒ‰ ์ง€์ •
    tonalElevation: Dp = NavigationBarDefaults.Elevation, // ์ƒ‰์„ ํ†ตํ•œ ์‹œ๊ฐ์  ๊ณ„์ธตํ™”์˜ ์ •๋„๊ฐ’
    windowInsets: WindowInsets = NavigationBarDefaults.windowInsets, // ์‹œ์Šคํ…œ UI๋ฅผ ๊ณ ๋ คํ•œ ์•”์‹œ์  ํŒจ๋”ฉ ์„ค์ •
    content: @Composable() (RowScope.() -> Unit)
): Unit

(์ฐธ์กฐ) NavigationBar์— ๋Œ€ํ•œ ๊ณต์‹ ๋ฌธ์„œ.

 

containerColor: Color

NavigationBar์˜ ๋ฐฐ๊ฒฝ ์ƒ‰์ƒ ์„ค์ •. ๊ธฐ๋ณธ๊ฐ’์€ NavigationBarDefaults.containerColor๋‹ค.


contentColor: Color

NavigationBar ์† content๋“ค์˜ ๊ธฐ๋ณธ ์ƒ‰์ƒ์„ ์ง€์ •. ๊ธฐ๋ณธ๊ฐ’์€ MaterialTheme.colorScheme.contentColorFor(containerColor)๋‹ค.


tonalElevation: Dp

UI ์š”์†Œ๊ฐ€ 2๊ฐœ ์ด์ƒ ์ค‘์ฒฉ๋˜์–ด์žˆ์„ ๋•Œ ์ƒ‰(tone)์„ ๊ฐ•์กฐํ•จ์œผ๋กœ์จ UI๋“ค์„ ์‹œ๊ฐ์ ์œผ๋กœ ๊ณ„์ธตํ™”ํ•œ๋‹ค. Dp ๋‹จ์œ„๋กœ ๊ฐ’์„ ์„ค์ •ํ•œ๋‹ค. ๊ธฐ๋ณธ๊ฐ’์€ NavigationBarDefaults.Elevation๋‹ค.


windowInsets: WindowInsets

WindowInsets์€ ์•ˆ๋“œ๋กœ์ด๋“œ ์‹œ์Šคํ…œ UI์— ๋Œ€ํ•œ ์ •๋ณด๋‹ค. ์ด๋ฅผ NavigationBar์— ์ œ๊ณตํ•ด NavigationBar๊ฐ€ ์ด ์˜ฌ๋ฐ”๋ฅธ ์˜์—ญ์— ๊ฒน์ณ์ง ์—†์ด ์ž๋ฆฌ์žก๊ฒŒ ๋งŒ๋“ ๋‹ค. ๊ธฐ๋ณธ๊ฐ’์€ WindowInsets.systemBars๋กœ, ์•ˆ๋“œ๋กœ์ด๋“œ ์‹œ์Šคํ…œ์˜ ์ƒํƒœ๋ฐ” ๋ฐ Navigation Bar(๋’ค๋กœ๊ฐ€๊ธฐ ๋ฒ„ํŠผ, ํ™ˆ ๋ฒ„ํŠผ, ์•ฑ ๋ชฉ๋ก ๋ณด๊ธฐ ๋ฒ„ํŠผ์ด ์žˆ๋Š” ๋ฐ”)๋ฅผ ํ”ผํ•ด์„œ ์ž๋ฆฌ์žก๋„๋ก ๋งŒ๋“œ๋Š” ์˜ต์…˜์ด๋‹ค. NavigationBar์™€ Navigation Bar๋Š” ์„œ๋กœ ๋‹ค๋ฅธ ์ข…๋ฅ˜์˜ ๊ฐ์ฒด๋‹ค. ์ „์ž๋Š” ์•ฑ์„ ํƒ์ƒ‰ํ•˜๊ธฐ ์œ„ํ•œ ๊ฒƒ, ํ›„์ž๋Š” ์‹œ์Šคํ…œ ์ž์ฒด๋ฅผ ํƒ์ƒ‰ํ•˜๊ธฐ ์œ„ํ•œ ๊ฒƒ์ด๋‹ค.

 

content: @Composable() (RowScope.() -> Unit)

NavigationBar๋Š” ๋ฐ˜๋“œ์‹œ 3๊ฐœ ์ด์ƒ 5๊ฐœ ์ดํ•˜์˜ NavigationBarItem์„ content๋กœ์„œ ๋ณด์œ ํ•ด์•ผ ํ•œ๋‹ค๋Š” ์ œ์•ฝ ์กฐ๊ฑด์ด ์žˆ๋‹ค. NavigationBar ๋ฐ NavigationBarItem ๊ตฌํ˜„์€ ์ด ๊ฒŒ์‹œ๊ธ€์—์„œ ๋‹ค๋ฃฌ๋‹ค.

 

#5 snackbarHost

#5-1 ๊ตฌํ˜„

...
import androidx.compose.material3.Button
import androidx.compose.material3.Snackbar
import androidx.compose.material3.SnackbarDuration
import androidx.compose.material3.SnackbarHost
import androidx.compose.material3.SnackbarHostState
import androidx.compose.runtime.remember
import androidx.compose.runtime.rememberCoroutineScope
import androidx.compose.ui.unit.dp
import kotlinx.coroutines.launch

class MainActivity : ComponentActivity() {

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContent {
            // Snackbar๋ฅผ ์œ„ํ•œ CoroutineScope์™€ State
            val scope = rememberCoroutineScope()
            val snackbarHostState = remember { SnackbarHostState() }

            Scaffold(
                modifier = Modifier.fillMaxSize(),
                snackbarHost = {
                    SnackbarHost(
                        hostState = snackbarHostState,
                        snackbar = { snackbarData ->
                            Snackbar (
                                modifier = Modifier.padding(12.dp),
                                containerColor = Color.Magenta,
                            ) {
                                Text(
                                    text = snackbarData.visuals.message,
                                    color = Color.White
                                )
                            }
                        }
                    )
                }
            ) { innerPadding ->
                Box(
                    modifier = Modifier
                        .padding(innerPadding)
                        .fillMaxSize()
                        .background(Color.LightGray)
                ) {
                    Button(
                        modifier = Modifier.align(Alignment.Center),
                        onClick = {
                            scope.launch {
                                snackbarHostState.showSnackbar(
                                    message = "Button Clicked",
                                    duration = SnackbarDuration.Short
                                )
                            }
                        }
                    ) {
                        Text("Button", fontSize = 30.sp)
                    }
                }
            }
        }
    }
}

๋ณธ ๊ฒŒ์‹œ๊ธ€์—์„œ, Scaffold์˜ content: @Composable (PaddingValues) -> Unit ๋ถ€๋ถ„์ด #2-1์™€ ๋‹ค๋ฅธ ์œ ์ผํ•œ ์ฝ”๋“œ๋‹ค. SnackbarHostState๋Š” Snackbar์˜ On(๋‚˜ํƒ€๋‚จ)/Off(์‚ฌ๋ผ์ง)์„ ๊ด€๋ฆฌํ•˜๋Š” ๊ฐ์ฒด๋‹ค. Recomposition์— ์˜ํ•ด On/Off ์ƒํƒœ ์ •๋ณด๊ฐ€ ์ดˆ๊ธฐํ™”๋˜์–ด์„œ๋Š” ์•ˆ๋˜๊ธฐ ๋•Œ๋ฌธ์— remember { ... }๋กœ ๊ฐ์‹ผ๋‹ค.

 

๋˜, SnackbarHostState.showSnackbar()๋ฅผ ์œ„์น˜์‹œํ‚ฌ ์žฅ์†Œ์ธ rememberCoroutineScope๋„ ์„ ์–ธํ•œ๋‹ค. rememberCoroutineScope๋Š” ํ•ด๋‹น ์ฝ”๋ฃจํ‹ด ์Šค์ฝ”ํ”„๊ฐ€ ์„ ์–ธ๋œ ์ปดํฌ์ €๋ธ”์˜ ์ƒ๋ช…์ฃผ๊ธฐ์™€ ๊ฒฐํ•ฉ๋œ ์ฝ”๋ฃจํ‹ด ์Šค์ฝ”ํ”„๋‹ค. ํ•ด๋‹น ์ปดํฌ์ €๋ธ”์ด ์•„๋ฌด๋ฆฌ Recomposition๋˜์–ด๋„ ์˜ํ–ฅ์„ ๋ฐ›์ง€ ์•Š๋Š” ์ฝ”๋ฃจํ‹ด ์˜์—ญ์„ ์ „๊ฐœํ•œ๋‹ค. rememberCoroutineScope๋Š” ํ•ด๋‹น ์Šค์ฝ”ํ”„์™€ ์ƒ๋ช…์ฃผ๊ธฐ๊ฐ€ ๊ฒนํ•ฉ๋œ ์ปดํฌ์ €๋ธ”์ด ํ™”๋ฉด์—์„œ ์™„์ „ํžˆ ์‚ฌ๋ผ์ ธ์•ผ๋งŒ ๊ฐ™์ด ์‚ฌ๋ผ์ง„๋‹ค. ์ฆ‰ ์ผ๋ฐ˜์ ์ธ CoroutineScope์™€ ๋น„๊ตํ•˜๋ฉด, ์ฝ”๋ฃจํ‹ด์˜ ์ƒ๋ช…์ฃผ๊ธฐ๋ฅผ ๋ณ„๋„๋กœ ๊ด€๋ฆฌํ•  ํ•„์š”๊ฐ€ ์—†๋‹ค๋Š” ์ด์ ์„ ๊ฐ€์ง„๋‹ค.

 

rememberCoroutineScope๋Š” UI ์ƒ์„ฑ์˜ ๋น„๋™๊ธฐ์  ์ฒ˜๋ฆฌ๋ฅผ ๋ชฉ์ ์œผ๋กœ ์„ค๊ณ„๋˜์—ˆ๋‹ค. ๊ทธ๋ž˜์„œ rememberCoroutineScope์˜ ๊ธฐ๋ณธ ์Šค๋ ˆ๋“œ๋Š” ๋ฉ”์ธ ์Šค๋ ˆ๋“œ๋‹ค. ๋ฉ”์ธ ์Šค๋ ˆ๋“œ๋Š” ์•ˆ๋“œ๋กœ์ด๋“œ์—์„œ UI๋ฅผ ๊ทธ๋ฆด ์ˆ˜ ์žˆ๋Š” ์œ ์ผํ•œ ์Šค๋ ˆ๋“œ๋กœ, ์ฃผ์–ด์ง„ ํ•  ์ผ์ด ๋งŽ๊ธฐ ๋•Œ๋ฌธ์— ๋™๊ธฐ์ ์ธ ์ฝ”๋“œ๋ฅผ ์ถ”๊ฐ€ํ•˜๋ฉด ์•ˆ ๊ทธ๋ž˜๋„ ๋ฐ”์œ ๋ฉ”์ธ ์Šค๋ ˆ๋“œ์— ๋ถ€๋‹ด์„ ์ฃผ๊ฒŒ ๋œ๋‹ค. ์ด ๋•Œ rememberCoroutineScope๋ฅผ ์‚ฌ์šฉํ•˜๋ฉด ๋ฉ”์ธ ์Šค๋ ˆ๋“œ์˜ ๋ง‰ํž˜(Blocking)์—†๋Š” ๋™์ž‘์„ ๋ณด์žฅํ•  ์ˆ˜ ์žˆ๋‹ค. ๊ทธ๋ ‡๊ธฐ์— UI์— ์Šค๋‚ต๋ฐ”๋ฅผ ํ‘œ์‹œํ•˜๋Š” ์ฝ”๋“œ์ธ SnackbarHostState.showSnackbar()๋ฅผ ์ฝ”๋ฃจํ‹ด ์˜์—ญ์— ๋„ฃ์Œ์œผ๋กœ์จ, ๋น„๋™๊ธฐ์ ์œผ๋กœ ์‹คํ–‰ํ•˜๋Š” ๊ฒƒ์ด๋‹ค (๊ฐ™์€ ์Šค๋ ˆ๋“œ ๋‚ด์—์„œ ์„œ๋กœ ๋‹ค๋ฅธ 2๊ฐœ ์ด์ƒ์˜ ์ž‘์—…์„ ๋น„๋™๊ธฐ์ ์œผ๋กœ ์ˆ˜ํ–‰ํ•œ๋‹ค๋Š” ๋ง์€ ์ฝ”๋ฃจํ‹ด์—์„œ ๋ชจ์ˆœ์ด ์•„๋‹ˆ๋‹ค (์ด ๊ฒŒ์‹œ๊ธ€์˜ #4 ์ฐธ์กฐ)).

 

#5-2 ์ž‘๋™ ๊ธฐ์ œ

#5-1 ์† Snackbar ์ฝ”๋“œ์˜ ์ž‘๋™ ๊ธฐ์ œ๋‹ค.

 

#5-3 ์ž‘๋™ ํ™•์ธ

๋ฒ„ํŠผ์„ ๋ˆ„๋ฅด๋ฉด Snackbar๊ฐ€ ๋œฌ๋‹ค.

 

#5-4 ์„ธ๋ถ€ ๊ตฌํ˜„

@Composable
public fun SnackbarHost(
    hostState: SnackbarHostState, // Snackbar์˜ On/Off ๊ด€๋ฆฌ์ž
    modifier: Modifier = Modifier,
    snackbar: @Composable (SnackbarData) -> Unit = { Snackbar(it) } // Snackbar์˜ ๋ชจ์–‘ ์„ค์ •
): Unit

(์ฐธ์กฐ) SnackbarHost์— ๋Œ€ํ•œ ๊ณต์‹ ๋ฌธ์„œ.

 

hostState: SnackbarHostState

์Šค๋‚ต๋ฐ”์˜ ์ƒํƒœ(State)๋ฅผ ๊ด€๋ฆฌ. ์ฆ‰, ์Šค๋‚ต๋ฐ”๊ฐ€ '์ˆจ๊ฒจ์ ธ ์žˆ๋Š” ์ƒํƒœ'์ธ์ง€, 'ํ‘œ์‹œ๋˜๊ณ  ์žˆ๋Š” ์ƒํƒœ'์ธ์ง€๋ฅผ ๊ด€๋ฆฌ. SnackbarHostState.showSnackbar()๋ฅผ ํ•˜๋ฉด ์Šค๋‚ต๋ฐ”๊ฐ€ ํ‘œ์‹œ๋˜๊ณ , SnackbarHostState.currentSnackbarData?.dismiss()๋กœ ํ‘œ์‹œ ์ค‘์ธ ์Šค๋‚ต๋ฐ”๋ฅผ ์ˆจ๊ธธ ์ˆ˜ ์žˆ๋‹ค. ํ•˜์ง€๋งŒ, ์Šค๋‚ต๋ฐ”๋ฅผ ๋ช…์‹œ์ ์œผ๋กœ ์ˆจ๊ธฐ๋Š” ์ฝ”๋“œ๋Š” ๊ฑฐ์˜ ์“ฐ์ด์ง€ ์•Š๋Š”๋‹ค๊ณ  ํ•œ๋‹ค. ์Šค๋‚ต๋ฐ”๋Š” ํ‘œ์‹œ๋˜๊ณ  ์ผ์ • ์‹œ๊ฐ„์ด ์ง€๋‚˜๋ฉด (ํ† ์ŠคํŠธ ๋ฉ”์‹œ์ง€์ฒ˜๋Ÿผ) ์•Œ์•„์„œ ์ˆจ๊ฒจ์ง€๊ธฐ ๋•Œ๋ฌธ์ด๋‹ค.

 

snackbar: @Composable (SnackbarData) -> Unit

@Composable
public fun Snackbar(
    modifier: Modifier = Modifier,
    action: @Composable() (() -> Unit)? = null, // Snackbar์— ํฌํ•จ๋  ์ž‘์—… ๋ฒ„ํŠผ ์ง€์ • (null์ด๋ฉด ํ‘œ์‹œ ์•ˆ ๋จ)
    dismissAction: @Composable() (() -> Unit)? = null, // Snackbar๋ฅผ ๋‹ซ๋Š” ๋ฒ„ํŠผ ์ง€์ • (null์ด๋ฉด ํ‘œ์‹œ ์•ˆ ๋จ)
    actionOnNewLine: Boolean = false, // action(์ž‘์—… ๋ฒ„ํŠผ)์„ ์ƒˆ๋กœ์šด ์ค„์— ํ‘œ์‹œํ•  ์ง€ ์—ฌ๋ถ€ ์„ค์ •
    shape: Shape = SnackbarDefaults.shape, // ๋ชจ์–‘ ์ง€์ •
    containerColor: Color = SnackbarDefaults.color, // ๋ฐฐ๊ฒฝ์ƒ‰ ์ง€์ •
    contentColor: Color = SnackbarDefaults.contentColor, // Snackbar ์† ์ปดํฌ๋„ŒํŠธ๋“ค์˜ ๊ธฐ๋ณธ์ƒ‰ ์ง€์ •
    actionContentColor: Color = SnackbarDefaults.actionContentColor, // action(์ž‘์—… ๋ฒ„ํŠผ)์˜ ๊ธฐ๋ณธ์ƒ‰ ์ง€์ •
    dismissActionContentColor: Color = SnackbarDefaults.dismissActionContentColor, // dismissAction(๋‹ซ๊ธฐ ๋ฒ„ํŠผ)์˜ ๊ธฐ๋ณธ์ƒ‰ ์ง€์ •
    content: @Composable () -> Unit
): Unit

Snackbar์˜ ๋ชจ์–‘์„ ์ •์˜ํ•œ๋‹ค.

 

SnackbarHostState.showSnackbar()

public final suspend fun showSnackbar(
    message: String, // Snackbar์— ํ‘œ์‹œํ•  ๋ฌธ์ž์—ด
    actionLabel: String? = null, // Snackbar์— ํฌํ•จ๋  ํ…์ŠคํŠธ ๋ชจ์–‘ ์ž‘์—… ๋ฒ„ํŠผ ์ง€์ • (null์ด๋ฉด ํ‘œ์‹œ ์•ˆ ๋จ)
    withDismissAction: Boolean = false, // Snackbar๋ฅผ ๋‹ซ๋Š” ๋ฒ„ํŠผ ์œ ๋ฌด ์„ค์ •
    duration: SnackbarDuration = if (actionLabel == null) SnackbarDuration. Short else SnackbarDuration. Indefinite // Snackbar ํ‘œ์‹œ ์ง€์† ์‹œ๊ฐ„ ์„ค์ •
): SnackbarResult

hostState๊ฐ€ ์‚ฌ์šฉํ•  ํ™•์žฅ ํ•จ์ˆ˜๋‹ค.

 

#6 floatingActionButton

#6-1 ๊ธฐ์ดˆ

...
import android.widget.Toast
import androidx.compose.material3.FabPosition
import androidx.compose.material3.FloatingActionButton

class MainActivity : ComponentActivity() {

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContent {
            Scaffold(
                modifier = Modifier.fillMaxSize(),
                floatingActionButton = {
                    FloatingActionButton(
                        onClick = {
                            Toast.makeText(this@MainActivity, "FAB Clicked", Toast.LENGTH_SHORT).show()
                        }
                    ) {
                        Text(
                            "F A B"
                        )
                    }
                },
                floatingActionButtonPosition = FabPosition.End
            ) { innerPadding ->
                ... (#2-1 ์ฐธ์กฐ)
            }
        }
    }
}

ํด๋ฆญ ์‹œ ํ† ์ŠคํŠธ ๋ฉ”์‹œ์ง€๋ฅผ ๋„์šฐ๋Š” ํ”Œ๋กœํŒ… ์•ก์…˜ ๋ฒ„ํŠผ.

 

#6-2 ์ž‘๋™ ํ™•์ธ

ํ”Œ๋กœํŒ… ์•ก์…˜ ๋ฒ„ํŠผ์„ ๋ˆ„๋ฅด๋ฉด ํ† ์ŠคํŠธ ๋ฉ”์‹œ์ง€๊ฐ€ ๋œฌ๋‹ค.

 

#6-3 ์„ธ๋ถ€ ๊ตฌํ˜„

@Composable
public fun FloatingActionButton(
    onClick: () -> Unit, // ํด๋ฆญ ์‹œ ๋™์ž‘ ์ •์˜
    modifier: Modifier = Modifier,
    shape: Shape = FloatingActionButtonDefaults.shape, // ๋ชจ์–‘ ์ง€์ •
    containerColor: Color = FloatingActionButtonDefaults.containerColor, // ๋ฐฐ๊ฒฝ์ƒ‰ ์ง€์ •
    contentColor: Color = contentColorFor(containerColor), // FloatingActionButton ์† ์ปดํฌ๋„ŒํŠธ๋“ค์˜ ๊ธฐ๋ณธ์ƒ‰ ์ง€์ •
    elevation: FloatingActionButtonElevation = FloatingActionButtonDefaults.elevation(), // ๊ทธ๋ฆผ์ž ํšจ๊ณผ๋ฅผ ํ†ตํ•œ ์‹œ๊ฐ์  ๊ณ„์ธตํ™”์˜ ์ •๋„๊ฐ’
    interactionSource: MutableInteractionSource = remember { MutableInteractionSource() }, // ๋ฒ„ํŠผ ๋™์ž‘ ์ปค์Šคํ…€(Custom)
    content: @Composable () -> Unit
): Unit

(์ฐธ์กฐ) FloatingActionButton์— ๋Œ€ํ•œ ๊ณต์‹ ๋ฌธ์„œ.

 

shape: Shape

FloatingActionButton์˜ ๋ชจ์–‘์„ ์ •์˜. ๊ธฐ๋ณธ๊ฐ’์€ FloatingActionButtonDefaults.shape๋‹ค.


containerColor: Color

FloatingActionButton์˜ ๋ฐฐ๊ฒฝ ์ƒ‰์ƒ ์„ค์ •. ๊ธฐ๋ณธ๊ฐ’์€ FloatingActionButtonDefaults.containerColor์ด๋‹ค.


contentColor: Color

FloatingActionButton ์† content๋“ค์˜ ๊ธฐ๋ณธ ์ƒ‰์ƒ์„ ์ง€์ •. ๊ธฐ๋ณธ๊ฐ’์€ contentColorFor(containerColor)๋‹ค.


elevation: FloatingActionButtonElevation

๊ทธ๋ฆผ์ž ๊นŠ์ด๋ฅผ ๋‚˜ํƒ€๋‚ด๋Š” ์ธ์ˆ˜๋กœ, ์Œ์˜ ํšจ๊ณผ(= ๊ณต์ค‘์— ๋–  ์žˆ๋Š” ๋“ฏํ•œ ํšจ๊ณผ)๋ฅผ ํ†ตํ•ด ์ž…์ฒด๊ฐ์„ ์ค€๋‹ค. Dp ๋‹จ์œ„๋กœ ๊ฐ’์„ ์„ค์ •ํ•œ๋‹ค.


interactionSource: MutableInteractionSource

๋ฒ„ํŠผ์„ ๋ˆ„๋ฅผ ๋•Œ ๋ฐœ์ƒํ•˜๋Š” ์• ๋‹ˆ๋ฉ”์ด์…˜ ํšจ๊ณผ๋‚˜ ์ƒ‰์ƒ ๋ณ€ํ™” ๋“ฑ์„ ์ œ์–ดํ•˜๋Š” ์ธ์ˆ˜. FloatingActionButton์ด ์‚ฌ์šฉ์ž์™€ ์ƒํ˜ธ์ž‘์šฉํ•˜๊ณ  ๋ฐ˜์‘ํ•˜๋Š” ๋ฐฉ์‹์„ ์ปค์Šคํ…€ํ•  ๋•Œ ์‚ฌ์šฉํ•œ๋‹ค.

 

#7 ๊ธฐํƒ€

#7-1 ๊ธฐ์ดˆ

...
import androidx.compose.foundation.layout.WindowInsets
import androidx.compose.foundation.layout.systemBars
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.contentColorFor

class MainActivity : ComponentActivity() {

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContent {
            Scaffold(
                modifier = Modifier.fillMaxSize(),
                containerColor = MaterialTheme.colorScheme.background, // Scaffold์˜ ๋ฐฐ๊ฒฝ ์ƒ‰์ƒ
                contentColor = contentColorFor(MaterialTheme.colorScheme.background), // ๋‚ด๋ถ€ content๋“ค์˜ ๊ธฐ๋ณธ ์ƒ‰
                contentWindowInsets = WindowInsets.systemBars // ์ƒํƒœ๋ฐ” ์œ„์น˜๋ฅผ ๊ณ ๋ คํ•œ Scaffold์˜ ์ž๋ฆฌ
            ) { innerPadding ->
                ... (#2-1 ์ฐธ์กฐ)
            }
        }
    }
}

containerColor: Color

Scaffold์˜ ๋ฐฐ๊ฒฝ ์ƒ‰์ƒ ์„ค์ •. ๊ธฐ๋ณธ๊ฐ’์€ MaterialTheme.colorScheme.background์ด๋‹ค.


contentColor: Color

Scaffold ์† content๋“ค์˜ ๊ธฐ๋ณธ ์ƒ‰์ƒ์„ ์ง€์ •. ๊ธฐ๋ณธ๊ฐ’์€ contentColorFor(MaterialTheme.colorScheme.background)๋‹ค.


contentWindowInsets: WindowInsets

WindowInsets์€ ์•ˆ๋“œ๋กœ์ด๋“œ ์‹œ์Šคํ…œ UI์— ๋Œ€ํ•œ ์ •๋ณด๋‹ค. ์ด๋ฅผ Scaffold์— ์ œ๊ณตํ•ด Scaffold๊ฐ€ ์ด ์˜ฌ๋ฐ”๋ฅธ ์˜์—ญ์— ๊ฒน์ณ์ง ์—†์ด ์ž๋ฆฌ์žก๊ฒŒ ๋งŒ๋“ ๋‹ค. ๊ธฐ๋ณธ๊ฐ’์€ WindowInsets.systemBars๋กœ, ์•ˆ๋“œ๋กœ์ด๋“œ ์‹œ์Šคํ…œ์˜ ์ƒํƒœ๋ฐ” ๋ฐ Navigation Bar(๋’ค๋กœ๊ฐ€๊ธฐ ๋ฒ„ํŠผ, ํ™ˆ ๋ฒ„ํŠผ, ์•ฑ ๋ชฉ๋ก ๋ณด๊ธฐ ๋ฒ„ํŠผ์ด ์žˆ๋Š” ๋ฐ”)๋ฅผ ํ”ผํ•ด์„œ ์ž๋ฆฌ์žก๋„๋ก ๋งŒ๋“œ๋Š” ์˜ต์…˜์ด๋‹ค. Scaffold ์†์˜ ๋ชจ๋“  content๋“ค์— ์ „์—ญ์ ์ด๊ณ  ์ผ๊ด„์ ์œผ๋กœ ์ ์šฉ๋œ๋‹ค. ์—ฌ๊ธฐ์„œ ๋งํ•˜๋Š” content๋Š” topBar, bottomBar์„ ๋งํ•˜๋Š” ๊ฒŒ ์•„๋‹ˆ๋ผ, Scaffold์˜ ๋งˆ์ง€๋ง‰ ๋งค๊ฐœ๋ณ€์ˆ˜์ธ content: @Composable (PaddingValues) -> Unit๋ฅผ ์˜๋ฏธํ•œ๋‹ค (#2-3 ์ฐธ์กฐ).

 

#8 ํ•ฉ์‚ฐ

#8-1 ์ฝ”๋“œ

// package com.example.scaffold

import android.os.Bundle
import androidx.activity.ComponentActivity
import androidx.activity.compose.setContent
import androidx.compose.foundation.background
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.WindowInsets
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.systemBars
import androidx.compose.material3.BottomAppBar
import androidx.compose.material3.ExperimentalMaterial3Api
import androidx.compose.material3.FabPosition
import androidx.compose.material3.FloatingActionButton
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Scaffold
import androidx.compose.material3.Snackbar
import androidx.compose.material3.SnackbarDuration
import androidx.compose.material3.SnackbarHost
import androidx.compose.material3.SnackbarHostState
import androidx.compose.material3.Text
import androidx.compose.material3.TopAppBar
import androidx.compose.material3.contentColorFor
import androidx.compose.runtime.remember
import androidx.compose.runtime.rememberCoroutineScope
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp
import kotlinx.coroutines.launch

class MainActivity : ComponentActivity() {

    @OptIn(ExperimentalMaterial3Api::class)
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContent {
            // Snackbar๋ฅผ ์œ„ํ•œ CoroutineScope์™€ State
            val scope = rememberCoroutineScope()
            val snackbarHostState = remember { SnackbarHostState() }

            Scaffold(
                modifier = Modifier.fillMaxSize(),
                // #3
                topBar = {
                    TopAppBar(
                        title = { Text("TopBar Text") }
                    )
                },
                // #4
                bottomBar = {
                    BottomAppBar(
                        content = { Text("BottomBar Text") }
                    )
                },
                // #5
                snackbarHost = {
                    SnackbarHost(
                        hostState = snackbarHostState,
                        snackbar = { snackbarData ->
                            Snackbar(
                                modifier = Modifier.padding(12.dp),
                                containerColor = Color.Magenta,
                            ) {
                                Text(
                                    text = snackbarData.visuals.message,
                                    color = Color.White
                                )
                            }
                        }
                    )
                },
                // #6
                floatingActionButton = {
                    FloatingActionButton(
                        onClick = {
                            scope.launch {
                                snackbarHostState.showSnackbar(
                                    // snackbarData ๋ณด๋‚ด๊ธฐ
                                    message = "FAB Clicked",
                                    duration = SnackbarDuration.Short
                                )
                            }
                        }
                    ) {
                        Text(
                            "F A B"
                        )
                    }
                },
                floatingActionButtonPosition = FabPosition.End,
                // #7
                containerColor = MaterialTheme.colorScheme.background, // Scaffold์˜ ๋ฐฐ๊ฒฝ ์ƒ‰์ƒ
                contentColor = contentColorFor(MaterialTheme.colorScheme.background), // ๋‚ด๋ถ€ content๋“ค์˜ ๊ธฐ๋ณธ ์ƒ‰
                contentWindowInsets = WindowInsets.systemBars // ์ƒํƒœ๋ฐ” ์œ„์น˜๋ฅผ ๊ณ ๋ คํ•œ Scaffold์˜ ์ž๋ฆฌ
            ) { innerPadding -> // Scaffold๊ฐ€ ๊ณ„์‚ฐํ•œ, content๊ฐ€ Scaffold์˜ ๋‹ค๋ฅธ UI ์š”์†Œ๋“ค๊ณผ ๊ฒน์น˜์ง€ ์•Š์„ ์ •๋„์˜ Padding๊ฐ’
                Box(
                    modifier = Modifier
                        .padding(innerPadding)
                        .fillMaxSize()
                        .background(Color.LightGray)
                ) {
                    Text(
                        text = "Content Text",
                        modifier = Modifier.align(Alignment.Center),
                        fontSize = 30.sp
                    )
                }
            }
        }
    }
}

#3 ~ #7์˜ ๋ชจ๋“  ์ฝ”๋“œ๋ฅผ ํ•ฉ์ณค๋‹ค. ํ•ฉ์นœ ๊น€์— ์•ฝ๊ฐ„์˜ ๋ณ€๊ฒฝ์ ์„ ๋„ฃ์—ˆ๋Š”๋ฐ, ํ”Œ๋กœํŒ… ์•ก์…˜ ๋ฒ„ํŠผ์„ ํด๋ฆญํ•˜๋ฉด Snackbar๊ฐ€ ๋œจ๊ฒŒ ์ˆ˜์ •ํ–ˆ๋‹ค. #7 ๋ถ€๋ถ„์€ ์ƒ๋žตํ•ด๋„ ์ƒ๊ด€์—†๋‹ค. ์ƒ๋žตํ–ˆ๋‹ค๋ฉด ์•”์‹œ์ ์œผ๋กœ ์ ์šฉ๋˜์—ˆ์„ ๊ธฐ๋ณธ๊ฐ’์„ ๊ทธ๋Œ€๋กœ ์ผ๊ธฐ ๋•Œ๋ฌธ์ด๋‹ค.

 

#8-2 ์ž‘๋™ ํ™•์ธ

ํ”Œ๋กœํŒ… ์•ก์…˜ ๋ฒ„ํŠผ์„ ๋ˆ„๋ฅด๋ฉด Snackbar๊ฐ€ ๋œฌ๋‹ค.

 

#9 ์š”์•ฝ

์ตœ๊ทผ ์•ˆ๋“œ๋กœ์ด๋“œ ์•ฑ UI์˜ ์ •ํ˜•ํ™”๋œ ๋ชจ์Šต์€ Scaffold์˜ ์˜ํ–ฅ๋„ ์ผ๋ถ€ ์žˆ์„ ๊ฒƒ ๊ฐ™๋‹ค๋Š” ์ƒ๊ฐ์ด ๋“ ๋‹ค. ๊ทธ๋งŒํผ ์“ฐ๊ธฐ ํŽธํ•˜๋‹ค.

 

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

 

android-practice/jetpack-compose/Scaffold at master ยท Kanmanemone/android-practice

Contribute to Kanmanemone/android-practice development by creating an account on GitHub.

github.com