#1 ์ด์ ๊ฒ์๊ธ
[Android] UI architecture - Phases
#1 ๊ฐ์ Jetpack Compose ๋จ๊ณ | Android Developers์ด ํ์ด์ง๋ Cloud Translation API๋ฅผ ํตํด ๋ฒ์ญ๋์์ต๋๋ค. Jetpack Compose ๋จ๊ณ ์ปฌ๋ ์ ์ ์ฌ์ฉํด ์ ๋ฆฌํ๊ธฐ ๋ด ํ๊ฒฝ์ค์ ์ ๊ธฐ์ค์ผ๋ก ์ฝํ ์ธ ๋ฅผ ์ ์ฅํ๊ณ ๋ถ๋ฅ
kenel.tistory.com
์ ๊ฒ์๊ธ์์ ์ด์ด์ง๋ค.
#2 State
[Android] Jetpack Compose - State ๊ธฐ์ด
#1 ๊ฐ์ ์ํ ๋ฐ Jetpack Compose | Android Developers์ด ํ์ด์ง๋ Cloud Translation API๋ฅผ ํตํด ๋ฒ์ญ๋์์ต๋๋ค. ์ํ ๋ฐ Jetpack Compose ์ปฌ๋ ์ ์ ์ฌ์ฉํด ์ ๋ฆฌํ๊ธฐ ๋ด ํ๊ฒฝ์ค์ ์ ๊ธฐ์ค์ผ๋ก ์ฝํ ์ธ ๋ฅผ ์ ์ฅํ๊ณ
kenel.tistory.com
๋จผ์ , State์ ๋ํด ์ดํดํด์ผ ํ๋ค. ๋ชจ๋ฅด๋ ์ฌ๋์ ์ ๊ฒ์๊ธ์ ์ฝ์. ๊ฒ์๊ธ์ ์์ฝํ๋ฉด, "Compose Runtime์ State ๊ฐ ๋ณํ์ ๋ฐ์ํ๋ค"์ด๋ค.
#3 State์ Phase
#3-1 Phase ์ฌ์คํ์ ๋ ๋ฆฝ์ ์ด๋ค
Compose์๋ UI ์์ฑ์ ์ํ 3๋จ๊ณ๊ฐ ์กด์ฌํ๋ค. Layout ๋จ๊ณ์ ์ต์ด ์คํ์ Composition ๋จ๊ณ๊ฐ ๋ฐ๋์ ์ ํ๋์ด์ผ ํ๋ฉฐ, Drawing ๋จ๊ณ์ ์ต์ด ์คํ์ Layout ๋จ๊ณ๊ฐ ๋ฐ๋์ ์ ํ๋์ด์ผ ํ๋ค. ํ์ง๋ง, ์ฌ์คํ์ด๋ผ๋ฉด ์๊ธฐ๊ฐ ๋ฌ๋ผ์ง๋ค. ์ฌ์คํ ์์๋ Layout ๋จ๊ณ๊ฐ Composition ๋จ๊ณ์ ์๊ด์์ด ๋ ๋ฆฝ์ ์ผ๋ก ์๋ํ ์๋ ์๋ค. ๋ง ๊ทธ๋๋ก ์ฌ์คํ์ด๊ธฐ์, Composition ๋จ๊ณ์์ ๋ง๋ค์๋ UI Tree๋ฅผ ์ฌ์ฌ์ฉํ ์๋ ์๊ธฐ ๋๋ฌธ์ด๋ค. ์ฐธ๊ณ ๋ก State๊ฐ ์ด๋์ ์ ์ฅ๋๋๊ฐ? ๋ฐ์๋ ์ค์์น ์๋ค. ์ฌ๊ธฐ์์ ์ค์ํ ๊ฒ์ State์ ๊ฐ์ด ์ด๋ค ๋จ๊ณ์์ ์ฝํ๋๋๋ค. State์ ๊ฐ์ด ์ด๋ ๋จ๊ณ์์ ์ฝํ๋๋์ ๋ฐ๋ผ, State ์ธ์คํด์ค ๋ณ๋ก ํ์ฑ๋๋ Restart Scope๋ฅผ ์ธ์ ์ฌ์คํํ ์ง๊ฐ ๊ฒฐ์ ๋๊ธฐ ๋๋ฌธ์ด๋ค.
#3-2 Restart Scope
@Composable
fun Counter() {
var count by remember { mutableStateOf(0) }
Column {
Button(onClick = { count++ }) {
Text("Increment")
}
Text("Current count: $count")
}
}
Restart Scope๋ Compose์์ ํน์ State ๊ฐ์ ๋ณ๊ฒฝ์ด ๋ฐ์ํ์ ๋, ์ฌ์คํ๋ ์ ์๋ ์ฝ๋ ๋ธ๋ก์ ๋ฒ์๋ค. ์ ์ฝ๋์์ Text("Current count: $count")๋ Stateํ ๋ณ์์ธ count์ ์์กดํ๋ค. Button() ์ฌ์ฉ์๊ฐ ํด๋ฆญํด์ count ๊ฐ์ด ๋ณ๊ฒฝ๋๋ฉด, Compose๋ Text ์ปดํฌ์ ๋ธ๋ง ๋ค์ ์คํํ๋ค. ์ด ๋ค์ ์คํ๋๋ ์ฝ๋์ ๋ฒ์๋ฅผ Restart Scope๋ผํ๋ค. Stateํ ์ธ์คํด์ค๋ค์ ๊ฐ ์ธ์คํด์ค๋ง๋ค ์์ ๋ค๋ง์ Restart Scope๋ฅผ ํ์ฑํ๋ค. ๊ฐ ์ธ์คํด์ค๋ (๋น์ฐํ) ์ผ๋ถ ํน์ ์ ๋ถ๊ฐ ๊ฒน์น๋ Restart Scope๋ฅผ ๊ฐ์ง ์ ์๋ค.
์๋์์ ๊ฐ ๋จ๊ณ ๋ณ State์์ ์ํธ์์ฉ์ ์์ ํ๋ค.
#3-3 Composition ๋จ๊ณ
var padding by remember { mutableStateOf(8.dp) }
var anyState by remember { mutableStateOf(0)}
Text(
text = "Hello",
// `padding` ์ํ๋ Modifier๊ฐ ์์ฑ๋ ๋ Composition ๋จ๊ณ์์ ์ฝํ
// `padding` ๊ฐ์ด ๋ณ๊ฒฝ๋๋ฉด Composition์ด ์ฌ์คํ๋จ
modifier = Modifier.padding(padding)
)
Composition ๋จ๊ณ๋ ํ ๋ง๋๋ก UI Tree๋ฅผ ๊ทธ๋ฆฌ๋ ๊ณผ์ ์ด๋ค. UI Tree๋ฅผ ๊ทธ๋ฆด ๋ Compose Runtime์ UI Tree์ ๊ด๋ จ๋ State์ ๊ฐ์ ์ฝ๊ณ ์ถ์ ๋ชฉ๋ก์ ๋ฃ๋๋ค (!= Compose Runtime์ด ๋ฌด์กฐ๊ฑด ๋ฐ๋์ ๋ชจ๋ State๋ฅผ ์ฝ๊ณ ์ถ์ ํ๋ค). ์ฌ๊ธฐ์ ์ค์ํ ์ ์ UI Tree์ ๊ด๋ จ๋ State๋ง์ ์ฝ๊ณ ์ถ์ ๋ชฉ๋ก์ ๋ฃ๋๋ค๋ ๊ฒ์ด๋ค. ๋ฐ๋๋ก ๋งํด, UI Tree์ ๊ด๋ จ์๋ State๋ Composition ๋จ๊ณ์์ ๋ฌด์๋๋ค (= Restart Scope ์์ฒด๊ฐ ๋ง๋ค์ด์ง์ง ์์). ๋ฐ๋ผ์ ์ ์ฝ๋ ์ ๋ณ์ anyState๋ ์ ์ด๋ Composition ๋จ๊ณ์์ ์๋ ๋ณ์์ฒ๋ผ ์ทจ๊ธ๋๋ค. Composition ๋จ๊ณ์ ์ถ์ ๋ชฉ๋ก์ ๋ฑ๋ก๋ State์ ๊ฐ์ด ๋ณ๊ฒฝ๋๋ฉด, Composition์ด ์ฌ์คํ(Recomposition)๋๋ค.
๋์๋

Modifier๊น์ง ํฌํจํ UI ํธ๋ฆฌ์ ๋ชจ์ต
#3-4 Layout ๋จ๊ณ
var offsetX by remember { mutableStateOf(8.dp) }
Text(
text = "Hello",
modifier = Modifier.offset {
// 'offsetX' ์ํ๋ Layout ๋จ๊ณ์ ๋ฐฐ์น ๋จ๊ณ(Placement)์์ ์ฝํ
// 'offsetX' ๊ฐ์ด ๋ณ๊ฒฝ๋๋ฉด Layout ๋จ๊ณ๊ฐ ๋ค์ ์คํ๋จ
IntOffset(offsetX.roundToPx(), 0)
}
)
Layout ๋จ๊ณ๋ UI Tree์ ๊ธฐ๋ฐํด (ํฌ๊ธฐ ๋ฐ ์์น๋ฅผ) ๊ณ์ฐํ๋ ๊ณผ์ ์ด๋ค. ์ด ๊ณ์ฐ ๊ณผ์ ์์ Compose Runtime์ ํฌ๊ธฐ ๋ฐ ์์น์ ๊ด๋ จ๋ State์ ๊ฐ์ ์ฝ๊ณ ์ถ์ ๋ชฉ๋ก์ ๋ฃ๋๋ค. ์ฌ๊ธฐ์ ์ค์ํ ์ ์ ํฌ๊ธฐ ๋ฐ ์์น์ ๊ด๋ จ๋ State๋ง์ ์ฝ๊ณ ์ถ์ ๋ชฉ๋ก์ ๋ฃ๋๋ค๋ ๊ฒ์ด๋ค. ๋ฐ๋๋ก ๋งํด, ํฌ๊ธฐ ๋ฐ ์์น์ ๊ด๋ จ์๋ State๋ Layout ๋จ๊ณ์์ ๋ฌด์๋๋ค.
์๋ฌธ์ : padding()๊ณผ offset()์ด ์ ์๋ก ๋ค๋ฅธ ๋จ๊ณ์์ ์ถ์ ๋๋๊ฐ?
์์ฐํ ์๋ฌธ์ด ๋ ๋ค. #3-2์ padding() ๋ํ ํฌ๊ธฐ ๋ฐ ์์น์ ๊ด๋ จ๋ ๊ฐ๋ ์ด ์๋์๋๊ฐ? ์ padding()์ Composition ๋จ๊ณ์์ ์ถ์ ๋๋ฉฐ, offset()์ Layout ๋จ๊ณ์์ ์ถ์ ๋๋๊ฐ? ๋์ ๋ฌด์จ ์ฐจ์ด๊ฐ ์๋๊ฐ? ๋์ '๋ชจ์' ์ด๋ผ๋ ์ถ์์ ๊ด๋ ์ ๊ณต์ ํ์ง๋ง, ๊ทธ ๊ตฌํ ๋ฐฉ์์ด ๋ค๋ฅด๊ธฐ ๋๋ฌธ์ด๋ค. padding()์ ํจ์ ์์ฒด๊ฐ ์ ์ ๊ฐ์ ์ธ์๋ก ๋ฐ๊ณ offset์ ํจ์ ์์ฒด๊ฐ ๋๋ค ํจ์(๋์ ๊ฐ)์ ์ธ์๋ก ๋ฐ๋๋ค. ์ ์๋ ๊ณ ์ ๋ ๊ฐ์ Modifier๊ฐ ์ ์ฅํ๋ ๋ฐ๋ฉด ํ์๋ ๋๋ค ์์ฒด๋ง์ ์ ์ฅํ์ฌ Offset ๊ฐ์ ๋ฐํํ๋ ๋ก์ง๋ง ๋์ค์ ์คํ๋๋ค. #3-2์ ๋์๋ ์ Modifier์ padding(start = 8)๊ณผ ๊ฐ์ ๊ตฌ์ฒด์ ์์น๊ฐ ์๋๋ผ, offset { IntOffset(offsetX.roundToPx(), 0) }์ ๊ฐ์ ๋๋ค ํจ์๊ฐ ๋ด๊ฒจ ์๋ค๊ณ ์๊ฐํ๋ฉด ๋๋ค.
๊ทธ๋ ๋ค๋ฉด ์ง๋ฌธ์ ๋ฐ๊ฟ๋ณด๊ฒ ๋ค. ์ padding์ ์ ์ ๊ฐ์ ๋ฐ๋๋ก ๋ง๋ค์ด์ ธ์๊ณ offset์ ๋๋ค ํจ์๋ฅผ ์ธ์๋ก ๋ฐ๊ฒ ๋ง๋ค์ด์ง ๊ฒ์ผ๊น? ๊ฐ๋ฐ์๋ผ๋ฉด ๋๊ตฌ๋ ๊ฐ๋จํ Web ํ๋ก๊ทธ๋๋ฐ์ ํด๋ณธ ์ ์ด ์์ ๊ฒ์ด๋ค. css ์์ฑ์์ padding๊ฐ์ ์ค์ ํ ๋๋ฅผ ์๊ฐํด๋ณด์. padding์ ์ด๋ป๊ฒ ์ค์ ํ๋๋์ ๋ฐ๋ผ ๋ถ๋ชจ ๋ฐ ์์์ ๋ชจ์์ด ๋ณํ๋ค. ์ฆ, ์๊ธฐ ์์ ๋ง ๋ฐ๊พธ๋ ๊ฒ ์๋๋ผ ์ด๋ค ์ฐ์ ๋ฐ์์ ์ผ์ผํจ๋ค. ์ด๋ ์ค๊ณํ๋ ์ ์ฅ(ํ๋ก๊ทธ๋๋จธ ์ ์ฅ)์์ ๋์์ ์ฐํธ๋ฆฌ๊ฒ ๋ง๋๋ ๋ถํ์คํจ์ ์์๋ธ๋ค. ๋ฐ๋ฉด offset์ ์๊ธฐ ์์ ์ ์์น๋ง์ ๋ณ๊ฒฝํ๋ค. ์๊ธฐ ์์ ๋ง ๋ฐ๊พธ๊ธฐ์ ์์ ํ๋ค. ์ค์ ๋ก Jetpack Compose์์ offset๊ฐ์ ์ด์ํ๊ฒ ์ค์ ํ๋ฉด ๋ถ๋ชจ์ ์์ญ์ ๋ซ๊ณ ๋๊ฐ๋ฒ๋ฆฐ๋ค๋ ๊ฐ ํ๋ ์ผ์ด ์๊ธด๋ค. ์๊ธฐ ์์ ์ ๋ชจ์๋ง ๋ฐ๊พธ๋๊น ๊ทธ๋ ๋ค. ์ ๋ฆฌํ๋ฉด, ์ฐ๋ฆฌ(๊ฐ๋ฐ์)๊ฐ ๊ฐ๋ฐํ๊ธฐ ํธํ๊ฒ Google์ด ์๋์ ์ผ๋ก ๊ตฌ๋ถํด ๋์ ๊ฒ์ด๋ค.
Layout ๋จ๊ณ๋ ์ธก์ (Measurement) ๋ฐ ๋ฐฐ์น(Placement)๋ผ๋ ๋ ํ์ ๋จ๊ณ๋ฅผ ๊ฐ์ง๋ค.
#3-5 Layout - Measurement
์ฌ๊ธฐ์ ์ค๋ช ํ๋ ๊ฑด ํผ๋๋ง ๊ฐ์ค์ํจ๋ค. ์ด ๊ฒ์๊ธ์์ ์ค์ ์ฝ๋์ ํจ๊ป ๋ด์ผํ๋ค. ์ ๊ฒ์๊ธ์ ๋์ค๋ measurable.measure()๊ฐ Measurement ๋จ๊ณ์ ํด๋นํ๋ค.
#3-6 Layout - Placement
์ฌ๊ธฐ์ ์ค๋ช ํ๋ ๊ฑด ํผ๋๋ง ๊ฐ์ค์ํจ๋ค. ์ด ๊ฒ์๊ธ์์ ์ค์ ์ฝ๋์ ํจ๊ป ๋ด์ผํ๋ค. ์ ๊ฒ์๊ธ์ ๋์ค๋ MeasureScope.layout()์ด Placement ๋จ๊ณ์ ํด๋นํ๋ค.
#3-7 Drawing ๋จ๊ณ
var color by remember { mutableStateOf(Color.Red) }
Canvas(modifier = modifier) {
// `color` ์ํ๋ Drawing ๋จ๊ณ์์ ์ฝํ
// `color` ๊ฐ์ด ๋ณ๊ฒฝ๋๋ฉด Drawing ๋จ๊ณ๊ฐ ๋ค์ ์คํ๋จ
drawRect(color)
}
Drawing ๋จ๊ณ๋ ์ค์ ํ๋ฉด์ ๊ทธ๋ํฝ์ ํ์ํ๋ ๊ณผ์ ์ด๋ค. ์ด ๊ณ์ฐ ๊ณผ์ ์์ Compose Runtime์ ๊ทธ๋ํฝ์ ๊ด๋ จ๋ State์ ๊ฐ์ ์ฝ๊ณ ์ถ์ ๋ชฉ๋ก์ ๋ฃ๋๋ค. ์ฌ๊ธฐ์ ์ค์ํ ์ ์ ๊ทธ๋ํฝ์ ๊ด๋ จ๋ State๋ง์ ์ฝ๊ณ ์ถ์ ๋ชฉ๋ก์ ๋ฃ๋๋ค๋ ๊ฒ์ด๋ค. ๋ฐ๋๋ก ๋งํด, ๊ทธ๋ํฝ๊ณผ ๊ด๋ จ์๋ State๋ Drawing ๋จ๊ณ์์ ๋ฌด์๋๋ค.
#4 State์ '์ข์' ์์น
#4-1 2๊ฐ์ง Modifier.offset()
ํ์ ํ ์ฝ๋ ์ค๋ช ์ ์ํด ๋จผ์ ๋ฉ์๋ ์ค๋ฒ๋ก๋ฉ๋ Modifier.offset()์ 2๊ฐ์ง ํํ๋ฅผ ๋จผ์ ์์๋ณธ๋ค.
// https://developer.android.com/reference/kotlin/androidx/compose/foundation/layout/package-summary#(androidx.compose.ui.Modifier).offset(androidx.compose.ui.unit.Dp,androidx.compose.ui.unit.Dp)
fun Modifier.offset(x: Dp = 0.dp, y: Dp = 0.dp): Modifier
#3-2์ padding()์ฒ๋ผ ์ ์ ์ธ ์ ๋ณด๋ฅผ ๋ด๋๋ค.
// https://developer.android.com/reference/kotlin/androidx/compose/foundation/layout/package-summary#(androidx.compose.ui.Modifier).offset(kotlin.Function1)
fun Modifier.offset(offset: Density.() -> IntOffset): Modifier
#3-3์์ ์ฐ์ธ offset()์ผ๋ก, ๋์ ์ธ ์ ๋ณด(๋๋ค ํจ์)๋ฅผ ๋ด๋๋ค.
#4-2 State์ ๋์ ์์น
Box {
val density = LocalDensity.current
val listState = rememberLazyListState()
Image(
// ...
// ๋นํจ์จ์ ์ธ ๊ตฌํ ๋ฐฉ์!
Modifier.offset(
// Composition ์ค firstVisibleItemScrollOffset ์ํ ์ฝ๊ธฐ
(listState.firstVisibleItemScrollOffset / 2).toDp(density)
)
)
LazyColumn(state = listState) {
// ...
}
}
์ ์ ์ธ ์ ๋ณด๋ฅผ ๋ด๋ Modifier.offset()์ด ์ฐ์ธ ์ฝ๋๋ค. ์๋์ ํ์ง๋ง ์ฑ๋ฅ์์ผ๋ก ์ข์ง ์์ ์ฝ๋๋ค. ์๋ํ๋ฉด Composition ๋จ๊ณ์์ ์ฝํ listState.firstVisibleItemScrollOffset์ ๊ฐ์ด ์คํฌ๋กค ํ ๋๋ง๋ค ๋ณ๋๋๊ธฐ์, Composition ๋จ๊ณ๊ฐ ๋๋ฌด ์์ฃผ ์ฌ์คํ๋๊ธฐ ๋๋ฌธ์ด๋ค.
#4-3 ์ฝ๋ ๋ฆฌํฉํ ๋ง
Box {
val listState = rememberLazyListState()
Image(
// ...
Modifier.offset {
// Layout ๋จ๊ณ์์ firstVisibleItemScrollOffset ์ํ ์ฝ๊ธฐ
IntOffset(x = 0, y = listState.firstVisibleItemScrollOffset / 2)
}
)
LazyColumn(state = listState) {
// ...
}
}
๋์ ์ธ ์ ๋ณด๋ฅผ ๋ด๋ Modifier.offset()์ผ๋ก ๋ฐ๊ฟจ๋ค. ์ด๋ฌ๋ฉด ์ฌ์ฉ์๊ฐ ์คํฌ๋กค์ ํ ๋๋ง๋ค, Composition ๋จ๊ณ ๋์ Layout ๋จ๊ณ๊ฐ ์ฌ์คํ๋ ๊ฒ์ด๋ค. ์์์ ๋งํ๋ฏ ๊ฐ Phase์ ์ฌ์คํ์ ์๋ก ๋ ๋ฆฝ์ ์ด๊ธฐ์, ๊ฐ๋ น Composition ๋จ๊ณ ์ฌ์คํ์ด ๊ผญ layout ๋จ๊ณ ์ฌ์คํ์ ์ ๋ฐํ์ง ์๋๋ค. ํ์ง๋ง #4-2์ ์ฝ๋์์๋ listState.firstVisibleItemScrollOffset์ ๋ณํ๊ฐ Image ๋ฐ LazyColumn์ Layout ๋จ๊ณ ์ฌ์คํ์ ์ ๋ฐํ๋ค. ์ ๋ฆฌํ๋ฉด Composition์ด ๊ตณ์ด ์ฌ์คํ๋ ํ์๊ฐ ์๋ ๊ตฌ์กฐ์ด๊ธฐ์, Layout๋ง ์ฌ์คํ๋๋๋ก ์ฝ๋๋ฅผ ๋ฆฌํฉํ ๋งํ ๊ฒ์ด๋ค.
#4-4 ์ผ๋ฐํ
#4-2์ ์ฝ๋๋ฅผ #4-3์ ์ฝ๋๋ก ๋ณ๊ฒฝํ ๊ฒ์ ํ๋์ ์์์ ๋ถ๊ณผํ๋ค. ์ด๋ฅผ ์ผ๋ฐํํด ์ค๋ช ํ๋ฉด, State๊ฐ ์ฝํ๋ ๋จ๊ณ๊ฐ ์ต๋ํ ๋ฎ์ ๋จ๊ณ๊ฐ ๋๊ฒ๋ ๋ก์ปฌํ(localize)ํ์ฌ Compose Runtime๊ฐ ์ธ๋ฐ์๋ ์ผ์ ํ์ง ์๋๋ก ๋ง๋๋ ๊ฒ์ด๋ค. ๋ ๋ค๋ฅธ ๋ฐฉ๋ฒ์ผ๋ก๋ Side-Efftects ์ค ํ๋์ธ derivedStateOf๋ฅผ ์ด์ฉํด์, ์ฌ๋ฌ State ์ธ์คํด์ค ๊ฐ์ด ๊ฐ์ง ์ ์๋ ๋ฌด์ํ ๋ง์ ์กฐํฉ์ ๋ช ๊ฐ์ง์ ์นดํ ๊ณ ๋ฆฌ๋ก ์ค์ฌ Composition ์ฌ์คํ ํ์๋ฅผ ์ค์ผ ์๋ ์๋ค.
#5 ์ํ ์ฐธ์กฐ ํผํ๊ธฐ
#5-1 ์ ์ข์ ์ฝ๋
Box {
var imageHeightPx by remember { mutableStateOf(0) }
val density = LocalDensity.current.density
Image(
painter = painterResource(R.drawable.rectangle),
contentDescription = "I'm above the text",
modifier = Modifier
.fillMaxWidth()
.onSizeChanged { size ->
imageHeightPx = size.height // ์ ์ข์ ์ฝ๋!
}
)
Text(
text = "I'm below the image",
modifier = Modifier.padding(
top = (imageHeightPx / density).dp
)
)
}
์๋ ์ด๋ฏธ์ง๊ฐ ์์, ํ ์คํธ๊ฐ ์๋์ ๋ฐฐ์น๋ ํํ์ ์์ง Column์ ์๋ชป ๊ตฌํํ ์ฝ๋๋ค. ์ด ์ฝ๋์ ๊ฐ์ฅ ํฐ ๋ฌธ์ ๋ "๋จ์ผ Frame๋ง์ผ๋ก ์ต์ข Layout์ด ์์ฑ๋์ง ์๋๋ค"๋ ๊ฒ์ด๋ค.
#5-2 ๋ฌธ์ ๋ถ์

imageHeightPx๋ onSizeChaged()์์๋ ์ฐ์ด๊ณ padding()์์๋ ์ฐ์ธ๋ค. ์ ์์์ ์ฝํ ๋ Layout ๋จ๊ณ์์ ์ถ์ ๋๊ณ , ํ์์์ ์ฝํ ๋ Composition ๋จ๊ณ์์ ์ถ์ ๋๋ค. ๋๋ฌธ์, imageHeightPx์ ๊ฐ์ด Layout ๋จ๊ณ์์ ๋ณํ๋ฉด ๊ทธ ๋ค์ ํ๋ ์์์ Composition์ด ์ฌ์คํ๋๋ค. ํ๋ก๊ทธ๋๋จธ๊ฐ ์๋ํ ํ๋ฉด์ ์ํด์ ์ด 2ํ๋ ์์ด ์๊ตฌ๋๋ ๊ฒ์ด๋ค. ์ด๋ฌ๋ฉด ํ๋ฉด์ด ๋๋ ๋์ด์ ธ์ ๋ณด์ผ ๊ฒ์ด๋ค. ์ด ๋ถ์ ์ ํจํด์ Cyclic Phase Dependency (๋จ๊ณ ๊ฐ ์ํ ์ฐธ์กฐ)๋ผ๋ ์ด๋ฆ์ผ๋ก ๋ถ๋ฅธ๋ค.
#5-4 ํด๊ฒฐ
Column()์ ์ด์ฉํ๊ธฐ
Column {
Image(
painter = painterResource(R.drawable.rectangle),
contentDescription = "I'm above the text",
modifier = Modifier.fillMaxWidth()
)
Text(
text = "I'm below the image",
modifier = Modifier.padding(top = 16.dp)
)
}
Jetpack Compose์ ๊ธฐ๋ณธ ์ปจํ ์ด๋์ธ Column()์ ์ฌ์ฉํ ๊ฐ์ฅ ๋จ์ํ ํด๊ฒฐ๋ฒ.
SubcomposeLayout() ์ด์ฉํ๊ธฐ
@Composable
fun CustomLayoutWithImageAndText() {
Layout(
content = {
Image(
painter = painterResource(R.drawable.rectangle),
contentDescription = "I'm above the text"
)
Text(text = "I'm below the image")
}
) { measurables, constraints ->
// ์์ ์ปดํฌ๋ํธ ์ธก์
val imagePlaceable = measurables[0].measure(constraints)
val textPlaceable = measurables[1].measure(constraints)
// ์ ์ฒด ๋ ์ด์์ ํฌ๊ธฐ ๊ณ์ฐ
val layoutHeight = imagePlaceable.height + textPlaceable.height
layout(constraints.maxWidth, layoutHeight) {
// ์ด๋ฏธ์ง ๋ฐฐ์น
imagePlaceable.place(x = 0, y = 0)
// ํ
์คํธ ๋ฐฐ์น (์ด๋ฏธ์ง ์๋)
textPlaceable.place(x = 0, y = imagePlaceable.height)
}
}
}
๋จ์ผ ์ง์ค ์์ค(Single Source of Truth) ์์น์ ์ค์ํด์ State์ ๊ฐ์ ๊ด๋ฆฌ๊ฐ (Image ๋ฐ Text์์) ์ค๋ณต๋์ง ์๋๋ก ๋ง๋ ๋ค.
์ฅ์ :
SubcomposeLayout์ ๋ ์ด์์ ๊ณ์ฐ ๋จ๊ณ๋ฅผ ๋ถ๋ฆฌํ์ฌ ์ ํ ํ์์ ๋ฐฉ์งํฉ๋๋ค.
ํ
์คํธ๊ฐ ์ด๋ฏธ์ง์ ๋์ด์ ์์ฐ์ค๋ฝ๊ฒ ๋ฐ๋ผ๊ฐ๋๋ก ๋ณด์ฅํฉ๋๋ค.
ํ์ง๋ง ๋ ๋ณต์กํ ๊ตฌ์กฐ๋ฅผ ๊ฐ์ง ์ฌ์ฉ ์ฌ๋ก์์๋ **์ฌ์ฉ์ ์ ์ ๋ ์ด์์(custom layout)**์ ์์ฑํด์ผ ํ ์๋ ์์ต๋๋ค. ์ฌ์ฉ์ ์ ์ ๋ ์ด์์ ์์ฑ์ ๋ํ ์์ธํ ๋ด์ฉ์ Custom layouts ๊ฐ์ด๋๋ฅผ ์ฐธ๊ณ ํ์ธ์.
์ ๋ด์ฉ์ ์ ๋ฆฌํ์๋ฉด, Compose์์ ๋ค์ ๋จ๊ณ์ ์์กดํ๋ ์ํ์ ์ธ ์ํ ํ๋ฆ์ ๋์ ํ์ง ์๋๋ก ์ฃผ์ํด์ผ ํฉ๋๋ค. ์ ์ ํ ๋ ์ด์์ ๊ตฌ์ฑ ์์๋ฅผ ํ์ฉํ๊ณ , ํ์ํ๋ค๋ฉด ์ฌ์ฉ์ ์ ์ ๋ ์ด์์์ ๋ง๋ค์ด ๋์์ ์ ์ดํจ์ผ๋ก์จ ์ฌ๊ตฌ์ฑ ๋ฃจํ ๋ฌธ์ ๋ฅผ ๋ฐฉ์งํ ์ ์์ต๋๋ค.
'๊นจ์ ๊ฐ๋ ๐ > Android' ์นดํ ๊ณ ๋ฆฌ์ ๋ค๋ฅธ ๊ธ
[Android] App layout - Custom layouts (0) | 2025.02.27 |
---|---|
[Android] UI architecture - Phases (0) | 2025.02.27 |
[Android] App layout - ๊ธฐ์ด (0) | 2025.02.27 |
[Android] Pointer input - Nested Scroll (0) | 2025.02.18 |
[Android] Pointer input - Scroll (0) | 2025.02.17 |