Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Jetpack Compose: Row with all items same height

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

what I get

but what I actually want is

what I want

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.

like image 227
Sven Jacobs Avatar asked Aug 31 '25 06:08

Sven Jacobs


2 Answers

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:

like image 123
Sahal Nazar Avatar answered Sep 02 '25 21:09

Sahal Nazar


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

like image 35
dazza5000 Avatar answered Sep 02 '25 21:09

dazza5000