LazyList
exceptions with dimensions occur when you try to measure respective dimension with Constraints.Infinity
. I explained about in this answer how Constraints are applied based on modifiers, Modifier.scroll()
. Also, using a scrollable parent Composable like LazyColumn
creates Constraints with infinite height, LazyRow
creates Constraints with infinite width.
You can use Modifier.heightIn(maxHeight) with maxHeight which means this LazyColumn can be measured or assigned total height of parent at most if sum of content height is bigger than rest of the height that is not used by other items.
You can use Modifier.heightIn(maxHeight)
or Modifier.layout{}
and constrain max height to screen height or selected height in px. Constraints return dimensions in px that is to be used with layout function. Getting Constrains height from parent by wrapping it with BoxWithConstraints returns non-infinite height
@Composable
private fun Sample() {
val arrayList = remember {
mutableStateListOf<String>().apply {
repeat(40) {
add("Item $it")
}
}
}
Column(
modifier = Modifier
.border(4.dp, Color.Green)
.fillMaxSize()
) {
BoxWithConstraints {
val parentConstraints = this.constraints
val maxHeight = this.maxHeight
LazyColumn {
//other contents
item {
Image(
modifier = Modifier
.height(200.dp)
.fillMaxWidth(),
painter = painterResource(id = R.drawable.landscape2),
contentDescription = null,
contentScale = ContentScale.FillBounds
)
}
item {
LazyHorizontalGrid(
modifier = Modifier
.border(2.dp, Color.Red)
// Alternative 1
// .heightIn(maxHeight)
// Alternative 2
.layout { measurable, constraints ->
val placeable = measurable.measure(
constraints.copy(maxHeight = parentConstraints.maxHeight)
)
layout(placeable.width, placeable.height) {
placeable.placeRelative(0, 0)
}
},
rows = GridCells.Fixed(3),
horizontalArrangement = Arrangement.spacedBy(16.dp),
verticalArrangement = Arrangement.spacedBy(16.dp)
) {
items(arrayList.size) {
Box(
modifier = Modifier
.shadow(2.dp, RoundedCornerShape(8.dp))
.background(Color.White)
.aspectRatio(1f)
.padding(2.dp)
) {
Text(arrayList[it])
}
}
}
}
}
}
}
}
The example above creates a LazyHorizontalGrid with full height of screen, i added an Image
as another element to show that grid covers parent height.
The important thing here is how you set maxHeight for Constraints in
constraints.copy(maxHeight = parentConstraints.maxHeight)
If you want grid to match item size then you should measure size of an item
using rowCount * itemHeight + (rowCount-1)* verticalSpacingInPx
.
constraints.copy(maxHeight = 600f)
for instance.
If you don't know the height of the item you should use Modifier.onSizeChanged or as in this answer SubcomposeLayout
or Layout
How to get exact size without recomposition?
And using Text as dependent item we can do it as
@Composable
private fun Sample2() {
val arrayList = remember {
mutableStateListOf<String>().apply {
repeat(40) {
add("Item $it")
}
}
}
val mainComposable = @Composable {
Box(
modifier = Modifier
.shadow(2.dp, RoundedCornerShape(8.dp))
.background(Color.White)
.size(80.dp)
.padding(2.dp)
) {
Text(arrayList[0])
}
}
Column(
modifier = Modifier
.border(4.dp, Color.Green)
.fillMaxSize()
) {
DimensionSubcomposeLayout(
rowCount = 3,
verticalSpacing = 16.dp,
mainContent = {
mainComposable()
}
) { size: Dp ->
LazyColumn {
//other contents
item {
Image(
modifier = Modifier
.height(200.dp)
.fillMaxWidth(),
painter = painterResource(id = R.drawable.landscape2),
contentDescription = null,
contentScale = ContentScale.FillBounds
)
}
item {
LazyHorizontalGrid(
modifier = Modifier
.height(size)
.border(2.dp, Color.Red),
rows = GridCells.Fixed(3),
horizontalArrangement = Arrangement.spacedBy(16.dp),
verticalArrangement = Arrangement.spacedBy(16.dp)
) {
items(arrayList.size) {
Box(
modifier = Modifier
.shadow(2.dp, RoundedCornerShape(8.dp))
.background(Color.White)
.size(80.dp)
.padding(2.dp)
) {
Text(arrayList[it])
}
}
}
}
}
}
}
}
SubComposeLayout that uses Text height and spacing to calculate height for LazyHorizontal Grid
@Composable
fun DimensionSubcomposeLayout(
modifier: Modifier = Modifier,
rowCount: Int,
verticalSpacing: Dp = 0.dp,
mainContent: @Composable () -> Unit,
dependentContent: @Composable (Dp) -> Unit
) {
val density = LocalDensity.current
val verticalSpacingPx = density.run { verticalSpacing.toPx() }
SubcomposeLayout(
modifier = modifier
) { constraints: Constraints ->
// Subcompose(compose only a section) main content and get Placeable
val mainPlaceable = subcompose(SlotsEnum.Main, mainContent)
.map {
it.measure(constraints)
}
.first()
// Get max width and height of main component
val maxHeight = mainPlaceable.height * rowCount + (rowCount - 1) * verticalSpacingPx
val dpSize = with(density.density) {
maxHeight.toDp()
}
val dependentPlaceable = subcompose(SlotsEnum.Dependent) {
dependentContent(dpSize)
}
.map { measurable: Measurable ->
measurable.measure(constraints)
}
.first()
layout(dependentPlaceable.width, dependentPlaceable.height) {
dependentPlaceable.placeRelative(0, 0)
}
}
}