Jetpack Compose Layouts

от автора

Иногда для вёрстки сложных экранов не хватает 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 — это самое правое и нижнее занятые места соответственно.

Результат:


ссылка на оригинал статьи https://habr.com/ru/post/691954/


Комментарии

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *