Введение
В этой статье будет пример контейнера с добавлением новых элементов во время композиции. В частности разделителей.
Эта статья поделена на 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 часть статьи.
Rusrst
Не проще написать modifier к элементу контента и пусть этот modifier все сам рисует. Есть же measurescope, там все можно посчитать. Там же все равно наверняка список с foreach.
Это просто попытка сделать как в recyclerview с декораторами, но у compose же другая парадигма прям.
GoogleTan Автор
Никогда не использовала recyclerview, поэтому ничего не могу сказать.
Не совсем представляю, как Modifier может что-то рисовать. В моём понимании он используется для настройки различных Composable. Можете привести пример, как Modifier что-то рисует?
Rusrst
Да, конечно, вот 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)
}
GoogleTan Автор
Я не смогла найти модифаер, который даёт размеры всех детей контейнера. А без этого никак. Укажите если оный там есть
Rusrst
Вы кажется не поняли. Вы в контейнер добавляете элементы по типу
items.forEach{
MyFunc()
}
Ну и создайте modifier и передайте его каждому ребенку, чтобы ребенок сам для себя все рисовал.
items.foreach{
MyFunc(Modifier.myModifier)
}
GoogleTan Автор
Тогда мы даём модифаер столько раз, сколько есть разделителей. Это решение мало отличается от добавления их вручную после каждого ребёнка