컴포즈의 3단계
컴포즈에는 세개의 주요 단계가 있다.
- 컴포지션: 표시할 UI로, 컴포즈는 구성 가능한 함수를 실행하고 UI 설명을 만든다.
- 레이아웃: UI를 배치할 위치이다. 이 단계는 측정과 배치라는 두 단계로 구성된다. 레이아웃 요소는 레이아웃 트리에 있는 각 노드의 레이아웃 요소 및 모든 하위 요소를 2D 좌표로 측정하고 배치한다
- 그리기: UI를 렌더링하는 방법이다. UI 요소는 일반적으로 기기 화면인 캔버스에 그려진다.
따라서 데이터가 컴포지션 -> 레이아웃 -> 그리기의 순서로 한 방향으로 이동하여 단방향 데이터 흐름을 가진다고 한다. 개념적으로 이러한 각 단계는 모든 프레임에서 발생하지만 성능을 최적화하기 위해 Compose는 모든 단계에서 동일한 입력으로 동일한 결과를 계산하는 반복 작업을 피해 최적화를 한다.
그럼 각 단계에 대해 더 자세히 알아보자.
1️⃣ 컴포지션
- 컴포즈 런타임은 컴포저블을 실행하고, UI를 나타내는 트리 구조를 출력한다.
- 이 UI 트리는 다음 단계에 필요한 모든 정보가 포함된 레이아웃 노드로 구성된다.
2️⃣ 레이아웃
- Compose는 컴포지션 단계에서 생성된 UI 트리를 입력으로 사용한다.
- 레이아웃 노드 모음에는 2D 공간에서 각 노드의 크기와 위치를 결정하는 데 필요한 모든 정보가 포함되어 있다
- 측정 - 배치 두가지의 단계로 이루어진다.
- 측정(measure) 단계
- 각 UI 요소의 크기를 결정한다.
- Layout 컴포저블에 전달된 측정 람다(measure)와 LayoutModifier의 MeasureScope.measure() 메서드를 사용한다.
- 예를 들어, 부모 레이아웃은 자식 요소들에게 "너는 최대 100dp까지 커질 수 있어"라고 제한을 주고, 자식은 그 제한 내에서 자신의 크기를 결정한다.
- 이 단계에서 상태 값을 읽으면 크기에 영향을 미치기 때문에 레이아웃이 다시 측정된다.
- 배치(place) 단계
- 측정된 크기를 바탕으로 UI 요소를 실제 화면에 배치한다.
- layout 함수의 place() 블록과 Modifier.offset { ... } 등의 람다 블록을 사용한다.
- 예를 들어, Modifier.offset { IntOffset(10, 20) }를 사용하면 해당 컴포저블이 x축으로 10dp, y축으로 20dp만큼 이동하여 배치된다.
- 이때 상태 값을 읽으면 위치가 달라지기 때문에 다시 배치된다.
- 측정(measure) 단계
3️⃣ 그리기
- 그리기 단계에서는 트리가 위에서 아래로 다시 탐색되고 각 노드가 차례로 화면에 그려진다.
컴포즈의 상태 읽기와 상태 추적
Jetpack Compose에서는 상태 값을 읽으면 Compose가 값을 읽는 동안 실행된 작업을 자동으로 추적한다. 이를 통해 상태 값이 변경되면 해당 상태 값을 읽은 부분만 리컴포지션하여 UI를 효율적으로 업데이트한다.
상태 생성 방법
Compose에서 상태는 보통 mutableStateOf()를 사용해 생성하며, 두 가지 방법으로 상태 값을 읽을 수 있다
value 속성에 직접 접근
val paddingState: MutableState<Dp> = remember { mutableStateOf(8.dp) }
Text(
text = "Hello",
modifier = Modifier.padding(paddingState.value)
)
- paddingState.value로 상태 값을 읽는다.
- MutableState의 value 속성을 통해 접근하기 때문에 value가 변경되면 해당 값을 사용한 컴포저블이 다시 컴포지션된다.
속성 위임 사용
var padding: Dp by remember { mutableStateOf(8.dp) }
Text(
text = "Hello",
modifier = Modifier.padding(padding)
)
- by 키워드를 사용한 속성 위임으로 상태를 읽는다.
- 내부적으로는 getter와 setter를 통해 value에 접근하며, padding 값을 사용하면 paddingState.value와 동일하게 동작한다.
상태 추적과 재구성
Compose는 상태 읽기를 추적하여 해당 상태가 변경되면 해당 값을 읽은 컴포저블만 다시 시작 범위(restart scope) 내에서 다시 컴포지션한다. 예를 들어, padding 상태가 바뀌면 Text 컴포저블만 다시 컴포지션되며, 다른 컴포저블은 영향을 받지 않는다. 이전에도 언급했지만, 이 Compose의 이 자동 상태 추적 메커니즘 덕분에 UI 업데이트가 효율적으로 이루어진다.
컴포지션의 생명주기
Compose는 초기 컴포지션 시 처음으로 컴포저블을 실행할 때 컴포지션에서 UI를 기술하기 위해 호출하는 컴포저블을 추적한다. 그런 다음 앱 상태가 변경되면 Jetpack Compose는 리컴포지션을 예약한다.
즉, 컴포지션은 초기 컴포지션을 통해 생성되고, 리컴포지션을 통해서만 업데이트가 가능하며 따라서 간단히 3단계로 정의된다.
- 컴포지션 시작
- 0회 이상의 리컴포지션
- 컴포지션 종료
컴포저블의 Call Site
컴포저블이 여러번 호출되면 컴포지션에 여러 인스턴스가 배치된다. 이 각 호출에는 자체 수명주기가 있다. 이때, 같은 컴포저블을 여러번 호출한 경우 각 인스턴스가 어떻게 식별될까 ?
답은 Call Site, 즉 컴포저블이 호출되는 소스코드 위치이다.
리컴포지션 시 컴포저블이 이전 호출과 다른 컴포저블을 호출하는 경우, Compose는 호출되거나 호출되지 않은 컴포저블을 식별하며, 두 컴포지션 모두에서 호출된 컴포저블의 경우 입력이 변경되지 않은 경우 재구성하지 않는다.
call site 이외의 추가 정보
@Composable
fun MoviesScreen(movies: List<Movie>) {
Column {
for (movie in movies) {
// MovieOverview composables는 for문에서 인덱스 순서대로 Composition에 추가됨
MovieOverview(movie)
}
}
}
위의 코드와 같이 콜 사이트, 즉 호출 위치마저 같은 컴포저블은 어떻게할까 ? 이 경우 호출 위치에 실행 순서를 추가로 사용해 구분한다. for문 내의 순서에 따라 컴포지션에 추가되어 구분된다.
근데 이 경우, 리스트에 아이템을 중간 삽입 / 삭제하거나 순서를 변경하고 이때, MovieOverview 컴포저블 함수가 이미지로딩과 같은 사이드이펙트를 가지는 함수라면 어떻게할까 ?
이 경우 리컴포지션이 발생할텐데 그러면 사이드이펙트가 취소되고 다시 시작되므로 전부 재구성이 되어 문제가 된다. 이를 위한 것이 'key' 이다.
key 컴포저블 사용
@Composable
fun MoviesScreenWithKey(movies: List<Movie>) {
Column {
for (movie in movies) {
key(movie.id) { // 영화의 고유 ID 사용
MovieOverview(movie)
}
}
}
}
위와 같이 영화가 가진 고유한 id를 이용해 MovieOverview가 영화의 고유성에 따라 식별되도록 만들 수 있다. 이렇게 하면 리스트의 순서가 바뀌어도 개별 Composable 인스턴스를 유지할 수 있어 id가 같은 영화는 재구성되지 않고 재사용된다.
보면 떠오르겠지만, lazycolumn에서의 key 설정이 이 경우이다.
참고자료
Jetpack Compose 단계 | Android Developers
이 페이지는 Cloud Translation API를 통해 번역되었습니다. Jetpack Compose 단계 컬렉션을 사용해 정리하기 내 환경설정을 기준으로 콘텐츠를 저장하고 분류하세요. 대부분의 다른 UI 도구 키트와 마찬가
developer.android.com
컴포저블 수명 주기 | Jetpack Compose | Android Developers
이 페이지는 Cloud Translation API를 통해 번역되었습니다. 컴포저블 수명 주기 컬렉션을 사용해 정리하기 내 환경설정을 기준으로 콘텐츠를 저장하고 분류하세요. 이 페이지에서는 컴포저블의 수명
developer.android.com
'안드로이드 > 컴포즈' 카테고리의 다른 글
[Android][Compose] 컴포즈의 stateful / stateless, remember, mutableStateOf, remeberSaveable, 상태 호이스팅 (0) | 2025.02.28 |
---|---|
[Android][Compose] 선언형 UI 컴포즈, 컴포지션 (0) | 2025.02.26 |
[Android][Compose] 네비게이션 구현하기 (0) | 2024.07.18 |
[Android][Compose] State와 상태 호이스팅 (0) | 2024.07.05 |
[Android][Compose] Composable과 Recomposition (0) | 2024.07.05 |