JonasVautherin
JonasVautherin

Reputation: 8053

How can I make all cells of a Row have the width of the widest one?

I am making a "ToggleGroup" with Jetpack-Compose, using essentially a Row into which I print Text. I manage to make it work if I tune the width manually (.width(70.dp) in the code below), but I would like it to automatically do that.

I essentially want this:

enter image description here

But without manually adding .width(70.dp), I get this:

enter image description here

My current (tuned) code is the following:

Row(
    horizontalArrangement = Arrangement.End,
) {
    options.forEach { option ->
        val isSelected = option == selectedOption
        val textColor = if (isSelected) Color.White else MaterialTheme.colors.primary
        val backgroundColor = if (isSelected) Color.Gray else Color.White
        Row(
            horizontalArrangement = Arrangement.Center,
            modifier = Modifier
                .padding(
                    vertical = 6.dp, horizontal = 1.dp
                )
                .width(70.dp)
                .background(backgroundColor)
                .clickable { onSelectionChanged(option) }
        ) {
            Text(
                text = option,
                color = textColor,
                modifier = Modifier.padding(14.dp),
            )
        }
    }
}

It feels similar to this question, but somehow it's different because I use Row and the question uses Column (or at least I did not manage to use Intrinsics correctly).

How could I do that?

Upvotes: 1

Views: 1071

Answers (2)

Abhimanyu
Abhimanyu

Reputation: 14877

Changes required.

1. On the parent Row, use Modifier.width(IntrinsicSize.Min)

(min|max)IntrinsicWidth: Given this height, what's the minimum/maximum width you can paint your content properly?

Source - Docs

2. Use Modifier.weight(1F) on all children.

Size the element's width proportional to its weight relative to other weighted sibling elements in the Row.

The parent will divide the horizontal space remaining after measuring unweighted child elements and distribute it according to this weight.

Source - Docs

3. Use Modifier.width(IntrinsicSize.Max) on all children.

This ensures the Text inside the children composables are not wrapped.

(You can verify this by removing the modifier and adding long text)

Screenshot

Sample code

@Composable
fun AutoWidthRow() {
    val items = listOf("Item 1", "Item 2", "Item 300")
    Row(
        horizontalArrangement = Arrangement.End,
        modifier = Modifier.width(IntrinsicSize.Min),
    ) {
        items.forEach { option ->
            Row(
                horizontalArrangement = Arrangement.Center,
                modifier = Modifier
                    .padding(
                        vertical = 6.dp, horizontal = 1.dp
                    )
                    .width(IntrinsicSize.Max) // Removing this will wrap the text
                    .weight(1F)
                    .background(Color.Black)
            ) {
                Text(
                    text = option,
                    color = Color.White,
                    modifier = Modifier
                        .padding(14.dp),
                )
            }
        }
    }
}

Upvotes: 1

Francesc
Francesc

Reputation: 29330

You should create a custom Layout

@Composable
fun EqualSizeTiles(
    modifier: Modifier = Modifier,
    content: @Composable () -> Unit,
) {
    Layout(
        content = content,
        modifier = modifier,
    ) { measurables, constraints ->
        layoutTiles(
            measurables,
            constraints
        )
    }
}

private fun MeasureScope.layoutTiles(
    measurables: List<Measurable>,
    constraints: Constraints,
): MeasureResult {
    val tileHeight = constraints.maxHeight
    val tileWidths = measurables.map { measurable ->
        measurable.maxIntrinsicWidth(tileHeight)
    }
    val tileWidth = tileWidths.maxOrNull() ?: 0
    val tileConstraints = Constraints(
        minWidth = tileWidth,
        minHeight = 0,
        maxWidth = tileWidth,
        maxHeight = constraints.maxHeight,
    )
    val placeables = measurables.map { measurable ->
        measurable.measure(tileConstraints)
    }
    val width = (placeables.size * tileWidth).coerceAtMost(constraints.maxWidth)
    return layout(width = width, height = tileHeight) {
        placeables.forEachIndexed { index, placeable ->
            placeable.place(tileWidth * index, 0)
        }
    }
}

@Preview(showBackground = true, widthDp = 512)
@Composable
private fun EqualSizeTilesPreview() {
    WeatherSampleTheme {
        Surface(
            modifier = Modifier
                .fillMaxWidth()
                .background(color = Color.Yellow)
        ) {
            EqualSizeTiles(
                modifier = Modifier
                    .height(64.dp)
                    .background(color = Color.Green)
                    .padding(all = 8.dp)
            ) {
                Text(
                    text = "Left",
                    textAlign = TextAlign.Center,
                    modifier = Modifier
                        .background(color = Color.Red)
                        .padding(all = 8.dp)
                        .fillMaxHeight(),
                )
                Text(
                    text = "Center",
                    textAlign = TextAlign.Center,
                    modifier = Modifier
                        .background(color = Color.Yellow)
                        .padding(all = 8.dp)
                        .fillMaxHeight(),
                )
                Text(
                    text = "Right element",
                    textAlign = TextAlign.Center,
                    modifier = Modifier
                        .background(color = Color.Blue)
                        .padding(all = 8.dp)
                        .fillMaxHeight(),
                )
            }
        }
    }
}

Upvotes: 1

Related Questions