Compose 공식 문서를 참고해 번역한 내용입니다.
Compose는 UI를 그리기 위해 3개의 단계를 거친다
- Composition
- Layout
- Drawing
Compose Phase의 특징
- 단방향 데이터 흐름 및 순차적 실행 (Composition -> Layout -> Drawing)
- 이전 단계의 결과를 재사용할 수 있고 조건에 따라 단계를 skip할 수 있다
- 최적화 (단계 내에서 상태(State)를 추적해 필요한 단계만 다시 실행)
- State 변경 -> Composition 다시
- Size 변경 -> Layout 다시
- 색상 변경 -> Drawing 다시
Composition
Composable 함수를 실행하고 UI 설명을 생성
UI 트리를 생성하거나 업데이트하는 단계
이전 Composition과 비교해 변경된 점을 찾는 단계
UI Tree는 Layout Node로 구성 (다음 phase에 필요한 정보가 포함된)
Layout
UI를 어디에 배치할 지
측정(Measurement)과 배치(Placement)의 2단계
부모 Node가 자식의 크기를 측정하고 자식의 위치를 결정하는 단계
이전 단계 (Composition)에서 생성된 UI 트리를 Input으로 사용해
UI 트리의 각 Node에 대해 측정과 배치 실행
각 Node는 한 번만 방문
측정하고 배치하는 순서
- 하위 요소 측정 (Measure children) - Node가 child를 가질 경우 측정
- 자체 크기 결정 (Decide own size) - 측정치를 기반으로 Node가 자체 크기를 결정
- 하위 요소 배치 (Place children) - 각 child node는 node의 자체 위치를 기준으로 배치됨
위 3개의 순서가 끝나면 Layout Node에는
- Assigned width & height
- 그려져야 할 x, y 좌표
코드를 통해 동작하는 방식을 알아보는 예시
@Composable
fun Test() {
Row {
Image(...)
Column {
Text(...)
Text(...)
}
}
}
- Row는 하위 요소인 Image, Column을 측정
- Image는 하위 요소가 없으므로 자체 크기를 결정하고 Row에 전달
- Column은 2개의 Text 하위 요소가 있어 이를 먼저 측정
- 첫 번째 Text는 하위 요소가 없으므로 자체 크기를 결정하고 Column에 전달
- 두 번째 Text도 하위 요소가 없으므로 자체 크기를 결정하고 Column에 전달
- Column은 2개의 Text 하위 측정값을 사용해 자체 크기를 결정 (최대 하위 요소 width와 height 합계 사용)
- Column은 하위 요소를 자신에 상대적으로 배치하여 서로 아래에 배치
- Row는 하위 측정값을 사용해 자체 크기를 결정 (최대 하위 요소 width와 height 합계 사용). 이후 child 배치
Drawing
UI를 렌더링하는 단계
실제 화면에 UI를 그리는 단계
Canvas에 그리는 단계
Tree가 위에서 아래로 다시 탐색되고 각 노드가 순차적으로 화면에 그려진다
위 코드에서 그려지는 단계
- Row는 배경색과 같은 Content를 그린다
- Image가 자체적으로 그린다
- Column이 자체적으로 그린다
- 두 Text는 각각 자체적으로 그린다
Compose는 각 Phase에서 State(상태)를 읽고 자동으로 추적한다
Compose는 상태 값이 변경될 때 Reader를 다시 실행할 수 있고 이로 상태를 추적할 수 있다
상태는 일반적으로 mutableStateOf 함수를 사용해 생성된다
value를 통해 접근하거나 delegation을 사용해 접근할 수 있다
@Composable
fun Test() {
val state1: MutableState<String> = remember { mutableStateOf("") }
Text(text = state1.value)
val state2 by remember { mutableStateOf("") }
Text(text = state2)
}
Compose는 각 Phase 내에서 상태를 추적한다.
Composition 단계에서 상태를 추적할 때
@Composable 함수나 람다 블록 내 상태 읽기는 Composition과 이후 단계에 잠재적으로 영향을 미친다
상태가 변경되면 Recomposer는 상태 값을 읽는 모든 Composable의 재실행을 예약한다
Input이 변경되지 않은 경우 Runtime에서 Composable을 skip할 수 있다
Composition 결과에 따라 Compose UI는 Layout, Drawing 단계를 실행한다
예시)
padding State는 Test Composable 함수가 실행될 때 (Composition 단계) 에서 읽고 padding이 변경되면 recomposition 발생
@Composable
fun Test() {
val padding by remember { mutableStateOf(8.dp) }
Text(
text = "Hello",
modifier = Modifier.padding(padding)
)
}
Layout 단계에서 상태를 추적할 때
측정(Measurement)과 배치(Placement)로 구성
Measurement 단계
Layout Composable에 전달된 측정 람다와 LayoutModifier 인터페이스의 MeasureScope.measure 함수 등을 실행
Placement 단계
layout 함수의 배치 블록과 Modifier.offset { } 의 람다 블록 등을 실행
위 단계에서의 상태 읽기는 Layout과 Drawing 단계에 영향을 줄 수 있다
상태 값이 변경되면 Compose UI는 Layout 단계를 예약
크기나 위치가 변경된 경우 Drawing 단계도 실행
측정과 배치는 독립된 restart scope를 가진다 (각 단계의 상태 읽기가 서로 영향을 미치지 않을 수 있다)
예시)
offsetX 상태는 offset이 계산되면 배치 단계에서 읽는다
offsetX 상태가 변경되면 layout 단계를 재시작한다
@Composable
fun Test() {
val offsetX by remember { mutableStateOf(8.dp) }
Text(
text = "Hello",
modifier = Modifier.offset {
IntOffset(offsetX.roundToPx(), 0)
}
)
}
Drawing 단계에서 상태를 추적할 때
상태 읽기는 Drawing 단계에 영향을 미친다
일반적으로 Canvas(), Modifier.drawBehind, Modifier.drawWithContent가 있다
상태값이 변경되면 Compose UI는 Drawing 단계만 실행
예시)
color 상태는 canvas가 렌더링되면 Drawing 단계에서 읽는다
color 상태가 변경되면 drawing 단계를 재시작
@Composable
fun Test(modifier: Modifier) {
val color by remember { mutableStateOf(Color.Red) }
Canvas(modifier = modifier) {
drawRect(color)
}
}
'Compose' 카테고리의 다른 글
[Compose] Composable 수명 주기 (0) | 2025.02.09 |
---|---|
[Compose] Compose 이해하기 (0) | 2025.02.08 |