5

I run Code A and get Result A.

How can I make the two buttons the same width?

BTW, you know different string has different width, you can't write a hard code such as Modifier.width(100.dp).

Code A

        Row(
            modifier =Modifier.fillMaxWidth(),
            horizontalArrangement = Arrangement.Center
        ) {
            Button(
                modifier = Modifier,
                onClick = { }
            ) {
                Text("Short")
            }

            Button(
                modifier = Modifier.padding(start = 10.dp),
                onClick = { }
            ) {
                Text("This is a Long")
            }
        }

Result A

enter image description here

HelloCW
  • 843
  • 22
  • 125
  • 310

3 Answers3

1

You can achieve setting width of long button to short one as in the answer described here with SubcomposeLayout.

What you need to do is subcompose your layout to check which element has longer width then subcompose your items again with this width as minimum constraint.

@Composable
private fun SubcomposeRow(
    modifier: Modifier = Modifier,
    paddingBetween: Dp = 0.dp,
    content: @Composable () -> Unit = {},
) {
    val density = LocalDensity.current

    SubcomposeLayout(modifier = modifier) { constraints ->

        var subcomposeIndex = 0

        val spaceBetweenButtons = with(density) {
            paddingBetween.roundToPx()
        }

        var placeables: List<Placeable> = subcompose(subcomposeIndex++, content)
            .map {
                it.measure(constraints)
            }

        var maxWidth = 0
        var maxHeight = 0
        var layoutWidth = 0

        placeables.forEach { placeable: Placeable ->
            maxWidth = placeable.width.coerceAtLeast(maxWidth)
                .coerceAtMost(((constraints.maxWidth - spaceBetweenButtons) / 2))
            maxHeight = placeable.height.coerceAtLeast(maxHeight)
        }


        layoutWidth = maxWidth

        // Remeasure every element using width of longest item using it as min width
        // Our max width is half of the remaining area after we subtract space between buttons
        // and we constraint its maximum width to half width minus space between
        if (placeables.isNotEmpty() && placeables.size > 1) {
            placeables = subcompose(subcomposeIndex, content).map { measurable: Measurable ->
                measurable.measure(
                    constraints.copy(
                        minWidth = maxWidth,
                        maxWidth = ((constraints.maxWidth - spaceBetweenButtons) / 2)
                            .coerceAtLeast(maxWidth)
                    )
                )
            }

            layoutWidth = (placeables.sumOf { it.width } + spaceBetweenButtons)
                .coerceAtMost(constraints.maxWidth)

            maxHeight = placeables.maxOf { it.height }
        }

        layout(layoutWidth, maxHeight) {
            var xPos = 0
            placeables.forEach { placeable: Placeable ->
                placeable.placeRelative(xPos, 0)
                xPos += placeable.width + spaceBetweenButtons
            }
        }
    }
}

you can change this layouWidth with constraints.maxWidth if you want it to occupy available space.

Then, instead of setting them from beginning of Composable you need to have your algorithm for laying them out at 0 y position and x position from beginning of the Composable if you want to have different spacings.

placeable.placeRelative(xPos, 0)

Usage

@Composable
private fun Sample() {
    Column(
        modifier = Modifier
            .fillMaxSize()
            .padding(10.dp)
    ) {

        Spacer(modifier = Modifier.height(20.dp))

        SubcomposeRow(
            modifier = Modifier.background(Color.LightGray).border(3.dp, Color.Green),
            paddingBetween = 20.dp
        ) {
            Button(
                modifier = Modifier,
                onClick = { }
            ) {
                Text("Short")
            }

            Button(
                modifier = Modifier,
                onClick = { }
            ) {
                Text("This is a Long")
            }
        }

        Spacer(modifier = Modifier.height(20.dp))

        SubcomposeRow(
            modifier = Modifier.background(Color.LightGray).border(3.dp, Color.Green),
            paddingBetween = 20.dp
        ) {
            Button(
                modifier = Modifier,
                onClick = { }
            ) {
                Text("Short")
            }

            Button(
                modifier = Modifier,
                onClick = { }
            ) {
                Text("This is a Long a button with text")
            }
        }
    }
}

Result

enter image description here

Thracian
  • 43,021
  • 16
  • 133
  • 222
  • Thanks for sharing your answer. is there no simpler way to achieve this? Can we say something like `val widthA = buttonA.calculateWidth()` and then in buttonB `width(widthA)` or something similar. I am trying to make a column with 2 rows of buttons with the same size. – miva2 Jan 31 '23 at 17:35
  • 1
    If you know which button is bigger beforehand you can use layout which again requires you to use similar steps but you don't have to use SubComposeLayout and `subCompose` function. But the thing you ask for doesn't exist without triggering another recomposition. Again, if you know which button will be bigger and parent width using Modifier.onSizeChanged you can set other one's width using parent width - other button width – Thracian Jan 31 '23 at 17:38
  • And also you don't have to write code yourself in my answer. Just add your buttons to `SubcomposeRow ` as in demonstration. This is quite easy. And if you think this is complex check out Column or Row source code. You will see how complicated they are compared to this. But usages is as simple as using this layout – Thracian Jan 31 '23 at 17:40
  • Thanks for your explanation! yes the usage is very simple! Just like any other layout. Writing the layout is a bit complex as many things need to be considered and the advantages and drawbacks should be documented for usage in production applications. – miva2 Feb 01 '23 at 09:59
1

If you are interested in both buttons splitting the full space and being equal in width, you can use weight modifier

Row(modifier = modifier.fillMaxWidth(), horizontalArrangement = Arrangement.SpaceEvenly) {
        Button(onClick = { /*TODO*/ }, modifier = Modifier.weight(1f)) {
            Text(text = "Next")
        }

        Button(onClick = { /*TODO*/ }, modifier = Modifier.weight(1f)) {
            Text(text = "Previous")
        }
    }
Ryan M
  • 18,333
  • 31
  • 67
  • 74
0

Weight modifier and intrinsic measurement

Combining the weight modifier (proposed by yonas tatek) with intrinsic measurement solves your problem.

  • Replace in Row arguments Modifier.fillMaxWidth() by Modifier.width(IntrinsicSize.Min),
  • add equal weights to the buttons and
  • apply padding to both buttons or use a spacer.

To get the buttons centered perfectly you should change the way the padding is added.

Row(
    modifier = Modifier.width(IntrinsicSize.Min),
    horizontalArrangement = Arrangement.Center
) {
    Button(
        modifier = Modifier.weight(1f),
        onClick = { }
    ) {
        Text("Short")
    }
    Spacer(
        modifier = Modifier.width(10.dp)
    )
    Button(
        modifier = Modifier.weight(1f),
        onClick = { }
    ) {
        Text("This is a Long")
    }
}
fe60
  • 170
  • 1
  • 7