Введение

В этой статье будет пример контейнера с добавлением новых элементов во время композиции. В частности разделителей.

Эта статья поделена на 2 части. Перед чтением этой статьи советую прочитать первую часть.

Добавление разделителей

У нас нет способа добавить разделители во время расстановки содержимого контейнера, поэтому мы должны добавить их заранее. После, зная их количество, на моменте расстановки элементов мы отделим их от остальных.

Считать количество элементов мы будем весьма костыльно, но тем не менее удобно:

@Composable
fun rememberElementsCount(content: @Composable () -> Unit) : State<Int?> {
    val result : MutableState<Int?> = remember { mutableStateOf(null) }
    Layout(
        modifier = Modifier.size(0.dp),
        content = content,
    ) { measurables, _ ->
        result.value = measurables.size
        layout(0, 0) {}
    }
    return result
}

Так как Compose не гарантирует порядка композиции, мы не можем быть уверены, что result будет установлен до выхода из функции, из‑за чего нам приходится использовать опциональные значения.

Теперь мы можем отделять изначальное содержимое от разделителей, добавленных нами:

@Composable
private fun DividerLayout(
    modifier: Modifier = Modifier,
    dividerCount: Int,
    divider: @Composable () -> Unit = { androidx.compose.material.Divider() },
    content: @Composable () -> Unit,
    ...
) {
    Layout(
        modifier = modifier,
        content = {
            content()
            repeat(dividerCount) {
                divider()
            }
        },
    ) { measurables, constraints ->
        val contentPlaceables = measurables.dropLast(dividerCount).map { it.measure(constraints) }
        val dividerPlaceables = measurables.takeLast(dividerCount).map { it.measure(constraints) }
        measure(constraints, contentPlaceables, dividerPlaceables, ...) // Какая-то функция расстановки элементов контейнера
    }
}

Имея их раздельно, не составит труда написать функцию measure. Результат будет выглядеть так:

Увы, из‑за необходимости в рекомпозиции для работы, этот контейнер не будет работать в предпросмотре. Если вы знаете, как сделать это без костыля с пустым Layout, добро пожаловать в комментарии.

Вместо послесловия

Изначально статья была названа продвинутой, так как в неё должны были рассматриваться все стандартные параметры других контейнеров, такие как Alignment, Arrangement и т. п. Но оказалось, что это весьма просто, и писать там не о чем. Поэтому она была переименована в вторую часть.

Если есть что‑то, что не упомянутое в статье и не являющиеся понятным в реализации, пишите в комментарии, и я напишу 3 часть статьи.

Комментарии (6)


  1. Rusrst
    04.04.2023 17:33

    Не проще написать modifier к элементу контента и пусть этот modifier все сам рисует. Есть же measurescope, там все можно посчитать. Там же все равно наверняка список с foreach.

    Это просто попытка сделать как в recyclerview с декораторами, но у compose же другая парадигма прям.


    1. GoogleTan Автор
      04.04.2023 17:33

      Никогда не использовала recyclerview, поэтому ничего не могу сказать.

      Не совсем представляю, как Modifier может что-то рисовать. В моём понимании он используется для настройки различных Composable. Можете привести пример, как Modifier что-то рисует?


      1. Rusrst
        04.04.2023 17:33

        Да, конечно, вот modifiers для рисования по ссылке https://developer.android.com/jetpack/compose/graphics/draw/modifiers

        Тот же Canvas в compose - он тоже просто modifier drawBehind для spacer

        Суть предложения объединить два modifier - один размер посчитает, другой нарисует что надо. Не вижу сложностей вроде

        Рисовать просто

        .drawBehind{

        drawline(0, size.width, 0, 2.dp.value)

        }


        1. GoogleTan Автор
          04.04.2023 17:33

          Я не смогла найти модифаер, который даёт размеры всех детей контейнера. А без этого никак. Укажите если оный там есть


          1. Rusrst
            04.04.2023 17:33

            Вы кажется не поняли. Вы в контейнер добавляете элементы по типу

            items.forEach{

            MyFunc()

            }

            Ну и создайте modifier и передайте его каждому ребенку, чтобы ребенок сам для себя все рисовал.

            items.foreach{

            MyFunc(Modifier.myModifier)

            }


            1. GoogleTan Автор
              04.04.2023 17:33

              Тогда мы даём модифаер столько раз, сколько есть разделителей. Это решение мало отличается от добавления их вручную после каждого ребёнка