I'm trying to implement a layout in Compose where the items of a horizontally scrollable Row
should all have the same height, so smaller items should adjust to the size of the biggest item in the row. I know about intrinsic size but I just can't get it to work. Also I don't want to assign a fixed height to the Row
, as the Row's height should also be the height of its biggest child composable.
This is the simplified layout
@Composable
fun Screen(
modifier: Modifier = Modifier,
) {
Row(
modifier = modifier
.height(IntrinsicSize.Min)
.horizontalScroll(state = rememberScrollState()),
horizontalArrangement = Arrangement.spacedBy(10.dp),
) {
Item(
text = "Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy " +
"eirmod tempor invidunt ut labore et dolore magna aliquyam"
)
Item(
text = "Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy " +
"eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam " +
"voluptua. At"
)
Item(
text = "Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam"
)
}
}
@Composable
private fun Item(
modifier: Modifier = Modifier,
text: String,
) {
Column(
modifier = modifier.width(200.dp),
horizontalAlignment = Alignment.End,
verticalArrangement = Arrangement.SpaceBetween
) {
Column {
Text("Some static text")
// Dynamic text
Text(
text,
modifier = Modifier.padding(top = 5.dp)
)
}
// The space between these two composables should be flexible,
// hence the outer column with Arrangement.SpaceBetween
Button(
modifier = Modifier.padding(top = 20.dp),
onClick = {}
) {
Text("Button")
}
}
}
This is the result
but what I actually want is
When I apply fillMaxHeight()
to Item
, the items take up the whole height and all buttons are aligned to the bottom of the screen.
Jetpack Compose version: 1.1.0
Update: This was a bug in Compose which was fixed in compose-foundation
version 1.3.0-beta01.
This method is only applicable if you have a small list, as Row() loads all items at once (not lazily)
As of Compose v1.3.0 Lazy lists don't support modifier.height(intrinsicSize = IntrinsicSize.Max)
So for now we have to use Row with modifier = modifier.height(intrinsicSize = IntrinsicSize.Max)
When writing RowItem add Spacer(modifier = Modifier.weight(1f))
to fill empty space
@Composable
fun RowItem(modifier: Modifier = Modifier) {
Surface(
modifier = modifier,
color = Color.Gray,
shape = RoundedCornerShape(12.dp)
) {
Column(
verticalArrangement = Arrangement.spacedBy(12.dp),
modifier = Modifier.padding(16.dp)
) {
repeat((1..4).random()) {
Text(
text = "item $it",
color = Color.White,
modifier = Modifier.padding(
horizontal = 16.dp,
vertical = 4.dp
)
)
}
Spacer(modifier = Modifier.weight(1f)) // this is required to push below composables to bottom
Button(onClick = {
}) { Text(text = "Button") }
}
}}
Make sure to add horizontalScroll(rememberScrollState())
to make Row scrollable and height(intrinsicSize = IntrinsicSize.Max)
to make the height of all cards to the tallest item.
@Composable
fun ScrollableRow() {
Row(
Modifier
.horizontalScroll(rememberScrollState()) // this makes it scrollable
.height(intrinsicSize = IntrinsicSize.Max) // this make height of all cards to the tallest card.
.padding(horizontal = 16.dp),
content = {
repeat(4) {
RowItem(modifier = Modifier.fillMaxHeight())
}
},
horizontalArrangement = Arrangement.spacedBy(16.dp),
)}
Result:
I created a solution that is not recommended by Google, but works well for us.
fun Modifier.minimumHeightModifier(state: MinimumHeightState, density: Density) = onSizeChanged { size ->
val itemHeight = with(density) {
val height = size.height
height.toDp()
}
if (itemHeight > state.minHeight ?: 0.dp) {
state.minHeight = itemHeight
}
}.defaultMinSize(minHeight = state.minHeight ?: Dp.Unspecified)
class MinimumHeightState(minHeight: Dp? = null) {
var minHeight by mutableStateOf(minHeight)
}
You then configure and apply the modifier to everything you want to have the same minimum height
val density = LocalDensity.current
val minimumHeightState = remember { MinimumHeightState() }
val minimumHeightStateModifier = Modifier.minimumHeightModifier(
minimumHeightState,
density
)
These were all in a LazyRow
itemsIndexed(items = carouselModel.subviews, key = { _, item -> item.id }) { _, item ->
when (item) {
is BasicCard -> {
FooCard(
modifier = minimumHeightStateModifier,
section = item,
onAction = onAction
)
}
is BarCard -> {
BarVerticalCard(
modifier = minimumHeightStateModifier,
section = item,
onAction = onAction
)
}
A discussion about the solution on the kotlin slack can be found here: https://kotlinlang.slack.com/archives/CJLTWPH7S/p1649956718414129
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With