2

I have a row with 2 text items. One text is longer than another (I don't know, which one). How to create a row, so these two items will have the same width? I also need the whole row to have the minimum width, so I cannot simply use Modifier.weight() for text items because in this case the row is expanded to the maximum available width.

Here is an example. The text "Character search" is longer, but I want both text items to have the same width.

enter image description here

UPD: I would like to avoid such solutions as display a row, calculate max width of items, and do recomposition. Is there a way to avoid unnecessary recompositions by using intrinsic measurements for example?

0

2 Answers 2

2

To get rid of unnecessary recompositions, we need to just move our width calculations into stage of placement of these objects

enter image description here

@Preview @Composable fun DoubleButtonPreview() { DoubleButton( text1 = "Smart search", onClick1 = {}, text2 = "Character search", onClick2 = {}, chosenIndex = 0 ) } @Composable fun DoubleButton( text1: String, onClick1: () -> Unit, text2: String, onClick2: () -> Unit, chosenIndex: Int ) { require(chosenIndex in listOf(0, 1)) val strokeWidth = 1.5.dp val mainShape = RoundedCornerShape(4.dp) Layout( modifier = Modifier.border(strokeWidth, Color.Black, mainShape), content = { DoubleButtonContent(text1, onClick1, text2, onClick2, chosenIndex, strokeWidth, mainShape) }, measurePolicy = { measurables, constraints -> val (buttonWidth, buttonHeight) = calculateButtonsWidthAndHeight(measurables, constraints) val placeables = listOf( measurables[0].measure(Constraints.fixed(buttonWidth, buttonHeight)), measurables[1].measure(Constraints.fixedHeight(buttonHeight)), measurables[2].measure(Constraints.fixed(buttonWidth, buttonHeight)) ) layout( width = buttonWidth * 2 + strokeWidth.roundToPx(), height = buttonHeight, placementBlock = { placeButtonsAndSeparator(buttonWidth, placeables) } ) } ) } private fun Placeable.PlacementScope.placeButtonsAndSeparator( buttonWidth: Int, placeables: List<Placeable> ) { var start = 0 val firstSpacesWidth = (buttonWidth - placeables[0].width) / 2 val secondSpacesWidth = (buttonWidth - placeables[2].width) / 2 start += firstSpacesWidth placeables[0].place(start, 0) start += placeables[0].width + firstSpacesWidth placeables[1].place(start, 0) start += placeables[1].width start += secondSpacesWidth placeables[2].place(start, 0) } private fun calculateButtonsWidthAndHeight( measurables: List<Measurable>, constraints: Constraints ): Pair<Int, Int> { val subButtons = measurables.let { listOf(it[0], it[2]) } val subButtonsWidths = subButtons.map { it.maxIntrinsicWidth(constraints.maxHeight) } val maxSubButtonWidth = subButtonsWidths.maxOrNull()!! val buttonHeight = subButtons.first().minIntrinsicHeight(maxSubButtonWidth) return Pair(maxSubButtonWidth, buttonHeight) } @Composable private fun DoubleButtonContent( text1: String, onClick1: () -> Unit, text2: String, onClick2: () -> Unit, chosenIndex: Int, strokeWidth: Dp, mainShape: RoundedCornerShape ) { val verticalTextButtonPadding = 8.dp val horizontalTextButtonPadding = 6.dp Box( modifier = Modifier .background( color = if (chosenIndex == 0) MaterialTheme.colors.primary.copy(alpha = 0.15f) else MaterialTheme.colors.surface, shape = mainShape.copy(topEnd = CornerSize(0.dp), bottomEnd = CornerSize(0.dp)) ) ) { Text( text = text1, color = if (chosenIndex == 0) MaterialTheme.colors.primary else MaterialTheme.colors.onSurface, modifier = Modifier .onClick(onClick = onClick1) .padding(horizontalTextButtonPadding, verticalTextButtonPadding) .align(Alignment.Center) ) } Box( modifier = Modifier .fillMaxHeight() .width(strokeWidth) .background(Color.Black) ) Box( modifier = Modifier .background( color = if (chosenIndex == 1) MaterialTheme.colors.primary.copy(alpha = 0.15f) else MaterialTheme.colors.surface, shape = mainShape.copy(topEnd = CornerSize(0.dp), bottomEnd = CornerSize(0.dp)) ) ) { Text( text = text2, color = if (chosenIndex == 1) MaterialTheme.colors.primary else MaterialTheme.colors.onSurface, modifier = Modifier .onClick(onClick = onClick2) .padding(horizontalTextButtonPadding, verticalTextButtonPadding) .align(Alignment.Center) ) } } 
Sign up to request clarification or add additional context in comments.

1 Comment

Very thanks. I just needed to create a custom layout.
2

I don't know is there exists pre-made modifier for this, my reliable solution is to calculate maximum minimum width and assign it to both items at recompose.

var item1WidthPx by remember { mutableStateOf(0) } var item2WidthPx by remember { mutableStateOf(0) } val maxItemWidth = max(item1WidthPx, item2WidthPx) .let { with(LocalDensity.current) { it.toDp() } } Row(modifier = Modifer.wrapContentWidth()) { Item1( modifier = Modifier .onPlaced { item1WidthPx = it.size.width } .widthIn(min = maxItemWidth) ) Item2( modifier = Modifier .onPlaced { item2WidthPx = it.size.width } .widthIn(min = maxItemWidth) ) } 

When I did this at my projects, the recomposition happened so quickly that it was not visible that all sizes adjusted so much (carefully, if you have a complex structure with a huge number of so adjustable elements, this problem may still surface, but for this a double button all will be ok)

1 Comment

I would like to avoid unnecessary recompositions

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.