I am trying to achieve this:
This section is inside my ProductDetailsScreen
composable. Here is the code:
@Composable
fun ProductDetailsScreen(
modifier: Modifier = Modifier
) {
val scrollState = rememberScrollState()
Column(modifier = Modifier
.padding(horizontal = dimensionResource(id = R.dimen.dimen_24))
.verticalScroll(scrollState) // this makes the screen scrollable
) {
//images
ProductImages(
images = product?.productImage
)
//other stuff...
}
}
Here is also my ProductImages
composable:
@Composable
fun ProductImages(
modifier: Modifier = Modifier,
images: List<String>? = null,
) {
if (!images.isNullOrEmpty()) {
Column(
modifier = modifier,
verticalArrangement = Arrangement.spacedBy(dimensionResource(id = R.dimen.dimen_16))
) {
//title
ProductSectionTitle(
title = String.format(
stringResource(id = R.string.media_section_title),
images.size
)
)
//images
Row() {
ProductImage(modifier = Modifier.size(160.dp), image = images[0])
LazyVerticalGrid(
columns = GridCells.Fixed(2)
) {
items(images.subList(1, 5)) { image ->
ProductImage(modifier = Modifier.size(75.dp), image = image)
}
}
}
}
}
}
As you can see in the layout I have 5 images. To achieve this layout I put the components within a Row
. So The first image which is the biggest one is the first item of the Row
as you can see from this code:
//images
Row() {
ProductImage(modifier = Modifier.size(160.dp), image = images[0])
}
And I put the other images into the LazyVerticalGrid
cuz it divides images into the cells and I don't need to handle the calculation process by myself :
LazyVerticalGrid(
columns = GridCells.Fixed(2)
) {
//since I already load the first image, I sublist the original one and take only 4 images
items(images.subList(1, 5)) { image ->
ProductImage(modifier = Modifier.size(75.dp), image = image)
}
}
But since it is in the Column
that has the .verticalScroll()
modifier it throws:
java.lang.IllegalStateException: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.
I don't want to give a fixed height to the LazyVerticalGrid
which fixes the issue. I want to make it wrap its content. Is there any other way to achieve this layout or how can I prevent this error?
CodePudding user response:
Setting a Modifier with fixed height for LazyVerticalGrid would solve it.
I made this example with LazyColumn
but Column
with vertical scrollstrong text also works when LazyVerticalGrid
has 160.dp height
@Composable
private fun CustomGrid() {
LazyColumn(
contentPadding = PaddingValues(8.dp),
modifier = Modifier.fillMaxSize(),
verticalArrangement = Arrangement.spacedBy(10.dp)
) {
item {
Text("Title", fontSize = 20.sp)
}
item {
Row {
Image(
painter = painterResource(id = images.first()),
modifier = Modifier
.size(160.dp)
.clip(RoundedCornerShape(10.dp)),
contentScale = ContentScale.FillBounds,
contentDescription = null
)
Spacer(modifier = Modifier.width(10.dp))
LazyVerticalGrid(
verticalArrangement = Arrangement.spacedBy(10.dp),
horizontalArrangement = Arrangement.spacedBy(10.dp),
modifier = Modifier
.fillMaxWidth()
.height(160.dp),
columns = GridCells.Fixed(2)
) {
items(items = images.subList(1, 5)) { image ->
Image(
modifier = Modifier
.size(75.dp)
.clip(RoundedCornerShape(10.dp)),
painter = painterResource(id = image),
contentScale = ContentScale.FillBounds,
contentDescription = null
)
}
}
}
}
item {
Text("Another Title", fontSize = 20.sp)
}
}
}
CodePudding user response:
This applies only to cases when nesting scrollable children without a predefined size inside another same direction scrollable parent. For example, trying to nest a child LazyColumn without a fixed height inside a vertically scrollable Column parent:
// Throws IllegalStateException
Column(
modifier = Modifier.verticalScroll(state)
) {
LazyVerticalGrid {
// ...
}
}
Instead, the same result can be achieved by wrapping all of your composables inside one parent LazyColumn and using its DSL to pass in different types of content. This enables emitting single items, as well as multiple list items, all in one place:
LazyVerticalGrid {
item {
Header()
}
items(data) { item ->
Item(item)
}
item {
Footer()
}
}
reference: https://developer.android.com/jetpack/compose/lists#avoid-nesting-scrollable
CodePudding user response:
What you can do is borrow and adapt the sample Grid.kt designed by the Jetpack Compose team. You can then use it everywhere in your project within LazyColumns. The code is Open Source:
/*
* Copyright 2020 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.example.jetsnack.ui.components
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
import androidx.compose.ui.layout.Layout
/**
* A simple grid which lays elements out vertically in evenly sized [columns].
*/
@Composable
fun VerticalGrid(
modifier: Modifier = Modifier,
columns: Int = 2,
content: @Composable () -> Unit
) {
Layout(
content = content,
modifier = modifier
) { measurables, constraints ->
val itemWidth = constraints.maxWidth / columns
// Keep given height constraints, but set an exact width
val itemConstraints = constraints.copy(
minWidth = itemWidth,
maxWidth = itemWidth
)
// Measure each item with these constraints
val placeables = measurables.map { it.measure(itemConstraints) }
// Track each columns height so we can calculate the overall height
val columnHeights = Array(columns) { 0 }
placeables.forEachIndexed { index, placeable ->
val column = index % columns
columnHeights[column] = placeable.height
}
val height = (columnHeights.maxOrNull() ?: constraints.minHeight)
.coerceAtMost(constraints.maxHeight)
layout(
width = constraints.maxWidth,
height = height
) {
// Track the Y co-ord per column we have placed up to
val columnY = Array(columns) { 0 }
placeables.forEachIndexed { index, placeable ->
val column = index % columns
placeable.placeRelative(
x = column * itemWidth,
y = columnY[column]
)
columnY[column] = placeable.height
}
}
}
}
You can use it like this:
VerticalGrid(
columns = 2
) {
(images.subList(1, 5)).forEach { image ->
ProductImage(modifier = Modifier.size(75.dp), image = image)
}
}