Search code examples
androidandroid-layoutandroid-jetpack-compose

Compose: wrap text in Row layout, instead of pushing siblings out


I'm dipping my toes into Jetpack Compose, but I'm stumped by the behaviour of Row. I have a text next to an icon button, and I want the icon button to be anchored to the side with a minimum width of 48dp, and have text wrap around it. Like this:

Layout goal

But the text does not wrap, it eats up all the space in the Row:

Layout previews

@Composable
fun SampleLayout(text: String) {
                    Row(
                        modifier = Modifier.fillMaxWidth(),
                        horizontalArrangement = Arrangement.SpaceBetween,
                    ) {
                        Text(text)
                        IconButton(
                            onClick = {  },
                        ) {
                            Icon(
                                imageVector = androidx.compose.material.icons.Icons.Default.StarBorder,
                                null
                            )
                        }
                    }
}

@Preview(showBackground = true, backgroundColor = 0x006EAEA0, fontScale = 1.5F)
@Composable
fun SamplePreview1() {
    Box(Modifier.padding(16.dp)) {
        SampleLayout("helooooo")
    }
}

@Preview(showBackground = true, backgroundColor = 0x006EAEA0, fontScale = 1.5F)
@Composable
fun SamplePreview2() {
    Box(Modifier.padding(16.dp)) {
        SampleLayout("helooooooooooooooooooooooooooo")
    }
}
@Preview(showBackground = true, backgroundColor = 0x006EAEA0, fontScale = 1.5F)
@Composable
fun SamplePreview3() {
    Box(Modifier.padding(16.dp)) {
        SampleLayout("heloooooooooooooooooooooooooooooooooooooooo")
    }
}

I've tried setting the minimum width of the icon 48dp, but the text then still fills until the end of the row.

How can I make sure the the Text width does not go further than the icon button?


Solution

  • Row measures its children one after an other, and if some item needs to take all space available(e.g. if you use fillMaxWidth, or in this case text that has more than one line), next items won't have any space left for them.

    If it's layout logic supports compression up to zero size, you won't see it at all (like in case with the Icon), otherwise it can be seen at the end of the row, actually taking zero size but being drawn outsize of the bounds.

    To change order in which children are measured, you can use weight modifier: in this case the size of Icon will be calculated before Text:

    The parent will divide the vertical space remaining after measuring unweighted child elements

    Also weight has a fill parameter, which is set to true by default. This is equivalent to fillMaxWidth (when weight is used inside a Row), so you can skip the fillMaxWidth modifier in your parent. When you don't need this behavior, pass false to this parameter.

    Row{
        Text(text, modifier = Modifier.weight(1f))
        IconButton(
            onClick = { }
        ) {
            Icon(
                imageVector = Icons.Default.StarBorder,
                null
            )
        }
    }