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

[Android] App layout - Custom layouts

interfacer_han 2025. 2. 27. 12:06

#1 ๊ฐœ์š”

๋งž์ถค ๋ ˆ์ด์•„์›ƒ  |  Jetpack Compose  |  Android Developers

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

developer.android.com

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

#2 ์ •ํ™•ํžˆ '๋ฌด์—‡'์„ ์ปค์Šคํ…€ํ•˜๋Š”๊ฐ€?

๊ฒŒ์‹œ๊ธ€ [Android] App layout - ๊ธฐ์ดˆ์— ์žˆ๋Š” ๋„์‹๋„๋‹ค. ์ด ๋„์‹๋„๋Š” Layout ๋‚ด๋ถ€์  ๋™์ž‘ ๊ธฐ์ œ๋ฅผ ํ‘œํ˜„ํ•˜๊ณ  ์žˆ๋‹ค. Layout์„ ์ปค์Šคํ…€ํ•œ๋‹ค๋Š” ๊ฑด ์œ„ ๋„์‹๋„์—์„œ ์ฃผํ™ฉ์ƒ‰ ๋„ค๋ชจ ๋ฐ ํŒŒ๋ž€์ƒ‰ ๋„ค๋ชจ ์•ˆ์˜ ๋‚ด์šฉ์„ ์˜ค๋ฒ„๋ผ์ด๋“œํ•˜๋Š” ๊ฒƒ์ด๋‹ค.
 

#3 Modifier.layout()

#3-1 ๊ฐ„๋‹จํ•œ UI

์ฝ”๋“œ

Scaffold(
    modifier = Modifier.fillMaxSize()
) { innerPadding ->
    // ์ฒซ๋ฒˆ์งธ Box()
    Box(
        modifier = Modifier
            .fillMaxSize()
            .padding(innerPadding)
            .background(Color.LightGray),
        contentAlignment = Alignment.Center
    ) {
        // ๋‘๋ฒˆ์งธ Box()
        Box(
            modifier = Modifier
                .width(200.dp)
                .height(200.dp)
                .background(Color.Gray), // ๋ถˆํˆฌ๋ช… ํšŒ์ƒ‰
            contentAlignment = Alignment.Center
        ) {
            Text("12345")
        }

        // ์„ธ๋ฒˆ์งธ Box()
        Box(
            modifier = Modifier
                .width(200.dp)
                .height(200.dp)
                .background(Color(0x8000FF00)), // ๋ฐ˜ํˆฌ๋ช… ์ดˆ๋ก์ƒ‰
            contentAlignment = Alignment.Center
        ) {
            Text("321")
        }
    }
}

์ฒซ๋ฒˆ์งธ Box()๋Š” ๋‘๋ฒˆ์งธ Box() ๋ฐ ์„ธ๋ฒˆ์งธ Box()์˜ ๋ถ€๋ชจ๋‹ค.
 
์Šคํฌ๋ฆฐ์ƒท

์Šคํฌ๋ฆฐ์ƒท์€ ์œ„์™€ ๊ฐ™๋‹ค.
 

#3-2 placeable์˜ ์˜๋ฏธ

์ฝ”๋“œ

Scaffold(
    ...
) { ...
    // ์ฒซ๋ฒˆ์งธ Box()
    Box(
        ...
    ) {
        // ๋‘๋ฒˆ์งธ Box()
        ...

        // ์„ธ๋ฒˆ์งธ Box()
        Box(
            modifier = Modifier
                .width(200.dp)
                .height(200.dp)
                .background(Color(0x8000FF00)) // ๋ฐ˜ํˆฌ๋ช… ์ดˆ๋ก์ƒ‰
                .customLayoutModifier(),
            contentAlignment = Alignment.Center
        ) {
            Text("321")
        }
    }
}

fun Modifier.customLayoutModifier() = layout { measurable, constraints ->
    val placeable = measurable.measure(constraints)

    layout(placeable.width, placeable.height) {
        placeable.place(
            x = 0,
            y = 0
        )
    }
}

Modifier.layout()์€ ์ปค์Šคํ…€ ๋ ˆ์ด์•„์›ƒ์„ ๋งŒ๋“œ๋Š” ํ•จ์ˆ˜๋‹ค. ์ด ํ•จ์ˆ˜๋ฅผ ์„ธ๋ฒˆ์งธ Box()์— ์ ์šฉํ–ˆ๋‹ค. ํ•˜์ง€๋งŒ, ๊ทธ ๊ตฌํ˜„ ๋‚ด์šฉ์€ ๋ณ„ ๊ฒŒ ์—†๋‹ค. ์—ฌ์ „ํžˆ #3-1๊ณผ ๊ฐ™์€ ํ™”๋ฉด์ด ๋‚˜์˜ค๊ฒŒ๋” ๋งŒ๋“ค์—ˆ๋‹ค. layout()๋Š” ํ•˜๋‚˜์˜ ๋žŒ๋‹ค ํ•จ์ˆ˜๋ฅผ ๋งค๊ฐœ๋ณ€์ˆ˜๋กœ ๋ณด์œ ํ•œ๋‹ค. ๊ทธ๋ฆฌ๊ณ  ๊ทธ ๋žŒ๋‹ค ํ•จ์ˆ˜๋Š” 2๊ฐœ์˜ ๋งค๊ฐœ๋ณ€์ˆ˜๋ฅผ ๊ฐ€์ง„๋‹ค. ๋ฐ”๋กœ measurable๊ณผ constraints๋‹ค.
 
measurable์€ content ํ”„๋กœํผํ‹ฐ์˜ ์˜์—ญ์„ ์˜๋ฏธํ•œ๋‹ค (๋งŽ์€ ์ปดํฌ์ €๋ธ” ํ•จ์ˆ˜๋Š” content๋ผ๋Š” ์ด๋ฆ„์˜ ํ”„๋กœํผํ‹ฐ๋ฅผ ๊ฐ€์ง„๋‹ค). ๋ณธ ์ฝ”๋“œ์—์„œ๋Š” Box()์˜ content ํ”„๋กœํผํ‹ฐ๊ฐ€ ๋˜๊ฒ ๋‹ค.
 
constraints๋Š” layout()์ด ๋ถ™์€ ์ปดํฌ์ €๋ธ”์˜ ๋ถ€๋ชจ ์ปดํฌ์ €๋ธ” ์ฆ‰, ์ฒซ๋ฒˆ์งธ Box()์˜ ์ œ์•ฝ์กฐ๊ฑด์„ ์˜๋ฏธํ•œ๋‹ค.
 
measurable.measure(constraints)์˜ ์˜๋ฏธ๊ฐ€ ๋ญ˜๊นŒ? ๋ฐ”๋กœ #2 ๋„์‹๋„์— ์žˆ๋Š” ์ฃผํ™ฉ์ƒ‰ ๋„ค๋ชจ๋‹ค. ๊ทธ๋ ‡๋‹ค๋ฉด ์œ„ ์ฝ”๋“œ ์† measurable.measure(constraints)๋Š” '์„ธ๋ฒˆ์งธ Box()์— ๋Œ€ํ•œ ์ธก์ • ์š”์ฒญ'์ด๋ผ๋Š” ์˜๋ฏธ๊ฐ€ ๋œ๋‹ค. ๋„์‹๋„์—์„œ๋„ ์•Œ ์ˆ˜ ์žˆ๋“ฏ์ด, ์ธก์ • ์š”์ฒญ์„ ๋ฐ›์€ ๋…ธ๋“œ์— ์ž์‹ ๋…ธ๋“œ๊ฐ€ ์กด์žฌํ•˜๋ฉด ์žฌ๊ท€์ ์œผ๋กœ ์ž์‹ ๋…ธ๋“œ๋“ค์˜ ์ฃผํ™ฉ์ƒ‰ ๋„ค๋ชจ(์ธก์ • ์š”์ฒญ) ๋ฐ ํŒŒ๋ž€์ƒ‰ ๋„ค๋ชจ(ํฌ๊ธฐ์™€ ์œ„์น˜ ๊ฒฐ์ •)์ด Trigger๋œ๋‹ค. ๋”ฐ๋ผ์„œ ์ตœ์ข…์ ์œผ๋กœ placeable์— ๋‹ด๊ธฐ๋Š” ๊ฒƒ์€ 'ํฌ๊ธฐ์™€ ์œ„์น˜๊ฐ€ ๊ฒฐ์ •๋œ ๋ชจ๋“  ์ž์†๋“ค์˜ ๋ชจ์ž„'์ด๋‹ค.
 
๋‚˜๋Š” ์œ„ ์ฝ”๋“œ๋ฅผ layout()์ด ๋ถ™์ง€ ์•Š์€ #3-1์˜ ์ฝ”๋“œ์™€ ๊ฐ™์€ ๊ฒฐ๊ณผ๋ฅผ ๋‚ด๋„๋ก ๋งŒ๋“ค์—ˆ๋‹ค๊ณ  ํ–ˆ๋‹ค. ์ฆ‰, ์œ„ ์ฝ”๋“œ๋Š” ์ปค์Šคํ…€์ด ๊ฐ€๋ฏธ๋˜์ง€ ์•Š์€, Compose์˜ ๊ธฐ๋ณธ ๋™์ž‘์ด๋ผ๋Š” ๋ง์ด๋‹ค. ์—ฌ๊ธฐ์—์„œ ๋‚ด๊ฐ€ measure()์˜ ์ธ์ˆ˜์— ์ฒซ๋ฒˆ์งธ Box()์˜ ์ œ์•ฝ์กฐ๊ฑด์„ ๋„ฃ์€ ์ด์œ ๋ฅผ ์•Œ ์ˆ˜ ์žˆ๋‹ค. ๋ฐ”๋กœ '(#2์˜ ๋„์‹๋„์—์„œ ์•Œ ์ˆ˜ ์žˆ๋Š”) ์ปดํฌ์ €๋ธ”์˜ ๊ณ„์ธต ๊ด€๊ณ„'์—์„œ ์ž์‹์— ๋Œ€ํ•œ ์ธก์ • ์š”์ฒญ์€ ๋ถ€๋ชจ์— ์˜ํ•ด Trigger๋œ๋‹ค๋Š” ์ด์œ ์—์„œ๋‹ค. ์„ธ๋ฒˆ์งธ Box()์˜ ๋ฐฐ์น˜๋Š” ์ฒซ๋ฒˆ์งธ Box()์— ์˜ํ•ด ์ˆ˜ํ–‰๋œ๋‹ค. ๊ทธ๋ ‡๊ธฐ์— (์ปค์Šคํ…€ ์—†์ด ์ˆœ์ •์œผ๋กœ ํ”„๋กœ๊ทธ๋ž˜๋ฐํ•˜๋ ค๋ฉด) ์„ธ๋ฒˆ์งธ Box()๋Š” ์ฒซ๋ฒˆ์งธ Box์˜ ์ œ์•ฝ ์กฐ๊ฑด์„ ์ง€์ผœ์•ผ๋งŒ ํ•˜๋Š” ๊ฒƒ์ด๋‹ค.
 
์Šคํฌ๋ฆฐ์ƒท

#3-1๊ณผ ๋˜‘๊ฐ™์€ ํ™”๋ฉด.
 

#3-3 MeasureScope.layout()

์ฝ”๋“œ 1

fun Modifier.customLayoutModifier() = layout { measurable, constraints ->
    val placeable = measurable.measure(constraints)
    
    layout(placeable.width, placeable.height) {
        placeable.place(
            x = 0,
            y = 0
        )
    }
}

์ด์ œ measurable.measure(constraints) ์•„๋ž˜์— ์žˆ๋Š” MeasureScope.layout()์— ๋Œ€ํ•ด ์„ค๋ช…ํ•œ๋‹ค. Modifier.layout()๊ณผ ์ด๋ฆ„์€ ๊ฐ™์ง€๋งŒ ๋‹ค๋ฅธ ํ•จ์ˆ˜๋‹ค.
 
measurable.measure(constraints)๊ฐ€ #2 ๋„์‹๋„์˜ ์ฃผํ™ฉ์ƒ‰ ๋„ค๋ชจ์— ํ•ด๋‹นํ•œ๋‹ค๋ฉด, MeasureScope.layout()๋Š” ํŒŒ๋ž€์ƒ‰ ๋„ค๋ชจ์— ํ•ด๋‹นํ•œ๋‹ค. ์ž๊ธฐ ์ž์‹ ์˜ ํฌ๊ธฐ์™€ ์œ„์น˜๋ฅผ ๊ฒฐ์ •ํ•˜๊ณ , ์ž์‹๋“ค์„ ๋ฐฐ์น˜ํ•œ๋‹ค. #3-2์—์„œ placeable์— ๋‹ด๊ธฐ๋Š” ๊ฒƒ์€ 'ํฌ๊ธฐ์™€ ์œ„์น˜๊ฐ€ ๊ฒฐ์ •๋œ ๋ชจ๋“  ์ž์†๋“ค์˜ ๋ชจ์ž„'์ด๋ผ๊ณ  ํ–ˆ๋‹ค. ์ด์ œ '๋‚จ์€ ๋‹จ๊ณ„(ํŒŒ๋ž€์ƒ‰ ๋„ค๋ชจ)'๋Š” ์„ธ๋ฒˆ์งธ Box()์˜ ํฌ๊ธฐ ๋ฐ ์œ„์น˜ ๊ฒฐ์ •๊ณผ ๋ชจ์ž„์˜ ์‹ค์ œ ๋ฐฐ์น˜๋‹ค.
 
MeasureScope.layout()์˜ ์ธ์ˆ˜ width ๋ฐ height์— ์–ด๋–ค ๊ฐ’์„ ์ „๋‹ฌํ•˜๋Š๋ƒ ๋”ฐ๋ผ ์ž๊ธฐ ์ž์‹ ์˜ ํฌ๊ธฐ๊ฐ€ ๊ฒฐ์ •๋œ๋‹ค. ์œ„ ์ฝ”๋“œ์—์„œ๋Š” placeable.width ๋ฐ placeable.height๋ฅผ ๊ทธ๋Œ€๋กœ ๋„ฃ์–ด์ฃผ๊ณ  ์žˆ๋‹ค. ๊ทธ๋ ‡๋‹ค๋ฉด, ํ•ด๋‹น ๊ฐ’๋“ค์€ ์ž์‹์ธ Text("321")์˜ ๋„ˆ๋น„ ๋ฐ ๋†’์ด์ผ๊นŒ? ์•„๋‹ˆ๋‹ค. ์„ธ๋ฒˆ์งธ Box()์˜ Modifier์— ํ•˜๋“œ ์ฝ”๋”ฉํ•ด๋‘์—ˆ๋˜ ๋„ˆ๋น„(200.dp) ๋ฐ ๋†’์ด(200.dp) ๊ฐ’์ด๋‹ค. ์ด ํ•˜๋“œ ์ฝ”๋”ฉ์œผ๋กœ ์ธํ•ด constraints์˜ ์ œ์•ฝ ์กฐ๊ฑด์ด 200.dp๋กœ ๊ณ ์ •๋˜๊ธฐ ๋•Œ๋ฌธ์ด๋‹ค. ์ด์ƒํ•˜์ง€ ์•Š์€๊ฐ€? ๋‚˜๋Š” #3-2์—์„œ ๋ถ„๋ช…ํžˆ constraints๋Š” ์„ธ๋ฒˆ์งธ Box()๊ฐ€ ์•„๋‹Œ ์ฒซ๋ฒˆ์งธ Box()์˜ ์ œ์•ฝ ์กฐ๊ฑด์ด๋ผ ์„ค๋ช…ํ–ˆ๋‹ค. ๋„ˆ๋น„(200.dp) ๋ฐ ๋†’์ด(200.dp) ๊ฐ’์€ ์„ธ๋ฒˆ์งธ Box()์˜ ๊ฐ’์ธ๋ฐ ์ด๊ฒŒ ์™œ constraints์˜ ๊ฐ’์„ ์ˆ˜์ •ํ•ด๋ฒ„๋ฆฌ๋Š”๊ฐ€?
 
๊ฒฐ๋ก ๋ถ€ํ„ฐ ๋งํ•˜๋ฉด Modifier.width()๋‚˜ Modifier.height()์™€ ๊ฐ™์€ ํฌ๊ธฐ ๊ด€๋ จ Modifier๊ฐ€ ๋ถ™์œผ๋ฉด, Jetpack Compose ์ปดํŒŒ์ผ๋Ÿฌ๊ฐ€ ๋ถ€๋ชจ์˜ constraints๋ฅผ ๋ฌด์‹œํ•˜๊ณ  ์ƒˆ๋กญ๊ฒŒ (Modifier.width()๋‚˜ Modifier.height()์— ์˜ํ•ด) ์ˆ˜์ •๋œ constraints๋ฅผ ๋งŒ๋“ค์–ด ์ „๋‹ฌํ•˜๊ธฐ ๋•Œ๋ฌธ์ด๋‹ค. ์•„๋ž˜ ์ฝ”๋“œ๋กœ ์ด ์‚ฌ์‹ค์„ ์ฆ๋ช…ํ•ด๋ณด์ด๊ฒ ๋‹ค.
 
์ฝ”๋“œ 2

Scaffold(
    ...
) { ...
    // ์ฒซ๋ฒˆ์งธ Box()
    Box(
        ...
    ) {
        // ๋‘๋ฒˆ์งธ Box()
        ...

        // ์„ธ๋ฒˆ์งธ Box()
        Box(
            modifier = Modifier
                .width(200.dp)
                .height(200.dp)
                .background(Color(0x8000FF00)) // ๋ฐ˜ํˆฌ๋ช… ์ดˆ๋ก์ƒ‰
                .customLayoutModifier(),
            contentAlignment = Alignment.Center
        ) {
            Text("321")
        }
    }
}

fun Modifier.customLayoutModifier() = layout { measurable, constraints ->
    val placeable = measurable.measure(constraints)

    Log.d("interfacer_han", "constraints: $constraints")

    layout(placeable.width, placeable.height) {
        placeable.place(
            x = 0,
            y = 0
        )
    }
}

๊ธฐ์กด ์ฝ”๋“œ์— Log.d( ... )๋ฅผ ์ถ”๊ฐ€ํ–ˆ๋‹ค. ๋กœ๊ทธ ๋ฉ”์‹œ์ง€๋กœ "constraints: Constraints(minWidth = 600, maxWidth = 600, minHeight = 600, maxHeight = 600)"๊ฐ€ ์ถœ๋ ฅ๋œ๋‹ค. 200์ด ์•„๋‹ˆ๋ผ 600์ธ ์ด์œ ๋Š” ๋‹จ์œ„์˜ ์ฐจ์ด ๋•Œ๋ฌธ์ด๋‹ค. ๋ณธ ํ”„๋กœ์ ํŠธ์—์„œ 200.dp๋Š” 600.px์ด๋‹ค. ์–ด์จŒ๋“ , ์ € constraints๋Š” "๊ทธ๋ƒฅ ๋ฌด์กฐ๊ฑด 600.px๋กœ ์„ค์ •ํ•ด!"๋ผ๊ณ  ๋งํ•˜๋Š” ๋“ฏ ํ•˜๋‹ค. ๊ทธ์•ผ๋ง๋กœ ํ•˜๋“œ ์ฝ”๋”ฉ์ด๋‹ค.
 
์ฝ”๋“œ 3

Scaffold(
    ...
) { ...
    // ์ฒซ๋ฒˆ์งธ Box()
    Box(
        ...
    ) {
        // ๋‘๋ฒˆ์งธ Box()
        ...

        // ์„ธ๋ฒˆ์งธ Box()
        Box(
            modifier = Modifier
                .background(Color(0x8000FF00)) // ๋ฐ˜ํˆฌ๋ช… ์ดˆ๋ก์ƒ‰
                .customLayoutModifier(),
            contentAlignment = Alignment.Center
        ) {
            Text("321")
        }
    }
}

fun Modifier.customLayoutModifier() = layout { measurable, constraints ->
    val placeable = measurable.measure(constraints)
    
    Log.d("interfacer_han", "constraints: $constraints")

    layout(placeable.width, placeable.height) {
        placeable.place(
            x = 0,
            y = 0
        )
    }
}

๋ฐฉ๊ธˆ ์ฝ”๋“œ์—์„œ ์„ธ๋ฒˆ์งธ Box()์˜ Modifier.width() ๋ฐ Modifier.height()๋ฅผ ์ œ๊ฑฐํ–ˆ๋‹ค. ๋กœ๊ทธ ๋ฉ”์‹œ์ง€๋กœ "constraints: Constraints(minWidth = 0, maxWidth = 1344, minHeight = 0, maxHeight = 2769"๊ฐ€ ์ถœ๋ ฅ๋œ๋‹ค. ์ด๊ฒŒ ์ง„์งœ, ๊ฐ€๋กœ์ฑ„์ง€์ง€ ์•Š์€ ์ฒซ๋ฒˆ์งธ Box()์˜ constraints๋‹ค.
 

#3-4 ์ปค์Šคํ…€ - ์ž์‹์˜ ๋ฐฐ์น˜

์ฝ”๋“œ 1

Scaffold(
    ...
) { ...
    // ์ฒซ๋ฒˆ์งธ Box()
    Box(
        ...
    ) {
        // ๋‘๋ฒˆ์งธ Box()
        ...

        // ์„ธ๋ฒˆ์งธ Box()
        Box(
            modifier = Modifier
                .width(200.dp)
                .height(200.dp)
                .background(Color(0x8000FF00)) // ๋ฐ˜ํˆฌ๋ช… ์ดˆ๋ก์ƒ‰
                .customLayoutModifier(),
            contentAlignment = Alignment.Center
        ) {
            Text("321")
        }
    }
}

fun Modifier.customLayoutModifier() = layout { measurable, constraints ->
    val placeable = measurable.measure(constraints)

    layout(placeable.width, placeable.height) {
        placeable.place(
            x = 100,
            y = 100
        )
    }
}

x = 100, y = 100 ๋ถ€๋ถ„์„ ์ œ์™ธํ•˜๋ฉด #3-2์™€ ๋™์ผํ•œ ์ฝ”๋“œ๋‹ค. placeable.place()๋Š” MeasureScope.layout()์˜ placementBlock ํ”„๋กœํผํ‹ฐ์— ํ•ด๋‹นํ•˜๋Š” ๋ถ€๋ถ„์œผ๋กœ, ํ˜•ํƒœ๋Š” Placeable.PlacementScope.() -> Unit ์ด๋‹ค. ์—ฌ๊ธฐ์„œ๋Š” #2 ๋„์‹๋„์˜ ํŒŒ๋ž€์ƒ‰ ๋„ค๋ชจ์˜ ๋‚ด์šฉ ์ค‘, ์ž์†๋“ค์˜ ๋ชจ์ž„์„ ๋ฐฐ์น˜ํ•˜๋Š” ๋ถ€๋ถ„์— ํ•ด๋‹นํ•œ๋‹ค. Jetpack Compose์—์„œ๋Š” ๋งจ โ†–์ชฝ ์ขŒํ‘œ๊ฐ€ (0, 0)์ด๋‹ค. ๋”ฐ๋ผ์„œ (100, 100)์€ โ†“์ชฝ์œผ๋กœ 100ํ”ฝ์…€, โ†’์ชฝ์œผ๋กœ 100ํ”ฝ์…€๋งŒํผ ๋–จ์–ด์ ธ ๋ฐฐ์น˜ํ•˜๋ผ๋Š” ๋ง์ด ๋œ๋‹ค. ์Šคํฌ๋ฆฐ์ƒท์„ ํ™•์ธํ•ด๋ณด์ž.
 
 
์Šคํฌ๋ฆฐ์ƒท 1

์†”์งํžˆ, ๋ญ๊ฐ€ ์–ด๋–ป๊ฒŒ ๋œ๊ฑด์ง€ ๊ฐ์ด ์•ˆ์žกํžŒ๋‹ค. ๊ทธ๋ž˜์„œ ์ฝ”๋“œ๋ฅผ ์•ฝ๊ฐ„ ์ˆ˜์ •ํ–ˆ๋‹ค.
 
์ฝ”๋“œ 2

Scaffold(
    ...
) { innerPadding ->
    // ์ฒซ๋ฒˆ์งธ Box()
    Box(
       ...
    ) {
        // ๋‘๋ฒˆ์งธ Box()
       ...

        // ์„ธ๋ฒˆ์งธ Box()
        Box(
            modifier = Modifier
                .width(200.dp)
                .height(200.dp)
                .background(Color(0x4000FF00)) // ๋ฐ˜ํˆฌ๋ช… ์ดˆ๋ก์ƒ‰
                .customLayoutModifier()
                .background(Color(0x60FF00FF)), // ๋ฐ˜ํˆฌ๋ช… ๋ณด๋ผ์ƒ‰
            contentAlignment = Alignment.Center
        ) {
            Text("321")
        }
    }
}

 customLayoutModifier() ๋’ค์— background(Color(0x60FF00FF))๋ฅผ ์ถ”๊ฐ€ํ•ด๋ดค๋‹ค. ๋‹ค์‹œ ์Šคํฌ๋ฆฐ์ƒท์„ ๋ณด์ž.
 
 
์Šคํฌ๋ฆฐ์ƒท 2

์ด์ œ์•ผ ๊ฐ์ด ์ข€ ์žกํžŒ๋‹ค. ์ดˆ๋ก์ƒ‰ ๋„ค๋ชจ๋Š” 3๋ฒˆ์งธ Box()์˜ ์˜์—ญ์ด๋‹ค. ์ดˆ๋ก์ƒ‰ ๋„ค๋ชจ์˜ ์˜์—ญ์„ ๊ทธ๋Œ€๋กœ โ†“์ชฝ์œผ๋กœ 100ํ”ฝ์…€๋งŒํผ โ†’์ชฝ์œผ๋กœ 100ํ”ฝ์…€๋งŒํผ ์›€์ง์ธ๊ฒŒ ๋ฐ”๋กœ, 3๋ฒˆ์งธ Box()์˜ '์ž์†๋“ค์˜ ๋ชจ์ž„์˜ ์˜์—ญ(๋ณด๋ผ์ƒ‰ ๋„ค๋ชจ)'์ด ๋œ ๊ฒƒ์ด๋‹ค.
 
ํ˜น์‹œ ์„ธ๋ฒˆ์งธ Box(), ์ฆ‰ ์ดˆ๋ก์ƒ‰ ์˜์—ญ์€ ์™œ ์•ˆ ์›€์ง์ด๋Š” ๊ฑด์ง€ ๊ถ๊ธˆํ•œ ์‚ฌ๋žŒ๋„ ์žˆ๊ฒ ๋‹ค. ๊ทธ๋Ÿฐ ์‚ฌ๋žŒ๋“ค์€ #2 ๋„์‹๋„์˜ ํŒŒ๋ž€์ƒ‰ ๋„ค๋ชจ๋ฅผ ๋‹ค์‹œ ๋ณด๋ผ. ์ž์†๋“ค์˜ ๋ชจ์ž„์„ ๋ฐฐ์น˜ํ•˜๋Š”๊ฑฐ์ง€, ์ž๊ธฐ ์ž์‹ ์„ ๋ฐฐ์น˜ํ•˜์ง€๋Š” ์•Š๋Š”๋‹ค. ์„ธ๋ฒˆ์งธ Box()์˜ ์˜์—ญ ๋ฐฐ์น˜๋Š”, ์ฒซ๋ฒˆ์งธ Box()๊ฐ€ ์ˆ˜ํ–‰ํ•œ๋‹ค. ๊ทธ๋ฆฌ๊ณ  ์ฒซ๋ฒˆ์งธ Box()์—๋Š” contentAlignment = Alignment.Center๊ฐ€ ์ ์šฉ๋˜์–ด์žˆ๋‹ค (#3-1 ์ฐธ์กฐ). ๋”ฐ๋ผ์„œ ์ฒซ๋ฒˆ์งธ Box()์˜ ์ •๋ ฌ ๊ทœ์น™์„ ๋ฐ”๊พธ์ง€ ์•Š๋Š” ํ•œ, ์„ธ๋ฒˆ์งธ Box()๋Š” ๋ฌด์Šจ ์ง“์„ ํ•ด๋„ ๋ฐ˜๋“œ์‹œ ์ •์ค‘์•™์— ์œ„์น˜ํ•œ๋‹ค.
 

#3-5 ์ปค์Šคํ…€ - ์ž์‹ ์˜ ํฌ๊ธฐ

์ฝ”๋“œ

fun Modifier.customLayoutModifier() = layout { measurable, constraints ->
    val placeable = measurable.measure(constraints)
    
    layout(placeable.width + 300, placeable.height + 300) {
        placeable.place(
            x = 100,
            y = 100
        )
    }
}

#3-4์˜ ์ฝ”๋“œ 2์™€ ๋น„๊ตํ•ด placeable.width + 300, placeable.height + 300 ๋ถ€๋ถ„์„ ์ œ์™ธํ•˜๋ฉด ๊ฐ™์€ ์ฝ”๋“œ๋‹ค. ์ด๋Ÿฌ๋ฉด ์–ด๋–ป๊ฒŒ ๋ ๊นŒ?
 
์Šคํฌ๋ฆฐ์ƒท

์Šคํฌ๋ฆฐ์ƒท์˜ ๋นจ๊ฐ„์ƒ‰ ์ ๊ณผ ์ฃผํ™ฉ์ƒ‰ ์ ์€ ๋‚ด๊ฐ€ ์Šคํฌ๋ฆฐ์ƒท์„ ๊ทธ๋ฆผํŒ์œผ๋กœ ์—ด์–ด์„œ ๋”ฐ๋กœ ์ฐ์€ ๊ฒƒ์ด๋‹ค. ์„ธ๋ฒˆ์งธ Box()์˜ ๋„ˆ๋น„์™€ ๋†’์ด๊ฐ€ ๊ฐ๊ฐ 300ํ”ฝ์…€์”ฉ ์ปค์กŒ๋‹ค. ๊ทธ๋ž˜์„œ ์„ธ๋ฒˆ์งธ Box()์˜ (0, 0) ๊ธฐ์ค€์ ์ด ์ฃผํ™ฉ์ƒ‰ ์ ์—์„œ ๋นจ๊ฐ„์ƒ‰ ์ ์œผ๋กœ ์ด๋™ํ–ˆ๋‹ค. ๋ณด๋ผ์ƒ‰ ์˜์—ญ์€ ์—ฌ์ „ํžˆ (0, 0)๋กœ๋ถ€ํ„ฐ โ†“์ชฝ์œผ๋กœ 100ํ”ฝ์…€๋งŒํผ โ†’์ชฝ์œผ๋กœ 100ํ”ฝ์…€๋งŒํผ ์›€์ง์ธ ๊ณณ์— ์œ„์น˜ํ•œ๋‹ค.
 

#4 Layout()

#4-1 ์ผ๊ด„์  ์ปค์Šคํ…€

Layout()์€ ์•ž์„œ ์‚ดํŽด๋ณธ Modifier.layout()์˜ ๋™์ž‘์„ ์ผ๊ด„์ ์œผ๋กœ ์ˆ˜ํ–‰ํ•œ๋‹ค. ์–ด๋–ค ์ปดํฌ์ €๋ธ”์ด ์—ฌ๋Ÿฌ ์ž์‹์„ ๊ฐ€์ง€๊ณ  ์žˆ๋Š”๋ฐ, ๊ทธ ์ž์‹๋“ค์„ ๊ฐ๊ฐ ๋”ฐ๋กœ ์ปค์Šคํ…€ ํ•ด์ค„ ๋•Œ Layout()์„ ์‚ฌ์šฉํ•œ๋‹ค.
 

#4-2 ๊ฐ„๋‹จํ•œ UI

์ฝ”๋“œ

Scaffold(
    modifier = Modifier.fillMaxSize()
) { innerPadding ->
    Column(
        modifier = Modifier
            .fillMaxSize()
            .padding(innerPadding)
    ) {
        Text(text = "11111", fontSize = 32.sp)
        Text(text = "22222", fontSize = 32.sp)
        Text(text = "33333", fontSize = 32.sp)
        Text(text = "44444", fontSize = 32.sp)
        Text(text = "55555", fontSize = 32.sp)
    }
}

๊ฐ„๋‹จํ•œ Column์ด๋‹ค.
 
์Šคํฌ๋ฆฐ์ƒท

์Šคํฌ๋ฆฐ์ƒท์€ ์œ„์™€ ๊ฐ™๋‹ค.
 

#4-3 ์ปค์Šคํ…€

์ฝ”๋“œ

Scaffold(
    modifier = Modifier.fillMaxSize()
) { innerPadding ->
    ReplicatedColumn(
        modifier = Modifier.padding(innerPadding)
    ) {
        Text(text = "11111", fontSize = 32.sp)
        Text(text = "22222", fontSize = 32.sp)
        Text(text = "33333", fontSize = 32.sp)
        Text(text = "44444", fontSize = 32.sp)
        Text(text = "55555", fontSize = 32.sp)
    }
}
                
@Composable
fun ReplicatedColumn(
    modifier: Modifier = Modifier,
    content: @Composable () -> Unit
) {
    Layout(
        modifier = modifier,
        content = content
    ) { measurables, constraints ->
        val placeables = measurables.map { measurable ->
            measurable.measure(constraints)
        }

        /* ReplicatedColumn์˜ ํฌ๊ธฐ๋ฅผ ๊ฐ€๋Šฅํ•œ ํ•œ ํฌ๊ฒŒ (= ๋ถ€๋ชจ์˜ ์ตœ๋Œ€ ๋„ˆ๋น„ ๋ฐ ์ตœ๋Œ€ ๋†’์ด๋ฅผ ์ง€๋‹ˆ๋„๋ก) ์„ค์ •
         * ๋ณธ ์ฝ”๋“œ์—์„œ๋Š”, ReplicatedColumn์˜ ๋ถ€๋ชจ์ธ Scaffold๊ฐ€ ๊ฐ€์งˆ ์ˆ˜ ์žˆ๋Š” ์ตœ๋Œ€ ํฌ๊ธฐ๋กœ ์„ค์ •
         */
        layout(constraints.maxWidth, constraints.maxHeight) {
            var yPosition = 0

            placeables.forEach { placeable ->
                placeable.place(x = 0, y = yPosition)
                yPosition += placeable.height
            }
        }
    }
}

#4-2๋ฅผ Column() ๋Œ€์‹  Layout()์„ ์ด์šฉํ•ด ๊ตฌํ˜„ํ•ด๋ณด์•˜๋‹ค. ์ž์„ธํ•œ ์„ค๋ช…์€ #3์—์„œ ํ–ˆ์œผ๋ฏ€๋กœ ์ƒ๋žตํ•œ๋‹ค. MeasureScope.layout()์—์„œ ReplicatedColumn์˜ ํฌ๊ธฐ๋ฅผ ์ตœ๋Œ€๋กœ ์„ค์ •ํ•˜๊ธฐ์—, Column์— ๋ถ™์˜€๋˜ fillMaxSize()๋Š” ์ œ๊ฑฐํ–ˆ๋‹ค. ์ œ๊ฑฐํ•˜์ง€ ์•Š๋”๋ผ๋„ ๊ฒฐ๊ณผ๋Š” ์–ด์ฐจํ”ผ ๊ฐ™๊ฒ ์ง€๋งŒ.
 
Layout()์˜ ๋žŒ๋‹ค ํ•จ์ˆ˜๋Š” Modifier.layout()์˜ ๋žŒ๋‹ค ํ•จ์ˆ˜์™€๋Š” ๋‹ฌ๋ฆฌ, measurable ๋Œ€์‹  measurable's'์„ ๋งค๊ฐœ๋ณ€์ˆ˜๋กœ ๊ฐ€์ง„๋‹ค. ์ผ๊ด„์ ์œผ๋กœ ๋ณต์ˆ˜ ๊ฐœ์˜ ์ž์‹ ์ปดํฌ์ €๋ธ”์„ ์ปค์Šคํ…€ํ•ด์•ผํ•˜๋‹ˆ ๊ทธ๋ ‡๋‹ค. ๊ทธ๋ ‡๊ธฐ์— placeable ๋Œ€์‹  placeable's'์„ ์“ฐ๊ณ , placeable.place()๋„ forEach๋ฌธ์— ๋“ค์–ด๊ฐ€ ์žˆ๋Š” ๋ชจ์Šต์„ ํ™•์ธํ•  ์ˆ˜ ์žˆ๋‹ค.
 
์Šคํฌ๋ฆฐ์ƒท

#4-2์˜ ์Šคํฌ๋ฆฐ์ƒท์„ ๊ทธ๋Œ€๋กœ ๊ฐ€์ ธ์˜จ ๊ฒŒ ์•„๋‹Œ๊ฐ€ ์‹ถ๊ฒ ์ง€๋งŒ, ์•„๋‹ˆ๋‹ค. ์ด๊ฑธ๋กœ Layout()์— ๋Œ€ํ•œ ์„ค๋ช…์„ ๋งˆ์นœ๋‹ค.
 

#5 ์š”์•ฝ

์ปค์Šคํ…€ ๋ ˆ์ด์•„์›ƒ์€ Compose UI ๊ธฐ์ œ ๋„์‹๋„ ์ƒ์˜ ๊ฐ ๋‹จ๊ณ„๋ฅผ ์˜ค๋ฒ„๋ผ์ด๋“œํ•œ๋‹ค.

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

[Android] UI architecture - Phase์™€ State  (1) 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