Composable 및 Modifier를 지원하는 구성된 레이아웃 모델을 설명하고, 내부에서 작동하는 방식과 기능에 대한 내용이다.
Compose 레이아웃 시스템의 목표는
커스텀 레이아웃을 만들기 쉽고
레이아웃 시스템이 강력하여 앱에 필요한 것을 만들 수 있으며
고성능
Layout Model
Jetpack Compose는 상태를 UI로 변환한다
변환은 Composition, Layout, Drawing 3단계의 프로세스를 거친다
Composition은 UI를 방출(emit)하여 UI 트리를 만들 수 있는 Composable 함수를 실행한다
SearchResult Composable을 실행하면 그림과 같은 트리가 생성된다
상태에 따라서 다른 트리를 생성할 수 있다
Layout 단계서는 각 트리를 다니며 UI 항목을 측정하여 배치를 담당한다
각 노드의 Width, Height, x, y 좌표를 결정한다
Drawing 단계에서는 UI 트리를 다니면서 모든 요소를 렌더링 한다.
Layout 단계에는 측정(Measure)와 배치(Place) 단계가 있다
측정 결과는 기존 View 레이아웃 시스템의 measure 값과 거의 동일하다. 그러나 Compose에서는 이러한 단계가 서로 얽혀 있다
UI 트리에 각 노드 레이아웃은 3단계 프로세스를 진행한다
각 노드는 하위 노드를 측정
자체 크기 결정
하위 노드를 배치
루트 레이아웃에 측정(measure)을 요청한다
첫 번째 자식인 이미지에 측정을 요청한다
이미지는 자식이 없는 Leaf 노드라서, 자체적으로 측정하고 크기를 알려준다
자식 노드를 배치 방법에 대한 정보도 반환한다. 이 경우는 비어있다.
모든 레이아웃은 크기를 설정함과 동시에 배치 지침을 알려준다
Column의 노드를 측정한다.
첫 번째 Text의 크기와 배치 지침을 측정하고 알려준다
두 번째 Text도 마찬가지이다.
Column의 자식들이 측정되었으므로 자체 크기와 배치를 결정한다
루트 Row의 자식 노드들의 크기가 측정되었으므로 자신의 크기와 배치를 결정한다
모든 노드의 크기가 측정되면 트리가 다시 탐색되고 모든 배치(place) 지침이 배치 단계에서 실행된다.
기존 UI 트리에서 각 Composable은 자체적으로 하위 수준 Composable에서 구성된다.
Text는 여러 하위 수준의 블록으로 구성된다
화면에 요소를 배치하는 모든 Composable에 하나 이상의 Layout Composable이 존재한다.
Layout Composable의 기능은 Compose UI의 기본 구성 블록이다. 레이아웃 노드를 방출(emit) 한다
Compose에서 UI 트리 및 구성은 Layout Node의 트리이다
Layout composable
content : 모든 하위 Composable에 대한 슬롯이며 레이아웃을 위해 cotnent에는 자식 Layout이 포함된다
Modifier : 레이아웃에 Modifier를 적용하기 위한 파라미터를 받는다
measurePolicy : 레이아웃이 Node를 측정하고 배치하는 방법인 측정 정책이다
@ComposablefunMyCustomLayout(modifier:Modifier=Modifier,content:@Composable()->Unit){Layout(modifier=modifier,content=content// 전달된 자식 노드){measurables:List<Measurable>,// 측정 가능한 목록constraints:Constraints->// TODO measure and place imtes}}
MyCustomLayout에서 Layout 함수를 호출하고 측정 기능 구현을 후행 람다로 제공하는 모습이다.
Constraints 객체를 수신하여 레이아웃의 크기를 결정한다
measurable은 항목을 측정하기 위한 기능을 노출한다
Constraints는 레이아웃될 수 있는 최소 및 최대 너비와 높이를 모델링하는 간단한 클래스이다
// 레이아웃이 제한이 없으며 원하는 만큼 커질 수 있음을 표현valbigAsYouLike=Constraints(minWidth=0,maxWidth=Constraints.Infinity,minHeight=0,maxHeight=Constraints.Infinity)// 정확한 크기르 표현valexact=Constraints(minWidth=50,maxWidth=50,minHeight=50,maxHeight=50)
먼저 모든 자식들을 측정(measure)한다. measurable은 크기 제약을 허용하는 측정 방법을 노출한다.
커스텀 측정 로직을 적용하지 않는 간단한 경우에는 목록을 매핑하여 각각을 측정할 수 있다
최종적으로 placeables 목록이 생성된다. 여기에는 측정된 자식과 크기 정보가 있다.
placeables 목록을 사용하여 레이아웃의 크기를 계산할 수 있다.
layout 메서드를 호출하여 레이아웃이 원하는 크기를 전달할 수 있다.
후행 람다로 구현된 배치 블록이 사용되며, 각 항목을 원하는 위치에 배치할 수 있다
RTL을 처리를 위한 placeRelative 메서드도 존재한다
API 디자인적으로 측정되지 않은 요소를 배치하려는 시도를 방지한다.
place 메서드는 측정(measure) 메서드에서 반환된 placeable 항목만 사용할 수 있다
Layout Examples
Column
항목들을 세로로 배치하기 위한 Column 컴포넌트의 방식을 이해하기 위해 먼저 자체 Column을 구현해 본다
MyColumn의 높이는 모든 항목의 측정된 높이의 합이 된다. 너비는 가장 넓은 항목이 너비가 된다
크기를 보고한 다음, y 위치를 추적(tracking)하고 배치된 각 항목의 높이만큼 증가시켜 각 항목을 배치한다.
실제 Column은 가중치, 정렬 등의 기능을 추가로 지원한다
VerticalGrid
2개의 Column을 가지는 VerticalGrid Composable 함수 예시
각 열의 너비는 레이아웃의 최대 너비를 사용하여 계산한다
계산된 약 Column의 너비 값을 사용하여 항목에 대한 새 Constraints 객체를 구성한다
앞서 작성한 제약 조건으로 각 항목을 측정(measure)한 다음 Grid에 배치한다
자식 노드를 측정하기 위해 다양한 제약 조건을 생성하는 기능이 이 모델의 핵심이다
부모와 자식 사이에는 협상이 없으며, 부모는 제약 조건으로 표현되는 허용 가능한 크기 범위를 전달한다
자식 노드가 전달된 범위 내에서 크기를 선택하면, 부모는 그것을 받아들이고 처리해야 한다
단일 패스로 전체 UI 트리를 측정할 수 있고, 여러 번 측정을 금지할 수 있다
실제로 항목을 2번 측정하면 Compose에서 예외가 발생한다
앱 디자인에서 요구하는 특정 레이아웃을 만들 때 Layout Composable이 유용하다
Jetsnack 샘플의 Bottom Navigation Design이 그 사례이다.
선택된 항목만 레이블이 표시. 그 외에는 아이콘만 표시
항목 선택 시 애니메이션을 적용
요구 사항에 맞추기 위해서 아이콘과 텍스트에 대한 두개의 슬롯을 노출한다
호출자가 제공해야 하는 애니메이션 진행률 값이 있다
0 : 선택 해제
1 : 항목 선택
커스텀 레이아웃에서 Box에 아이콘과 텍스트를 래핑한다. Layer ID Modifier를 각가 적용하기 위한 것이다.
Measure 블록에서 measurables에서 필요한 배치 항목을 검색하여 가져온다
애니메이션 진행률에 따라 텍스트와 아이콘을 배치하는 계산을 한다
Compose의 Layout 성능이 매우 좋아서 측정 또는 배치를 애니메이션으로 만들거나 제스처로 구동할 수 있기 때문에 가능하다.
기존 View 시스템에서는 레이아웃 애니메이션이 성능상의 문제로 권장되지 않았다.
커스텀 Compose가 유용한 경우는?
표준 레이아웃으로는 어려운 디자인을 만들어야 하는 경우. 대부분 Row/Columns으로 가능하다
측정 또는 배치 로직을 정밀하게 제어해야 하는 경우
레이아웃을 애니메이션 시키거나 동작시키고 싶은 경우
배치(Place) 애니메이션 API는 현재 개발 중
성능을 제어할 경우
Modifier
Modifier는 레이아웃, 크기, 위치 구성에 중요한 역할을 한다. 자신이 적용되는 요소를 장식하고, 레이아웃 자체의 측정 및 배치 전에 측정 및 배치에 참여한다
Drawing, 포인터 입력 Modifier, 포커스 등 다양한 동작에 영향을 주는 Modifier가 있다.
MeasureScope.measure는 측정 가능한 단일 항목에만 작동한다는 점을 제외하고 Layout Composable과 동일한 측정 방법을 제공한다.
제약 조건을 변경하거나 커스텀한 배치 로직을 구현할 수 있다.
단일 항목에 대해서만 작업을 수행하려는 경우에만 Modifier를 대신 사용할 수 있다
Modifier.padding은 Modifier 체인을 기반으로 만들어지며, 원하는 패딩 값을 캡처하는 PaddingModifier 객체를 만든다
PaddingModifier는 LayoutModifier의 한 종류이다
패딩 크기만큼 외부 제약 조건을 축소하여 측정을 변경한 다음 내용을 측정한다. 그다음 원하는 패딩만큼 오프셋 된 콘텐츠를 배치한다.
PaddingModifier처럼 커스텀 Layout Modifie를 작성하는 것 이외에 Modifier.layout을 사용도 가능하다
Modifier 체인에서 직접 Composable 구성 요소에 커스텀 측정 또는 배치 로직을 추가할 수 있다
Box가 최대 200x300 픽셀의 컨테이너 안에 배치되는 경우
체인의 첫 번째 Modifier에 크기의 제약 조건이 전달된다
fillMaxSize : 새로운 제약 조건을 생성 후, 최소/최대 너비와 높이를 최대 너비와 높이와 동일하게 설정하여 최대로 채운다