Как стать автором
Обновить

Jetpack Compose Layouts

Время на прочтение3 мин
Количество просмотров5.5K

Иногда для вёрстки сложных экранов не хватает Row, Column, Box и других встроенных контейнеров, тогда нам приходится писать свои собственные. В этой статье мы напишем Row, который переносит дочерние элементы на следующую строку в случае недостатка места.

Эта статья поделена на 2 части. Вторая часть.

Для создания собственных контейнеров в Compose используется элемент Layout:

@Composable
fun Layout(content: @Composable () -> Unit, modifier: Modifier = Modifier, measurePolicy: MeasurePolicy)
  • content - тело контейнера, содержащее все дочерние элементы.

  • measurePolicy - объект, отвечающий за расположение элементов внутри контейнера

Основной элемент будет выглядеть так:

@Composable
inline fun RowWithWrap(
    modifier: Modifier = Modifier,
    verticalSpacer: Dp = 0.dp,
    horizontalSpacer: Dp = 0.dp,
    content: @Composable () -> Unit
) {
    Box(modifier) {
        Layout(
            content = content,
            measurePolicy = rowWithWrapMesaurePolicy(verticalSpacer, horizontalSpacer)
        )
    }
}
  • verticalSpacer и horizontalSpacer - отступы между элементами по вертикали и горизонтали соответственно.

  • Box(modifier) - это небольшой костыль. Заставить Layout корректно обработать Modifier на уровне базовой статьи мы не можем. Это мы решим в продвинутой статье.

  • rowWithWrapMesaurePolicy создаёт политику расположения элементов исходя из отступов. Это так же понадобится в продвинутой статье

@Composable
fun rowWithWrapMesaurePolicy(
    verticalSpacer: Dp = 0.dp,
    horizontalSpacer: Dp = 0.dp
): MeasurePolicy = remember(verticalSpacer, horizontalSpacer) {
    MeasurePolicy { measurables: List<Measurable>, constraints: Constraints ->
        val positions = rowWithWrapRelativePositions(constraints, measurables, verticalSpacer, horizontalSpacer)
        val width = maxOf(positions.maxOf { it.maxXCoordinate }, constraints.minWidth)
        val height = minOf(maxOf(positions.maxOf { it.maxYCoordinate }, constraints.minHeight), constraints.maxHeight)
        layout(width, height) {
            for ((placeable, dx, dy) in positions) {
                placeable.placeRelative(dx, dy)
            }
        }
    }
}
  • Нам необходимо использование remember, чтобы не создавать лишних объектов каждую рекомпозицию.

  • MeasurePolicy - интерфейс с одним не реализованным, так что мы можем использовать лямбда выражение.

  • measurables - все дочерние элементы контейнера.

  • constraints - ограничения в размерах

  • Метод rowWithWrapRelativePositions вычисляет расположение всех элементов относительно верхнего левого угла контейнера. Возвращает наш дата-класс, но об этом далее.

  • layout(width, height) устанавливает размеры контейнера. Внутри него мы располагаем все элементы на вычисленных ранее местах.

private fun MeasureScope.rowWithWrapRelativePositions(
    constraints: Constraints,
    measurables: List<Measurable>,
    verticalSpacer: Dp,
    horizontalSpacer: Dp
): List<PlaceableRelativePosition> {
    val res = mutableListOf<PlaceableRelativePosition>()
    var x = 0
    var y = 0
    var maxHeight = -1

    for (measurable in measurables) {
        val placeable = measurable.measure(constraints)
        if (placeable.width + x > constraints.maxWidth) {
            y += maxHeight + verticalSpacer.roundToPx()
            x = 0
            maxHeight = -1
        }
        res += PlaceableRelativePosition(placeable, x, y)
        x += placeable.width + horizontalSpacer.roundToPx()
        maxHeight = maxOf(maxHeight, placeable.height)
    }

    return res
}

private data class PlaceableRelativePosition(val placable: Placeable, val dx: Int, val dy: Int)

private val PlaceableRelativePosition.maxXCoordinate: Int
    get() = dx + placable.width

private val PlaceableRelativePosition.maxYCoordinate: Int
    get() = dy + placable.height
  • Measurable::measure вычисляет размеры дочернего элемента исходя из внешних ограничений.

  • maxXCoordinate и maxYCoordinate - это самое правое и нижнее занятые места соответственно.

Результат:

Теги:
Хабы:
Всего голосов 3: ↑3 и ↓0+3
Комментарии5

Публикации

Истории

Работа

Ближайшие события

27 августа – 7 октября
Премия digital-кейсов «Проксима»
МоскваОнлайн
11 сентября
Митап по BigData от Честного ЗНАКа
Санкт-ПетербургОнлайн
14 сентября
Конференция Practical ML Conf
МоскваОнлайн
19 сентября
CDI Conf 2024
Москва
20 – 22 сентября
BCI Hack Moscow
Москва
24 сентября
Конференция Fin.Bot 2024
МоскваОнлайн
25 сентября
Конференция Yandex Scale 2024
МоскваОнлайн
28 – 29 сентября
Конференция E-CODE
МоскваОнлайн
28 сентября – 5 октября
О! Хакатон
Онлайн
30 сентября – 1 октября
Конференция фронтенд-разработчиков FrontendConf 2024
МоскваОнлайн