시배's Android

Compose Docs | Custom layouts 본문

Android/Compose Docs

Compose Docs | Custom layouts

si8ae 2023. 8. 26. 20:09
 

맞춤 레이아웃  |  Jetpack Compose  |  Android Developers

맞춤 레이아웃 컬렉션을 사용해 정리하기 내 환경설정을 기준으로 콘텐츠를 저장하고 분류하세요. Compose에서 UI 요소는 호출될 때 UI 요소를 내보내는 구성 가능한 함수로 표시됩니다. 그런 다음

developer.android.com

Composable 함수를 사용하여 Compose에서 UI 요소는 호출될 때 UI 조각을 내보내는 함수로 나타납니다. 이 UI 조각은 화면에 렌더링되는 UI 트리에 추가됩니다. 각 UI 요소는 하나의 부모와 잠재적으로 여러 개의 자식을 가지고 있습니다. 또한 각 요소는 부모 내에서 위치가 지정되며, (x, y) 위치와 너비 및 높이로 크기가 지정됩니다.

부모 요소는 자식 요소에 대한 제약 조건을 정의합니다. 요소는 이러한 제약 조건 내에서 크기를 정의해야 합니다. 제약 조건은 요소의 최소 및 최대 너비와 높이를 제한합니다. 요소에 자식 요소가 있는 경우 각 자식을 측정하여 크기를 결정하는 데 도움이 될 수 있습니다. 요소가 자체 크기를 결정하고 보고한 후에는 자식 요소를 자신에 상대적으로 배치하는 방법을 정의할 수 있는 기회가 있습니다. 

UI 트리의 각 노드를 레이아웃하는 과정은 세 가지 단계로 이루어집니다. 각 노드는 다음 작업을 수행해야 합니다:

  • 자식 요소의 크기를 측정합니다.
  • 자체 크기를 결정합니다.
  • 자식 요소를 배치합니다.

스코프의 사용은 자식 요소의 측정 배치를 언제 수행할 있는지를 정의합니다. 레이아웃의 측정은 측정 레이아웃 패스 중에만 수행할 있으며, 자식 요소는 레이아웃 패스 중에만 배치될 있습니다 (그리고 측정이 이루어진 후에만 배치 가능합니다). Compose MeasureScope PlacementScope 같은 스코프로 인해 이는 컴파일 시간에 강제됩니다.

Use the layout modifier

요소의 측정과 레이아웃 방식을 수정하는 레이아웃 수정자(layout modifier) 사용할 있습니다. 레이아웃은 람다(lambda)이며, 해당 매개변수에는 측정 가능한(measurable) 요소와 해당 컴포저블의 들어오는 제약 조건이 포함됩니다. 이러한 커스텀 레이아웃 수정자는 다음과 같이 구성될 있습니다:

fun Modifier.customLayoutModifier() =
    layout { measurable, constraints ->
        // ...
    }

화면에 텍스트(Text) 표시하고 번째 텍스트 줄의 기준선으로부터 위쪽까지의 거리를 제어해 보겠습니다. 이것은 바로 paddingFromBaseline 수정자가 수행하는 작업입니다. 우리는 여기서 이를 예제로 구현하고 있습니다. 이를 위해 layout 수정자를 사용하여 컴포저블을 화면에 수동으로 배치합니다. 다음은 원하는 동작입니다. 여기서는 텍스트(Text) 상단 패딩을 24.dp 설정하고자 합니다:

fun Modifier.firstBaselineToTop(
    firstBaselineToTop: Dp
) = layout { measurable, constraints ->
    // Measure the composable
    val placeable = measurable.measure(constraints)

    // Check the composable has a first baseline
    check(placeable[FirstBaseline] != AlignmentLine.Unspecified)
    val firstBaseline = placeable[FirstBaseline]

    // Height of the composable with padding - first baseline
    val placeableY = firstBaselineToTop.roundToPx() - firstBaseline
    val height = placeable.height + placeableY
    layout(placeable.width, height) {
        // Where the composable gets placed
        placeable.placeRelative(0, placeableY)
    }
}

해당 코드에서 일어나는 일은 다음과 같습니다:

  • 측정 가능한(lambda 매개변수) 내에서, 측정 가능한 매개변수를 사용하여 measurable.measure(constraints)를 호출하여 Text의 크기를 측정합니다.
  • composable의 크기를 지정하기 위해 layout(width, height) 메서드를 호출하며, 또한 래핑된 요소를 배치하는 데 사용되는 람다가 제공됩니다. 이 경우, 이는 마지막 기준선과 추가된 상단 패딩 사이의 높이입니다.
  • placeable.place(x, y)를 호출하여 화면에 래핑된 요소를 배치합니다. 래핑된 요소가 배치되지 않으면 보이지 않을 것입니다. 여기서 y 위치는 첫 번째 텍스트 줄의 기준선의 위치에서 상단 패딩을 나타냅니다.
  • 동작이 예상대로 작동하는지 확인하려면, Text 수정자를 사용하십시오:
@Preview
@Composable
fun TextWithPaddingToBaselinePreview() {
    MyApplicationTheme {
        Text("Hi there!", Modifier.firstBaselineToTop(32.dp))
    }
}

@Preview
@Composable
fun TextWithNormalPaddingPreview() {
    MyApplicationTheme {
        Text("Hi there!", Modifier.padding(top = 32.dp))
    }
}

Create custom layouts

레이아웃 수정자(layout modifier) 호출하는 컴포저블만 변경합니다. 여러 개의 컴포저블을 측정하고 레이아웃하려면 대신 Layout 컴포저블을 사용하세요. 컴포저블을 사용하면 자식 컴포저블을 수동으로 측정하고 레이아웃할 있습니다. Column Row 같은 상위 수준 레이아웃은 모두 Layout 컴포저블을 기반으로 구축됩니다.

@Composable
fun MyBasicColumn(
    modifier: Modifier = Modifier,
    content: @Composable () -> Unit
) {
    Layout(
        modifier = modifier,
        content = content
    ) { measurables, constraints ->
        // measure and position children given constraints logic here
        // ...
    }
}

레이아웃 수정자(layout modifier) 유사하게, measurables 측정되어야 자식 요소의 목록이며 constraints 부모로부터의 제약 조건입니다. 이전과 같은 논리를 따라, MyBasicColumn 다음과 같이 구현할 있습니다:

@Composable
fun MyBasicColumn(
    modifier: Modifier = Modifier,
    content: @Composable () -> Unit
) {
    Layout(
        modifier = modifier,
        content = content
    ) { measurables, constraints ->
        // Don't constrain child views further, measure them with given constraints
        // List of measured children
        val placeables = measurables.map { measurable ->
            // Measure each children
            measurable.measure(constraints)
        }

        // Set the size of the layout as big as it can
        layout(constraints.maxWidth, constraints.maxHeight) {
            // Track the y co-ord we have placed children up to
            var yPosition = 0

            // Place children in the parent layout
            placeables.forEach { placeable ->
                // Position item on the screen
                placeable.placeRelative(x = 0, y = yPosition)

                // Record the y co-ord placed up to
                yPosition += placeable.height
            }
        }
    }
}

자식 컴포저블은 레이아웃 제약 조건을 통해 제한되며(minHeight 제약 조건은 제외됨), 이전 컴포저블의 yPosition 기반으로 배치됩니다.

@Composable
fun CallingComposable(modifier: Modifier = Modifier) {
    MyBasicColumn(modifier.padding(8.dp)) {
        Text("MyBasicColumn")
        Text("places items")
        Text("vertically.")
        Text("We've done it by hand!")
    }
}