Compose CustomLayout으로 멋있는게 만들고싶어서 시간 날때마다 조금씩 구현해보려고 한다.
간단한 구현을 점점 더 디벨롭해나가는 것이 목표다!
구현한 결과는 아래와 같다.
먼저, 개념부터 정리해보자
Compose에서 UI는 호출될 때 UI 요소를 내보내는 컴포저블 함수로 표현된다. 각 UI 요소에는 하나의 상위 요소와 여러 개의 하위 요소가 있을 수 있고, 각 요소는 (x, y) 위치로 지정된 상위 요소 내에 배치되며 width, height로 크기가 지정된다.
상위 요소는 하위 요소의 제약 조건을 정의하고, 이러한 제약 조건 내에서 요소의 크기를 정의해야 한다.
UI 트리에 각 노드를 배치하는 작업은
1. 모든 하위 요소 측정
2. 자체 크기 결정
3. 하위 요소 배치
순서로 진행된다.
Custom Layout를 구현하기 위해 Layout
컴포저블을 사용한다. 이 컴포저블을 사용하면 하위 요소를 수동으로 측정하고 배치할 수 있다.
많이 사용하는 Column
및 Row
와 같은 모든 상위 수준 레이아웃은 Layout
컴포저블을 사용하여 빌드된다.
1. 모든 하위 요소 측정
val circleRadius: Int = if (constraints.hasFixedWidth) constraints.maxWidth / 3 else 500
val circularPositionData = mutableListOf<CircularPosition>()
val itemConstraints = Constraints(maxWidth = circleRadius / 2, maxHeight = circleRadius / 2)
val placeables = measurables.mapIndexed { index, measurable ->
val radians = angle[index] * (PI / 180)
val placeable = measurable.measure(itemConstraints)
val x = (cos(radians) * circleRadius).toInt() + circleRadius
val y = (sin(radians) * circleRadius).toInt() + circleRadius
circularPositionData.add(
CircularPosition(
x = x,
rightTopX = x + placeable.width,
y = y,
leftBottomY = y + placeable.height
)
)
placeable
}
먼저 하위 요소를 측정한다.
이때 원형으로 배치할 수 있도록 x와 y의 값도 계산해두는데
val cnt = measurables.size
val r = 360 / cnt.toDouble()
val angle = mutableListOf<Double>()
repeat(cnt) {
angle.add((it + 1) * r)
}
360도를 하위 컴포저블의 개수로 나눈 값을
각 컴포저블의 사이의 각도로 하여 각 컴포저블이 위치할 각도를 계산해둔다. (ex. 4개의 컴포저블 -> 90도, 180도, 270도, 360도)
그 후 각도를 라디안으로 변환 후 삼각함수를 사용해 각각의 x, y 좌표를 구하면
원형으로 배치될수 있는 x, y 좌표를 구할 수 있다.
2. 자체 크기 결정, 하위 요소 배치
크기의 경우 각 컴포저블의 rightTop의 x좌표 중 가장 큰것을 width로, leftBottom의 y좌표 중 가장 큰것을 height로 지정했다. (1번의 측정 과정에서 함께 계산)
val w = circularPositionData.maxOf { it.rightTopX }
val h = circularPositionData.maxOf { it.leftBottomY }
layout(w, h) {
placeables.forEachIndexed { index, placeable ->
placeable.placeRelative(
x = circularPositionData[index].x,
y = circularPositionData[index].y
)
}
}
그리고 계산해둔 x, y 값으로 배치해주면 된다.
프리뷰는 위와 같다.
아직 문제점이 많은 코드지만 차차 수정하면서 기능을 추가해보고 싶다.
기존의 뷰 시스템에서 ViewGroup
을 확장하면서 커스텀뷰를 만들었던 과정과 비교하면 간단해진거같다.
preview 기능이 있어 더 편하게 개발할 수 있었다!