Compose 레이아웃의 내장 기능 측정

Compose 규칙 중 하나는 하위 요소를 한 번만 측정해야 한다는 것입니다. 하위 요소를 두 번 측정하면 런타임 예외가 발생합니다. 하지만 하위 요소를 측정하기 전에 하위 요소에 관한 정보가 필요한 경우도 있습니다.

내장 기능을 사용하면 하위 요소가 실제로 측정되기 전에 하위 요소를 쿼리할 수 있습니다.

컴포저블에 IntrinsicSize.Min 또는 IntrinsicSize.Max를 요청할 수 있습니다.

  • Modifier.width(IntrinsicSize.Min) - 콘텐츠를 적절하게 표시하는 데 필요한 최소 너비는 무엇인가요?
  • Modifier.width(IntrinsicSize.Max) - 콘텐츠를 적절하게 표시하는 데 필요한 최대 너비는 무엇인가요?
  • Modifier.height(IntrinsicSize.Min) - 콘텐츠를 적절하게 표시하는 데 필요한 최소 높이는 무엇인가요?
  • Modifier.height(IntrinsicSize.Max) - 콘텐츠를 적절하게 표시하는 데 필요한 최대 높이는 무엇인가요?

예를 들어 맞춤 레이아웃에서 width 제약 조건이 무한대인 TextminIntrinsicHeight를 요청하면 텍스트가 한 줄에 그려진 Textheight가 반환됩니다.

내장 기능 실제 사례

구분선으로 구분된 두 텍스트를 화면에 표시하는 컴포저블을 만들 수 있습니다.

나란히 표시된 두 텍스트 요소, 사이에 세로 구분선이 있음

이렇게 하려면 사용 가능한 공간을 채우는 두 개의 Text 컴포저블과 가운데에 있는 Divider를 사용하여 Row를 사용합니다. Divider는 가장 높은 Text만큼 높아야 하고 가늘어야 합니다 (width = 1.dp).

@Composable
fun TwoTexts(modifier: Modifier = Modifier, text1: String, text2: String) {
    Row(modifier = modifier) {
        Text(
            modifier = Modifier
                .weight(1f)
                .padding(start = 4.dp)
                .wrapContentWidth(Alignment.Start),
            text = text1
        )
        VerticalDivider(
            color = Color.Black,
            modifier = Modifier.fillMaxHeight().width(1.dp)
        )
        Text(
            modifier = Modifier
                .weight(1f)
                .padding(end = 4.dp)
                .wrapContentWidth(Alignment.End),

            text = text2
        )
    }
}

Divider가 전체 화면으로 확장되는데 이는 원하는 동작이 아닙니다.

나란히 표시된 두 텍스트 요소, 사이에 구분선이 있지만 구분선이 텍스트 하단 아래로 확장됨

Row가 각 하위 요소를 개별적으로 측정하며 Text의 높이를 사용하여 Divider를 제약할 수 없기 때문에 이러한 결과가 발생합니다.

Divider가 가용 공간을 지정된 높이로 채우도록 하려면 height(IntrinsicSize.Min) 수정자를 사용하세요.

height(IntrinsicSize.Min)는 하위 요소의 크기를 고유한 최소 높이로 지정합니다. 이 수정자는 반복적이므로 Row 및 그 하위 요소의 minIntrinsicHeight를 쿼리합니다.

이 수정자를 코드에 적용하면 예상대로 작동합니다.

@Composable
fun TwoTexts(modifier: Modifier = Modifier, text1: String, text2: String) {
    Row(modifier = modifier.height(IntrinsicSize.Min)) {
        Text(
            modifier = Modifier
                .weight(1f)
                .padding(start = 4.dp)
                .wrapContentWidth(Alignment.Start),
            text = text1
        )
        VerticalDivider(
            color = Color.Black,
            modifier = Modifier.fillMaxHeight().width(1.dp)
        )
        Text(
            modifier = Modifier
                .weight(1f)
                .padding(end = 4.dp)
                .wrapContentWidth(Alignment.End),

            text = text2
        )
    }
}

// @Preview
@Composable
fun TwoTextsPreview() {
    MaterialTheme {
        Surface {
            TwoTexts(text1 = "Hi", text2 = "there")
        }
    }
}

미리보기는 다음과 같습니다.

나란히 표시된 두 텍스트 요소, 사이에 세로 구분선이 있음

Row의 높이는 다음과 같이 결정됩니다.

  • Row 컴포저블의 minIntrinsicHeight는 하위 요소의 최대 minIntrinsicHeight입니다.
  • Divider 요소의 minIntrinsicHeight는 제약 조건이 주어지지 않으면 공간을 차지하지 않으므로 0입니다.
  • Text minIntrinsicHeight은 특정 width의 텍스트입니다.
  • 따라서 Row 요소의 height 제약 조건은 Text의 최대 minIntrinsicHeight가 됩니다.
  • 그런 다음 DividerheightRow가 지정한 height 제약 조건으로 확장합니다.

맞춤 레이아웃의 내장 기능

맞춤 Layout 또는 layout 수정자를 만들 때 내장 기능 측정은 근사값에 따라 자동으로 계산됩니다. 따라서 일부 레이아웃의 계산은 정확하지 않을 수 있습니다. 이러한 API는 이와 같은 기본값을 재정의하는 옵션을 제공합니다.

맞춤 Layout의 내장 기능 측정을 지정하려면 만들 때 MeasurePolicy 인터페이스의 minIntrinsicWidth, minIntrinsicHeight, maxIntrinsicWidth, maxIntrinsicHeight를 재정의하세요.

@Composable
fun MyCustomComposable(
    modifier: Modifier = Modifier,
    content: @Composable () -> Unit
) {
    Layout(
        content = content,
        modifier = modifier,
        measurePolicy = object : MeasurePolicy {
            override fun MeasureScope.measure(
                measurables: List<Measurable>,
                constraints: Constraints
            ): MeasureResult {
                // Measure and layout here
                // ...
            }

            override fun IntrinsicMeasureScope.minIntrinsicWidth(
                measurables: List<IntrinsicMeasurable>,
                height: Int
            ): Int {
                // Logic here
                // ...
            }

            // Other intrinsics related methods have a default value,
            // you can override only the methods that you need.
        }
    )
}

맞춤 layout 수정자를 만들 때 LayoutModifier 인터페이스에서 관련 메서드를 재정의합니다.

fun Modifier.myCustomModifier(/* ... */) = this then object : LayoutModifier {

    override fun MeasureScope.measure(
        measurable: Measurable,
        constraints: Constraints
    ): MeasureResult {
        // Measure and layout here
        // ...
    }

    override fun IntrinsicMeasureScope.minIntrinsicWidth(
        measurable: IntrinsicMeasurable,
        height: Int
    ): Int {
        // Logic here
        // ...
    }

    // Other intrinsics related methods have a default value,
    // you can override only the methods that you need.
}