Jetpack Compose: How to make all items in a FlowRow the same height

217 views Asked by At

I have some dynamic Text Composables laid out in a FlowRow, but I can't seem to get them to be the same height. With a normal Row, you can use .height(Intrinsic.Min) on the Row in conjunction with .fillMaxHeight() on the children, but I can't get that to work with the FlowRow. The documentation of Flow Layouts talks about alignment of items with different heights within the FlowRow, but it doesn't mention how to make them all the same height. Maybe I missed something?

Here's a code example. I commented out where I tried intrinsic height along with fillMaxHeight:

import androidx.compose.foundation.background
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.ExperimentalLayoutApi
import androidx.compose.foundation.layout.FlowRow
import androidx.compose.foundation.layout.IntrinsicSize
import androidx.compose.foundation.layout.fillMaxHeight
import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.width
import androidx.compose.material.Text
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp


@OptIn(ExperimentalLayoutApi::class)
@Preview(showBackground = true, widthDp = 400)
@Composable
private fun FlowRowPreview() {
    val textModifier = Modifier.width(185.dp).background(Color.Red)//.fillMaxHeight()
    FlowRow(
        horizontalArrangement = Arrangement.Absolute.SpaceBetween,
        verticalArrangement = Arrangement.spacedBy(16.dp),
        modifier = Modifier//.height(IntrinsicSize.Min)
    ) {
        Text(modifier = textModifier, text = "Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam")
        Text(modifier = textModifier, text = "Lorem ipsum dolor sit amet, consectetur adipiscing elit")
        Text(modifier = textModifier, text = "Lorem ipsum dolor sit amet, consectetur adipiscing elit")
        Text(modifier = textModifier, text = "Lorem ipsum dolor sit amet, consectetur adipiscing elit")
    }
}

What the Code currently shows: Preview of how code is currently

What the code shows when I uncomment .height(IntrinsicSize.Min) and .fillMaxHeight(): Preview when I uncomment the height modifiers

What I actually want: Preview of expected behavior

2

There are 2 answers

1
Uchenna F. Okoye On BEST ANSWER
3
deadfish On

I have tried to get all the heights of the items and once they are all displayed I find the maximum height and apply it to all of them. Here is the code:

@OptIn(ExperimentalLayoutApi::class)
@Composable
private fun FlowRowPreview() {
    val lorem = "" +
            "Lorem ipsum dolor sit amet, consectetur adipiscing elit, " +
            "sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. " +
            "Ut enim ad minim"

    // Container to store all heights
    val heightsMap = mutableMapOf<Int, Int>() // map instead of list to avoid side effects
    
    // Will recompose views once the value is changed
    val finalHeightBlock = remember { mutableIntStateOf(0) } // final height to set
    
    // Data we are going to 
    val data = listOf(
        lorem,
        lorem.substring(0, lorem.length / 2),
        lorem.substring(0, lorem.length / 2),
        lorem.substring(0, lorem.length / 2),
    ).mapIndexed { index, text ->
        index to text
    }
    
    // Block for storing heights and calling recompose once all heights are stored
    val reportHeight: (Int, Int) -> Unit = { index, height ->
        heightsMap[index] = height

        if (heightsMap.size == data.size) {
            finalHeightBlock.intValue = heightsMap.maxOf { it.value }
        }
    }

    FlowRow(
        horizontalArrangement = Arrangement.Absolute.SpaceBetween,
        verticalArrangement = Arrangement.spacedBy(16.dp),
    ) {
        data.forEach {
            FlowText(it.second, it.first, finalHeightBlock, reportHeight)
        }
    }
}

@Composable
private fun FlowText(
    lorem: String,
    index: Int,
    height: MutableIntState,
    reportHeight: (Int, Int) -> Unit,
) {
    val modifier = if (height.intValue > 0) {
        Modifier
            .width(185.dp)
            .height(height.intValue.pxToDp())
            .background(Color.Red)
    } else {
        Modifier
            .width(185.dp)
            .wrapContentHeight()
            .background(Color.Red)
            .onGloballyPositioned { it ->
                reportHeight(index, it.size.height)
            }
    }

    Text(
        modifier = modifier,
        text = lorem
    )
}

@Composable
fun Int.pxToDp(): Dp {
    val density = LocalDensity.current.density
    return (this / density).dp
}

Preview: enter image description here