Compose CustomLayout 만들기(원형 레이아웃) - 1

햄햄·2022년 7월 14일
0

Compose

목록 보기
7/7
post-thumbnail

Compose CustomLayout으로 멋있는게 만들고싶어서 시간 날때마다 조금씩 구현해보려고 한다.
간단한 구현을 점점 더 디벨롭해나가는 것이 목표다!

구현한 결과는 아래와 같다.

Custom Layout

먼저, 개념부터 정리해보자
Compose에서 UI는 호출될 때 UI 요소를 내보내는 컴포저블 함수로 표현된다. 각 UI 요소에는 하나의 상위 요소와 여러 개의 하위 요소가 있을 수 있고, 각 요소는 (x, y) 위치로 지정된 상위 요소 내에 배치되며 width, height로 크기가 지정된다.
상위 요소는 하위 요소의 제약 조건을 정의하고, 이러한 제약 조건 내에서 요소의 크기를 정의해야 한다.
UI 트리에 각 노드를 배치하는 작업은
1. 모든 하위 요소 측정
2. 자체 크기 결정
3. 하위 요소 배치
순서로 진행된다.

Custom Layout를 구현하기 위해 Layout 컴포저블을 사용한다. 이 컴포저블을 사용하면 하위 요소를 수동으로 측정하고 배치할 수 있다.
많이 사용하는 ColumnRow와 같은 모든 상위 수준 레이아웃은 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 기능이 있어 더 편하게 개발할 수 있었다!

0개의 댓글