View 시스템에서 다중 측정이 필요한 이유
안드로이드 View 시스템에서는 부모 ViewGroup이 자식 뷰를 배치하기 위해 크기를 먼저 측정해야 합니다. 그런데 이 과정은 생각보다 단순하지 않습니다. 부모는 자식의 크기를 알아야 하고, 자식은 부모의 크기를 알아야 하는 상황이 발생하기 때문입니다.
ViewGroup 입장에서 생기는 문제
ViewGroup(예: LinearLayout, RelativeLayout)은 자신의 자식 뷰들을 measure() 메서드를 통해 먼저 측정합니다. 자식의 크기를 알아야 자신이 어떤 크기로 그릴지 결정할 수 있기 때문이죠.
하지만 자식이 이렇게 말할 수 있습니다:
“저는 부모가 어떤 크기를 주느냐에 따라 제 크기가 달라져요.”
이런 경우, 부모는 자식에게 다양한 제약 조건을 주며 여러 번 측정해보는 작업을 하게 됩니다. 즉, 자식의 크기를 정확히 결정하기 위해, 부모가 실험적으로 여러 번 측정하는 상황이 생기는 것입니다.
예시를 한번 들어 보겠습니다.
<LinearLayout
android:layout_width="wrap_content"
android:layout_height="wrap_content">
<TextView
android:layout_width="match_parent"
android:layout_height="50dp"
android:text="안녕하세요, 이것은 아주 긴 텍스트입니다" />
</LinearLayout>
이 상황을 보면:
- 부모인 LinearLayout은 wrap_content로 설정되어 있어, 자식의 크기에 따라 자신의 폭을 정해야 합니다.
- 하지만 자식인 TextView는 match_parent이기 때문에, 부모의 폭을 기준으로 자신의 폭을 정해야 합니다.
여기서 순환 의존 문제가 발생합니다:
- 부모는 자식의 크기를 알아야 자신을 결정할 수 있고,
- 자식은 부모의 크기를 알아야 자신의 크기를 결정할 수 있습니다.
따라서, Android View 시스템에서는 이 순환 문제를 다음과 같이 해결합니다:
- 자식을 임의의 조건으로 먼저 측정합니다 (예: 부모가 아직 폭을 모른다면, UNSPECIFIED로).
- 자식 측정 결과를 바탕으로 부모 자신의 크기를 결정합니다.
- 부모가 정해진 크기를 가지고 자식을 다시 측정합니다 (EXACTLY로).
즉, "자식 먼저 측정 → 부모 크기 결정 → 자식 다시 측정"이라는 순서가 필요합니다. 이것이 바로 다중 측정입니다.
이러한 방식은 매우 유연하고 다양한 UI 구현을 가능하게 하지만, 측정이 여러 번 반복되기 때문에 성능 비용이 커질 수 있습니다. 특히 복잡한 View 계층이나 중첩된 wrap_content, match_parent 조합은 렌더링 성능에 악영향을 줄 수 있으니 주의가 필요합니다.
Compose의 철학: "측정은 딱 한 번, 예측 가능하게"
View 시스템에서는 부모와 자식 간의 순환 의존을 해결하기 위해 다중 측정이라는 복잡한 과정이 필요했지만, Jetpack Compose는 이를 근본적으로 다르게 접근합니다.
Compose는 "측정은 단 한 번만 허용한다"는 철학을 따릅니다. 이는 선언형 UI 시스템의 핵심 원칙 중 하나로, 측정과 배치의 흐름을 단방향으로 명확히 유지함으로써 예측 가능한 UI 렌더링을 가능하게 합니다.
Compose의 측정 흐름
Compose의 측정 과정은 다음과 같은 단일 방향으로만 흐릅니다:
부모 → 자식에게 Constraints 제공 → 자식이 measure() 수행 → 자식이 place()됨
즉, 부모는 자식에게 명확한 제약 조건을 내려주고, 자식은 그 제약 안에서 최선을 다해 자신의 크기를 결정한 뒤 결과를 부모에게 응답합니다. 부모가 다시 자식을 측정하거나, 자식이 부모의 상태를 재참조하는 일은 없습니다.
아래 코드는 위 View System의 코드를 Compose로 변환한 코드입니다.
@Composable
fun ComposeLayoutExample() {
// 부모 Box는 wrap_content (내용만큼 크기)
Box(
modifier = Modifier
.background(Color.LightGray)
.border(1.dp, Color.Gray)
) {
// 자식 Text는 match_parent → 부모 너비를 채움
Text(
text = "안녕하세요, 이것은 아주 긴 텍스트입니다",
modifier = Modifier
.fillMaxWidth() // 부모 너비를 기준으로 동작
.height(50.dp)
.background(Color.Cyan)
)
}
}
Compose에서는 Box가 자식인 Text에게 최대 너비 제약 조건(maxWidth) 을 부여합니다. Text는 그 조건 내에서 fillMaxWidth()에 따라 너비를 결정하고, 부모는 자식의 측정 결과만을 참고하여 한 번에 자신의 크기를 정합니다.
다시 측정하거나 결과를 되돌아보는 일이 없기 때문에, 측정과 배치가 매우 간단하고 예측 가능합니다.
Compose는 자식이 부모의 크기를 참조하지 못하게 설계되어 있다!
Compose는 측정의 주도권이 항상 부모에게만 있는 구조입니다. 자식은 자신이 속한 레이아웃에서 받은 Constraints만 보고 크기를 계산하며, 부모가 어떤 크기로 측정될지는 참조할 수 없습니다.
이러한 단방향 측정 흐름은 다음과 같은 장점을 가져옵니다:
- 측정 로직 단순화 → 재측정, 실험적 측정이 필요 없음
- 렌더링 성능 향상 → 측정 횟수 최소화
- 예측 가능한 레이아웃 → 디버깅이 쉬움
- 선언적 패러다임과의 자연스러운 일치
마무리
View 시스템은 유연성을 바탕으로 다양한 레이아웃 시나리오를 구현할 수 있지만, 그만큼 복잡한 측정 로직과 성능 부담이 따라왔습니다. 반면 Compose는 측정 과정을 단방향으로 제한함으로써, 예측 가능하고 효율적인 UI 구성을 가능하게 합니다.
측정이 단 한 번만 수행된다는 Compose의 원칙은 선언적 UI 철학을 가장 잘 보여주는 예 중 하나이며, 안정적이고 유지보수하기 쉬운 UI를 만드는 데 큰 도움이 됩니다.
'Android > Compose' 카테고리의 다른 글
[Android] Compose 레이아웃에서 Intrinsic 측정 (0) | 2025.06.02 |
---|---|
[Android] Compose의 정렬선(Alignment lines) (1) | 2025.06.02 |
[Android] Compose의 커스텀 레이아웃 (0) | 2025.06.01 |
[Android] Compose의 Flow Layout (0) | 2025.05.31 |
[Android] Compose에서의 Pager (0) | 2025.05.31 |