본문 바로가기
Android/Compose

[Android] Compose 레이아웃에서 Intrinsic 측정

by 태크민 2025. 6. 2.

Compose 레이아웃에서 Intrinsic 측정

Compose에서는 자식 컴포저블을 한 번만 측정해야 한다는 규칙이 있습니다. 자식을 두 번 측정하려고 하면 런타임 예외가 발생합니다.
하지만 경우에 따라 자식을 실제로 측정하기 전에 그 크기 정보를 알고 싶을 때가 있습니다.

이럴 때 사용하는 것이 Intrinsics입니다.
Intrinsics를 사용하면 자식을 아직 측정하지 않은 상태에서 필요한 크기 정보를 미리 쿼리할 수 있습니다.

 

예를 들어, 특정 컴포저블에 대해 다음과 같은 정보를 요청할 수 있습니다:

  • Modifier.width(IntrinsicSize.Min)
    → 콘텐츠를 제대로 표시하기 위해 필요한 최소 너비는 얼마인가?
  • Modifier.width(IntrinsicSize.Max)
    → 콘텐츠를 제대로 표시할 수 있는 최대 너비는 얼마인가?
  • Modifier.height(IntrinsicSize.Min)
    → 콘텐츠를 제대로 표시하기 위한 최소 높이는 얼마인가?
  • Modifier.height(IntrinsicSize.Max)
    → 콘텐츠를 제대로 표시할 수 있는 최대 높이는 얼마인가?

예를 들어, Text 컴포저블에 대해 너비 제한이 무한한 상황에서 minIntrinsicHeight를 요청하면,
→ 텍스트가 한 줄로 표시될 때의 높이를 반환합니다.

참고: Intrinsics 측정을 요청하는 것은 자식을 두 번 측정하는 것이 아닙니다.
자식은 실제로 측정되기 전에 Intrinsic 값만 쿼리되고,
그 정보를 바탕으로 부모가 자식을 측정할 때 사용할 제약 조건(constraints) 을 계산하게 됩니다.
 
 

Intrinsics 사용 예시

화면에 두 개의 텍스트를 수직 구분선으로 나누어 표시하는 컴포저블을 만들고 싶다고 가정해봅시다:

 

두 개의 텍스트 요소가 나란히 배치되고, 그 사이에 수직 구분선이 있는 형태입니다.

이걸 어떻게 구현할 수 있을까요?

Row 안에 두 개의 Text를 넣고, 가운데에 Divider를 배치하면 됩니다.Text는 가능한 한 넓게 확장되고, Divider는 가장 키가 큰 Text의 높이에 맞게 높이를 지정하며, 너비는 얇게(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가 화면 전체 높이만큼 확장되어 의도한 결과가 아님을 알 수 있습니다:

두 개의 텍스트가 나란히 있고, 그 사이에 Divider가 있지만, Divider가 텍스트 아래까지 뻗어 있는 상태입니다.

 

이 현상이 발생하는 이유는 Row가 각 자식들을 개별적으로 측정하기 때문이며, Text의 높이는 Divider를 제약하는 데 사용될 수 없습니다.
우리가 원하는 것은 Divider가 텍스트의 높이에 맞춰 공간을 채우는 것입니다.

이를 위해서는 Modifier.height(IntrinsicSize.Min)을 사용할 수 있습니다.

height(IntrinsicSize.Min)은 자식들이 자신의 최소 intrinsic 높이만큼의 크기를 가지도록 강제합니다.
이 속성은 재귀적으로 동작하기 때문에, Row와 그 자식들의 minIntrinsicHeight를 쿼리하게 됩니다.

 

이제 해당 modifier를 코드에 적용하면, 우리가 기대한 대로 동작하게 됩니다.

@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")
        }
    }
}

 

미리 보기 결과:

두 개의 텍스트 요소가 나란히 배치되고, 그 사이에 수직 Divider가 있습니다.

Row 컴포저블의 minIntrinsicHeight는 자식들 중 가장 큰 minIntrinsicHeight 값이 됩니다.
Divider 요소의 minIntrinsicHeight는 제약이 주어지지 않으면 공간을 차지하지 않기 때문에 0입니다.
반면 Text의 minIntrinsicHeight는 주어진 너비에 따라 텍스트가 차지하는 높이가 됩니다.

따라서 Row 요소의 높이 제약은 텍스트들의 minIntrinsicHeight 중 최대값이 되며, 그 결과 Divider는 Row로부터 전달받은 해당 높이 제약에 맞춰 높이를 확장하게 됩니다.

 


사용자 정의 레이아웃에서의 Intrinsics

사용자 정의 Layout이나 layout modifier를 만들 때, intrinsic 측정값은 기본적으로 시스템이 추정하여 자동 계산합니다.
따라서 모든 레이아웃에 대해 그 계산이 정확하지 않을 수 있습니다. 이러한 기본 동작을 직접 제어하고 싶을 경우, 다음 API들을 통해 오버라이드할 수 있습니다.

 

사용자 정의 Layout을 만들 때 MeasurePolicy 인터페이스의 minIntrinsicWidth, minIntrinsicHeight, maxIntrinsicWidth, maxIntrinsicHeight를 오버라이드하면 원하는 방식으로 intrinsic 측정값을 지정할 수 있습니다.

@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.
        }
    )
}

 

사용자 정의 레이아웃 모디파이어를 만들 때는, 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.
}

 


참고자료

https://developer.android.com/develop/ui/compose/layouts/intrinsic-measurements?_gl=1*9hry8u*_up*MQ..*_ga*OTkzNjI4NTA4LjE3NDg4MjkyNDA.*_ga_6HH9YJMN9M*czE3NDg4MjkyNDAkbzEkZzAkdDE3NDg4MzAxODIkajYwJGwwJGgxMjgxMjk2MDkz

 

Compose 레이아웃의 내장 기능 측정  |  Jetpack Compose  |  Android Developers

이 페이지는 Cloud Translation API를 통해 번역되었습니다. Compose 레이아웃의 내장 기능 측정 컬렉션을 사용해 정리하기 내 환경설정을 기준으로 콘텐츠를 저장하고 분류하세요. Compose 규칙 중 하나

developer.android.com