#1 ๊ฐ์
#1-1 Scaffold์ ์ฌ์ ์ ์๋ฏธ

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
'๊นจ์ ๊ฐ๋ ๐ > Android' ์นดํ ๊ณ ๋ฆฌ์ ๋ค๋ฅธ ๊ธ
[Android] Jetpack Compose - ๊ฐ์ฒด ์งํฅ์ UI ๋ ์ด์ด ์ค๊ณ (0) | 2024.09.11 |
---|---|
[Android] Jetpack Compose - Side-effects (0) | 2024.09.06 |
[Android] LiveData - Flow๋ก ๋ง์ด๊ทธ๋ ์ด์ (0) | 2024.08.29 |
[Android] Jetpack Compose - StateFlow์ SharedFlow (0) | 2024.08.29 |
[Android] Coroutines Flow - Jetpack Compose์์ ์ฌ์ฉํ๊ธฐ (1) | 2024.08.06 |