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

[Android] App layout - ๊ธฐ์ดˆ

interfacer_han 2025. 2. 27. 12:06

#1 ๊ฐœ์š”

 

Compose ๋ ˆ์ด์•„์›ƒ ๊ธฐ๋ณธ์‚ฌํ•ญ  |  Jetpack Compose  |  Android Developers

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

developer.android.com

์œ„ ๊ณต์‹ ๋ฌธ์„œ๋ฅผ ๋‚˜์˜ ์–ธ์–ด๋กœ ์ •๋ฆฌํ–ˆ๋‹ค.

 

#2 ๊ธฐ๋ณธ ์ปดํฌ์ €๋ธ”

 

[Android] Jetpack Compose - Layout, Arrangement, Alignment

#1 Layout#1-1 ๊ฐœ์š” Compose ๋ ˆ์ด์•„์›ƒ ๊ธฐ๋ณธ์‚ฌํ•ญ  |  Jetpack Compose  |  Android Developers์ด ํŽ˜์ด์ง€๋Š” Cloud Translation API๋ฅผ ํ†ตํ•ด ๋ฒˆ์—ญ๋˜์—ˆ์Šต๋‹ˆ๋‹ค. Compose ๋ ˆ์ด์•„์›ƒ ๊ธฐ๋ณธ์‚ฌํ•ญ ์ปฌ๋ ‰์…˜์„ ์‚ฌ์šฉํ•ด ์ •๋ฆฌํ•˜๊ธฐ ๋‚ด ํ™˜๊ฒฝ

kenel.tistory.com

Column(), Row(), Box()๋Š” ๋ ˆ์ด์•„์›ƒ์„ ์œ„ํ•ด Jetpack Compose์—์„œ ์ œ๊ณต๋˜๋Š” ๊ธฐ๋ณธ ์ปดํฌ์ €๋ธ”๋“ค์ด๋‹ค. ์œ„ ๊ฒŒ์‹œ๊ธ€์— ์ •๋ฆฌํ–ˆ๋‹ค.

 

#3 Layout ์„ค๊ณ„์˜ ์ˆœ์„œ

#3-1 ์ฝ”๋“œ

@Composable
fun SearchResult() {
    Row {
        Image(
            // ...
        )
        Column {
            Text(
                // ...
            )
            Text(
                // ...
            )
        }
    }
}

์ด ์ฝ”๋“œ์˜ UI Tree๋Š” ์•„๋ž˜์™€ ๊ฐ™๋‹ค.

 

#3-2 UI Tree

SearchResult
    Row
        Image
        Column
            Text
            Text

์ด UI Tree์— ๊ธฐ๋ฐ˜ํ•ด Layout ๋‚ด๋ถ€์˜ ๋™์ž‘ ์ˆœ์„œ๋ฅผ ๋„์‹๋„๋กœ ๋‚˜ํƒ€๋‚ด๋ฉด ์•„๋ž˜์™€ ๊ฐ™๋‹ค.

 

#3-3 Layout ๋‚ด๋ถ€์  ๋™์ž‘ ์ˆœ์„œ

ํšŒ์ƒ‰ ๋„ค๋ชจ ์† ์ˆซ์ž๋Š” ๋™์ž‘ ์ˆœ์„œ๋‹ค. ์ž์‹์„ ๊ฐ€์ง€๋Š” ๋ถ€๋ชจ ๋…ธ๋“œ๋Š”, ์ž์‹๋“ค์˜ ํฌ๊ธฐใ†์œ„์น˜๋ฅผ ๋ณด๊ณ ๋ฐ›์•„ ์ž์‹ ์˜ ํฌ๊ธฐใ†์œ„์น˜๋ฅผ ๊ฒฐ์ •ํ•œ๋‹ค. ๋ถ€๋ชจ๊ฐ€ ์ž์‹ ์˜ ํฌ๊ธฐใ†์œ„์น˜๋ฅผ ๊ฒฐ์ •ํ•˜๋ฉด, ๋‚ด๋ถ€์— ์ž์‹๋“ค์„ ๋ฐฐ์น˜ํ•œ ๋’ค์— ์ž์‹ ์˜ ๋ถ€๋ชจ์—๊ฒŒ (์žฌ๊ท€์ ์œผ๋กœ) ์ž๊ธฐ ์ž์‹ ์˜ ํฌ๊ธฐใ†์œ„์น˜๋ฅผ ๋ณด๊ณ ํ•œ๋‹ค. ์ฆ‰, (์‹œ์Šคํ…œ ๋…ธ๋“œ๋ฅผ ์ œ์™ธํ•œ) ๋ชจ๋“  ๋…ธ๋“œ๋Š” [์ธก์ • ์š”์ฒญ] โ†’ [๋ฐฐ์น˜ ์ค€๋น„(ํฌ๊ธฐใ†์œ„์น˜ ๊ฒฐ์ •)] โ†’ [์‹ค์ œ ๋ฐฐ์น˜]์˜ ๋‹จ๊ณ„๋ฅผ ๊ฑฐ์นœ๋‹ค.

 

#3-4 ์ผ๋ฐ˜ํ™”์™€ ์˜ˆ์™ธ

Compose์˜ Layout ๋‹จ๊ณ„๋Š” ์œ„์™€ ๊ฐ™์ด ๋ถ€๋ชจ โ†’ ์ž์‹ ๋ฐฉํ–ฅ์œผ๋กœ ๋‹จ ํ•œ๋ฒˆ๋งŒ ์ธก์ • ์š”์ฒญ์„ ํ•˜๋Š”๋ฐ, ์ด๋Š” (2๋ฒˆ ์ด์ƒ ์ธก์ •์„ ํ•˜์ง€ ์•Š๋Š”๋‹ค๋Š” ์ ์—์„œ) ์ƒ๋Œ€์ ์œผ๋กœ ๋†’์€ ์„ฑ๋Šฅ์˜ ๋น„๊ฒฐ์ด ๋œ๋‹ค. ๊ทธ๋Ÿฌ๋‚˜, ํŠน์ • ์ƒํ™ฉ์—์„œ ์—ฌ๋Ÿฌ ๋ฒˆ์˜ ์ธก์ • ์š”์ฒญ์ด ํ•„์š”ํ•  ์ˆ˜๋„ ์žˆ๋Š”๋ฐ ์ด๋Ÿฐ ๊ฒฝ์šฐ๋ฅผ ์œ„ํ•œ Intrinsic measurements๋ผ๋Š” ๋„๊ตฌ๊ฐ€ ์ œ๊ณต๋œ๋‹ค. Intrinsic measurements๋Š” ์ž์‹ ์ปดํฌ์ €๋ธ”์„ ์‹ค์ œ๋กœ ์ธก์ •ํ•˜๊ธฐ ์ „์—, ํ•ด๋‹น ์ž์‹์˜ ์ตœ์†Œใ†์ตœ๋Œ€ ํฌ๊ธฐ๋ฅผ ๋ฏธ๋ฆฌ ํ™•์ธ(Query)ํ•  ์ˆ˜ ์žˆ๋Š” ๊ธฐ๋Šฅ์ด๋‹ค. ์ด๋Š” '์‹ค์ œ ์ธก์ •'์€ ์•„๋‹ˆ๊ณ  'Query'์— ๊ฐ€๊นŒ์šด ๋™์ž‘์ด๋‹ค. ๊ทธ๋ ‡๊ธฐ์— ์—ฌ์ „ํžˆ ๋‹จ ํ•œ๋ฒˆ์˜ ์ธก์ • ์š”์ฒญ์ด๋ผ๋Š” ๊ทœ์น™์€ ์œ ์ง€๋œ๋‹ค.

 

#4 ๋ถ€๋ชจ์˜ ์ œ์•ฝ ์กฐ๊ฑด

Layout์„ ์„ค๊ณ„ํ•  ๋•Œ๋Š” ๋‹ค์–‘ํ•œ ๋””๋ฐ”์ด์Šค์˜ ๋ฌผ๋ฆฌ์  ํฌ๊ธฐ(Form Factor)๋ฅผ ๊ณ ๋ คํ•ด์•ผ ํ•œ๋‹ค. Compose์—์„œ๋Š” ์ด๋ฅผ ์œ„ํ•ด BoxWithConstraints()๋ผ๋Š” ๋„๊ตฌ๋ฅผ ์ œ๊ณตํ•œ๋‹ค.

 

@Composable
@UiComposable
fun BoxWithConstraints(
    modifier: Modifier = Modifier,
    contentAlignment: Alignment = Alignment.TopStart,
    propagateMinConstraints: Boolean = false,
    content: @Composable @UiComposable BoxWithConstraintsScope.() -> Unit
): Unit

BoxWithConstraints()๋Š” BoxWithConstraintsScope๋ฅผ ๋ณด์œ ํ•˜๋Š”๋ฐ, ์ด Scope์—์„œ๋Š” ๋ถ€๋ชจ ์ปจํ…Œ์ด๋„ˆ์˜ ์ตœ์†Œ ๋†’์ด, ์ตœ๋Œ€ ๋†’์ด, ์ตœ์†Œ ๋„ˆ๋น„, ์ตœ๋Œ€ ๋„ˆ๋น„๋ฅผ ์ฐธ์กฐํ•  ์ˆ˜ ์žˆ๋‹ค. ์˜ˆ์ œ ์ฝ”๋“œ๋Š” ์•„๋ž˜์™€ ๊ฐ™๋‹ค.

 

@Composable
fun WithConstraintsComposable() {
    BoxWithConstraints {
        Text("My minHeight is $minHeight while my maxWidth is $maxWidth")
    }
}

/* minHeight ๋ฐ maxWidth๊ฐ€ ์™œ ๊ฐ‘์ž๊ธฐ ํŠ€์–ด๋‚˜์˜จ๊ฑธ๊นŒ? ๋ฐ”๋กœ, Kotlin์˜ '์•”์‹œ์  this' ๋•Œ๋ฌธ.
@Composable
fun WithConstraintsComposable() {
    BoxWithConstraints {
        // BoxWithConstraintsScope๋กœ๋ถ€ํ„ฐ ์ œ๊ณต๋˜๋Š” ํ”„๋กœํผํ‹ฐ๋“ค
        val minH = this.minHeight  // this๋Š” BoxWithConstraintsScope
        val maxW = this.maxWidth
        Text("My minHeight is $minH while my maxWidth is $maxW")
    }
}
*/

 

#5 ์Šฌ๋กฏ ๊ธฐ๋ฐ˜ Layout

#5-1 ๊ฐœ์š”

https://developer.android.com/develop/ui/compose/layouts/basics#slot-based-layouts

์Šฌ๋กฏ ๊ธฐ๋ฐ˜ ๋ ˆ์ด์•„์›ƒ(Slot-based layout)์€ ์ปดํฌ์ €๋ธ”์— ์‚ฌ์šฉ์žํ™”(customization)์˜ ์—ฌ์ง€๊ฐ€ ์กด์žฌํ•˜๋Š” ๋ ˆ์ด์•„์›ƒ์„ ์˜๋ฏธํ•œ๋‹ค. ์‚ฌ์šฉ์žํ™”๊ฐ€ ๊ฐ€๋Šฅํ•œ ๋ถ€๋ถ„๋ถ€๋ถ„๋“ค์„ '์Šฌ๋กฏ(Slot)'์ด๋ผ ๋ถ€๋ฅด๋Š”๋ฐ DrawerFloatingActionButtonTopAppBar์™€ ๊ฐ™์€ ์š”์†Œ๋“ค์ด ๊ทธ ์˜ˆ๋‹ค. ๊ฐœ๋ฐœ์ž๋Š” ์ด ์Šฌ๋กฏ๋“ค์„ ์›ํ•˜๋Š” ๋ฐฉ์‹์œผ๋กœ ์ฑ„์šธ ์ˆ˜ ์žˆ๋‹ค.

 

#5-2 ์Šฌ๋กฏ ์ปค์Šคํ…€์˜ ์˜ˆ์‹œ

@Composable
fun HomeScreen(/*...*/) {
    ModalNavigationDrawer(drawerContent = { /* ... */ }) {
        Scaffold(
            topBar = { /*...*/ }
        ) { contentPadding ->
            // ...
        }
    }
}

Scaffold()๊ฐ€ ์“ฐ์ธ ์ฝ”๋“œ๋‹ค. ์ปดํฌ์ €๋ธ”์„ ์œ„์น˜์‹œํ‚ฌ ์ˆ˜ ์žˆ๋Š” ๋žŒ๋‹ค ํ•จ์ˆ˜ @Composable () -> Unit๋Š” ๋Œ€๋ถ€๋ถ„ "content"๋ผ๋Š” ์ด๋ฆ„์˜ ํ”„๋กœํผํ‹ฐ๋กœ ์กด์žฌํ•œ๋‹ค. ์ด ํ”„๋กœํผํ‹ฐ๊ฐ€ Slot์˜ ์ž๋ฆฌ๋‹ค.

'๊นจ์•Œ ๊ฐœ๋… ๐Ÿ“‘ > Android' ์นดํ…Œ๊ณ ๋ฆฌ์˜ ๋‹ค๋ฅธ ๊ธ€

[Android] UI architecture - Phase์™€ State  (1) 2025.02.27
[Android] UI architecture - Phases  (0) 2025.02.27
[Android] Pointer input - Nested Scroll  (0) 2025.02.18
[Android] Pointer input - Scroll  (0) 2025.02.17
[Android] Pointer input - Gesture  (0) 2025.02.08