Исправлена сетка внутри LazyColumn в Jetpack Compose?
В настоящее время в Jetpack Compose этот код выдает
IllegalStateException
потому что вы не можете вложить два вертикально прокручиваемых Composable:
@ExperimentalFoundationApi
@Composable
fun MyLazyColumn() {
LazyColumn {
item {
Text(text = "My LazyColumn Title")
}
item {
LazyVerticalGrid(cells = GridCells.Fixed(4)) {
items(10) {
Box(
modifier = Modifier
.size(50.dp)
.padding(5.dp)
.background(Color.Gray)
)
}
}
}
}
}
Я не хочу, чтобы сама сетка прокручивалась, а просто отображала фиксированную сетку Composable, которую я в нее передаю. Есть ли обходной путь для отображения сетки без прокрутки внутри
LazyColumn
?
5 ответов
LazyVerticalGrid
с фиксированным количеством ячеек имеет довольно простую внутреннюю компоновку, основанную на плюсе
Row
.
Вместо этого вы можете реализовать
gridItems
для использования с
LazyColumn
:
fun LazyListScope.gridItems(
count: Int,
nColumns: Int,
horizontalArrangement: Arrangement.Horizontal = Arrangement.Start,
itemContent: @Composable BoxScope.(Int) -> Unit,
) {
gridItems(
data = List(count) { it },
nColumns = nColumns,
horizontalArrangement = horizontalArrangement,
itemContent = itemContent,
)
}
fun <T> LazyListScope.gridItems(
data: List<T>,
nColumns: Int,
horizontalArrangement: Arrangement.Horizontal = Arrangement.Start,
itemContent: @Composable BoxScope.(T) -> Unit,
) {
val rows = if (data.count() == 0) 0 else 1 + (data.count() - 1) / nColumns
items(rows) { rowIndex ->
Row(horizontalArrangement = horizontalArrangement) {
for (columnIndex in 0 until nColumns) {
val itemIndex = rowIndex * nColumns + columnIndex
if (itemIndex < data.count()) {
Box(
modifier = Modifier.weight(1f, fill = true),
propagateMinConstraints = true
) {
itemContent.invoke(this, data[itemIndex])
}
} else {
Spacer(Modifier.weight(1f, fill = true))
}
}
}
}
}
Использование:
LazyColumn {
item {
Text(text = "My LazyColumn Title")
}
// with count
gridItems(10, nColumns = 4) { index ->
Box(
modifier = Modifier
.size(50.dp)
.padding(5.dp)
.background(Color.Gray)
)
}
// or with list of items
gridItems(listOf(1,2,3), nColumns = 4) { item ->
Box(
modifier = Modifier
.size(50.dp)
.padding(5.dp)
.background(Color.Gray)
)
}
}
Этот принятый ответ великолепен и отлично работает, если у вас есть небольшой список данных и простой пользовательский интерфейс для элементов сетки, но когда ваши данные большие, а элементы сетки имеют сложный пользовательский интерфейс и состояние, он станет медленным и тяжелым.
В моем случае мне удалось решить эту проблему, сделав LazyVerticalGrid в качестве контейнера для элементов сетки, а также другого содержимого, теперь это выглядит так:
val spanCount = 2
LazyVerticalGrid(
modifier = modifier
.fillMaxWidth(),
cells = GridCells.Fixed(spanCount),
state = rememberLazyGridState(),
) {
item(
span = {
/** Take a full row */
GridItemSpan(currentLineSpan = spanCount)
}
) {
Column(
modifier = Modifier.fillMaxWidth()
) {
items.forEach {
/** Iterate over your items here */
}
}
}
items(
items = gridItems
){gridItem->
/** Grid items go here */
}
}
Для меня я добавил явное указание высоты, и оно отлично работало со мной для LazyverticalGrid внутри ленивого столбца.
LazyVerticalGrid(
cells = GridCells.Fixed(3),
contentPadding = PaddingValues(5.dp),
modifier = Modifier
.layoutId("lazy_list")
.height(200.dp)
В настоящее время в Jetpack Compose этот код выдает исключение IllegalStateException, поскольку невозможно вложить два компонента Composable с вертикальной прокруткой:
Это не совсем так: здесь говорится, что вы не можете передать Constraints.Infinity в другой Composable с возможностью прокрутки. Составные элементы с прокруткой, поскольку LazyLists также полагаются на модификатор прокрутки, не могут измерять себя с помощью maxWidth/Height, равного бесконечности. Если вы измените maxHeight на что-то конечное, у вас не возникнет проблемы. Ограничения означают диапазон измерения компонуемых объектов перед их размещением. Если вы передадите 0-конечное измерение, оно будет измерено в измерении.
У вас также нет фиксированной высоты, если ваш контент в вертикальной сетке меньше родительской высоты - суммы высоты другого контента.
Передавая его с помощью Modifier.heighIn(max), вы ограничиваете измерение высоты сетки до 0 и между родительскими элементами.
@Preview
@Composable
private fun LazyTest() {
BoxWithConstraints {
val parentHeight = maxHeight
LazyColumn {
item {
Text(text = "My LazyColumn Title")
}
item {
LazyVerticalGrid(
modifier = Modifier.heightIn(max = parentHeight),
columns = GridCells.Fixed(4)
) {
items(10) {
Box(
modifier = Modifier
.size(50.dp)
.padding(5.dp)
.background(Color.Gray)
)
}
}
}
}
}
}
Если в вашем случае вы хотите, чтобы Grid покрыл остальную часть высоты LazyColumn, вы можете измерить высоту других элементов и использовать Modifier.heigh(родительская высота - сумма высот другого контента), на самом деле это довольно просто с помощью специальногоLayout
.
И если вам интересно, почему возникает это исключение, оно находится в коде прокрутки, потому что они выполняют проверку перед установкой компоновки с модификатором прокрутки as.
private class ScrollingLayoutNode(
var scrollerState: ScrollState,
var isReversed: Boolean,
var isVertical: Boolean
) : LayoutModifierNode, Modifier.Node() {
override fun MeasureScope.measure(
measurable: Measurable,
constraints: Constraints
): MeasureResult {
// And here in LazyVerticalGrid there is a check if there is Constraints.Infinity
checkScrollableContainerConstraints(
constraints,
if (isVertical) Orientation.Vertical else Orientation.Horizontal
)
// this is how LazyColumn passes Constraints.Infinity content or child Composables
val childConstraints = constraints.copy(
maxHeight = if (isVertical) Constraints.Infinity else constraints.maxHeight,
maxWidth = if (isVertical) constraints.maxWidth else Constraints.Infinity
)
val placeable = measurable.measure(childConstraints)
val width = placeable.width.coerceAtMost(constraints.maxWidth)
val height = placeable.height.coerceAtMost(constraints.maxHeight)
val scrollHeight = placeable.height - height
val scrollWidth = placeable.width - width
val side = if (isVertical) scrollHeight else scrollWidth
// The max value must be updated before returning from the measure block so that any other
// chained RemeasurementModifiers that try to perform scrolling based on the new
// measurements inside onRemeasured are able to scroll to the new max based on the newly-
// measured size.
}
ИcheckScrollableContainerConstraints
функция
fun checkScrollableContainerConstraints(
constraints: Constraints,
orientation: Orientation
) {
if (orientation == Orientation.Vertical) {
check(constraints.maxHeight != Constraints.Infinity) {
"Vertically scrollable component was measured with an infinity maximum height " +
"constraints, which is disallowed. One of the common reasons is nesting layouts " +
"like LazyColumn and Column(Modifier.verticalScroll()). If you want to add a " +
"header before the list of items please add a header as a separate item() before " +
"the main items() inside the LazyColumn scope. There are could be other reasons " +
"for this to happen: your ComposeView was added into a LinearLayout with some " +
"weight, you applied Modifier.wrapContentSize(unbounded = true) or wrote a " +
"custom layout. Please try to remove the source of infinite constraints in the " +
"hierarchy above the scrolling container."
}
} else {
check(constraints.maxWidth != Constraints.Infinity) {
"Horizontally scrollable component was measured with an infinity maximum width " +
"constraints, which is disallowed. One of the common reasons is nesting layouts " +
"like LazyRow and Row(Modifier.horizontalScroll()). If you want to add a " +
"header before the list of items please add a header as a separate item() before " +
"the main items() inside the LazyRow scope. There are could be other reasons " +
"for this to happen: your ComposeView was added into a LinearLayout with some " +
"weight, you applied Modifier.wrapContentSize(unbounded = true) or wrote a " +
"custom layout. Please try to remove the source of infinite constraints in the " +
"hierarchy above the scrolling container."
}
}
}
Если ваша сетка маленькая и не слишком сложная (например, требует отложенной загрузки), вы можете использовать Jetpack Compose ConstraintLayout . Я столкнулся с этим, когда у меня был прокручиваемый родительский элемент, который я не мог изменить.
Он работает почти так же, как ConstraintLayout на основе представления, хотя и немного более ограниченно.
Например, это будет сетка точек маркера, которая выравнивает текстовые строки по самой длинной ширине маркера:
val spaceAfterBullet = 8.dp
val spaceBetweenRows = 4.dp
ConstraintLayout(modifier = Modifier.fillMaxWidth()) {
val (bullet1, bullet2, bullet3, text1, text2, text3) = createRefs()
val bulletBarrier = createEndBarrier(bullet1, bullet2, bullet3)
val row1Barrier = createBottomBarrier(bullet1, text1)
val row2Barrier = createBottomBarrier(bullet2, text2)
Text(text = "1.", textAlign = TextAlign.End, modifier = Modifier.constrainAs(bullet1) {
})
Text(text = "First line of text", modifier = Modifier
.padding(start = spaceAfterBullet)
.constrainAs(text1) {
start.linkTo(bulletBarrier)
})
Text(text = "2.", textAlign = TextAlign.End, modifier = Modifier.constrainAs(bullet2) {
top.linkTo(row1Barrier, margin = spaceBetweenRows)
})
Text(
text = "Second line of text which will overflow into two lines of text :)",
modifier = Modifier
.padding(start = spaceAfterBullet)
.constrainAs(text2) {
start.linkTo(bulletBarrier)
top.linkTo(row1Barrier, margin = spaceBetweenRows)
})
Text(text = "99.", textAlign = TextAlign.End, modifier = Modifier.constrainAs(bullet3) {
top.linkTo(row2Barrier, margin = spaceBetweenRows)
})
Text(
text = "\"Every man has two lives, and the second starts when he realizes he has just one\" — Confucius",
modifier = Modifier
.padding(start = spaceAfterBullet)
.constrainAs(text3) {
start.linkTo(bulletBarrier)
top.linkTo(row2Barrier, margin = spaceBetweenRows)
})
}
Я также создал здесь общий вариант , который допускает повторное использование.