Johan Reitan
03/28/2022, 11:35 AMRow
component that has two slots for text. Both texts can be of varying length, and I’m having a hard time getting the component to balance the text’s width so that the longest text takes the rest of the available space, and also weigh them equally if both texts are long.
The closest I’ve been able to get is using equal values of Modifier.weight()
, but that does not solve the case where one text is longer than the other. The first image is using weight()
, and the second is what I’m trying to achieve.
Code and more examples in thread 🧵Johan Reitan
03/28/2022, 11:37 AMweight
with this code:
Row(
Modifier
.fillMaxWidth()
.padding(8.dp),
) {
Text(
modifier = Modifier.weight(1f),
text = text1,
)
Spacer(Modifier.width(8.dp))
Text(
modifier = Modifier.weight(1f),
text = text2,
textAlign = TextAlign.End,
)
}
Only the case where both texts are long produce what I’m trying to achieve.Johan Reitan
03/28/2022, 11:40 AMIntrinsicSize
, and that produces the desired result if the first text is short, otherwise the first text takes all the available space:
Row(
Modifier
.fillMaxWidth()
.padding(8.dp),
) {
Text(
modifier = Modifier.width(IntrinsicSize.Max),
text = text1,
)
Spacer(Modifier.weight(1f))
Spacer(Modifier.width(8.dp))
Text(
modifier = Modifier.width(IntrinsicSize.Max),
text = text2,
textAlign = TextAlign.End,
)
}
Johan Reitan
03/28/2022, 11:41 AMSaiedmomen
03/28/2022, 11:47 AMJohan Reitan
03/28/2022, 11:49 AMSaiedmomen
03/28/2022, 12:07 PMAlbert Chang
03/28/2022, 12:10 PMJohan Reitan
03/28/2022, 12:15 PMJohan Reitan
03/28/2022, 2:36 PMLayout(
modifier = Modifier
.fillMaxWidth()
.padding(8.dp),
content = {
Text(
text = text1,
)
Text(
text = text2,
textAlign = TextAlign.End,
)
}
) { measurables, constraints ->
val minIntrinsicWidths = measurables.map { measurable ->
measurable.minIntrinsicWidth(constraints.minHeight)
}
val totalWidth = minIntrinsicWidths.sum().toFloat()
val spacing = 8.dp.roundToPx()
val spacingCompensation = (measurables.size - 1) * spacing / measurables.size
val placeables = measurables.mapIndexed { index, measurable ->
measurable.measure(
Constraints.fixedWidth((minIntrinsicWidths[index] / totalWidth * constraints.maxWidth).roundToInt() - spacingCompensation)
)
}
layout(constraints.maxWidth, placeables.maxOf { it.height }) {
var xPosition = 0
placeables.forEachIndexed { index, placeable ->
if (index > 0) {
xPosition += spacing
}
placeable.placeRelative(x = xPosition, y = 0)
xPosition += placeable.width
}
}
}
Johan Reitan
03/29/2022, 9:55 AMConstraintLayout(
modifier = Modifier
.fillMaxWidth()
.padding(8.dp),
) {
val (title, value) = createRefs()
val chain =
createHorizontalChain(title, value, chainStyle = ChainStyle.SpreadInside)
constrain(chain) {
start.linkTo(parent.start)
end.linkTo(parent.end)
}
Text(
modifier = Modifier.constrainAs(title) {
top.linkTo(<http://parent.top|parent.top>)
width = Dimension.preferredWrapContent.atLeast(100.dp)
},
text = text1,
)
Text(
modifier = Modifier
.constrainAs(value) {
top.linkTo(<http://parent.top|parent.top>)
width = Dimension.preferredWrapContent
}
.padding(start = 8.dp),
text = text2,
)
}
The only problem with this solution is that Dimension.preferredWrapContent
doesn’t seem to work as intended. If both texts are long, the second text seems to take precedence, squeezing the first text away, instead of weighing them equally. I added .atLeast(100.dp)
so that it doesn’t completely disappear.Albert Chang
03/29/2022, 10:53 AMLayout(content = {
Text(text = "Text1")
Text(text = "Text2", textAlign = TextAlign.Right)
}) { measurables, constraints ->
val first = measurables[0]
val second = measurables[1]
val firstWidth = first.maxIntrinsicWidth(constraints.maxHeight)
val secondWidth = second.maxIntrinsicWidth(constraints.maxHeight)
val totalWidth = constraints.maxWidth - 8.dp.roundToPx()
val halfWidth = totalWidth / 2
val firstConstraints: Constraints
val secondConstraints: Constraints
if ((firstWidth <= halfWidth && secondWidth <= halfWidth) ||
(firstWidth > halfWidth && secondWidth > halfWidth)
) {
firstConstraints = constraints.copy(minWidth = halfWidth, maxWidth = halfWidth)
secondConstraints = firstConstraints
} else if (firstWidth > halfWidth) {
firstConstraints = constraints.copy(
minWidth = totalWidth - secondWidth,
maxWidth = totalWidth - secondWidth
)
secondConstraints = constraints.copy(
minWidth = secondWidth,
maxWidth = secondWidth
)
} else {
firstConstraints = constraints.copy(
minWidth = firstWidth,
maxWidth = firstWidth
)
secondConstraints = constraints.copy(
minWidth = totalWidth - firstWidth,
maxWidth = totalWidth - firstWidth
)
}
val firstPlaceable = first.measure(firstConstraints)
val secondPlaceable = second.measure(secondConstraints)
layout(constraints.maxWidth, max(firstPlaceable.height, secondPlaceable.height)) {
firstPlaceable.placeRelative(0, 0)
secondPlaceable.placeRelative(constraints.maxWidth - secondPlaceable.width, 0)
}
}
Johan Reitan
03/29/2022, 11:02 AMStylianos Gakis
04/21/2022, 7:10 AMTextAlign
into the second one and have to remember to use it on the call site which would look something like this:
HorizontalTextsWithMaximumSpaceTaken(
startText = {
Text(
text = startText,
style = MaterialTheme.typography.h5,
)
},
endText = { textAlign ->
CompositionLocalProvider(LocalContentAlpha provides ContentAlpha.medium) {
Text(
text = endText,
style = MaterialTheme.typography.h6,
textAlign = textAlign,
)
}
},
spaceBetween = 10.dp,
)
Also I can’t for the life of me think of a nice name to call this function 😂
@Composable
fun HorizontalTextsWithMaximumSpaceTaken(
startText: @Composable () -> Unit,
endText: @Composable (textAlign: TextAlign) -> Unit,
modifier: Modifier = Modifier,
spaceBetween: Dp = 0.dp,
) {
Layout(
content = {
startText()
endText(textAlign = TextAlign.End)
},
modifier = modifier,
) { measurables, constraints ->
val first = measurables[0]
val second = measurables[1]
val firstWidth = first.maxIntrinsicWidth(constraints.maxHeight)
val secondWidth = second.maxIntrinsicWidth(constraints.maxHeight)
val totalWidth = constraints.maxWidth
val halfWidth = totalWidth / 2
val centerSpace = spaceBetween.roundToPx()
val halfCenterSpace = (spaceBetween / 2).roundToPx()
val halfWidthMinusSpace = halfWidth - halfCenterSpace
val firstConstraints: Constraints
val secondConstraints: Constraints
val textsShouldShareEqualSpace =
(firstWidth <= halfWidthMinusSpace && secondWidth <= halfWidthMinusSpace) ||
(firstWidth > halfWidthMinusSpace && secondWidth > halfWidthMinusSpace)
if (textsShouldShareEqualSpace) {
firstConstraints = constraints.copy(minWidth = halfWidthMinusSpace, maxWidth = halfWidthMinusSpace)
secondConstraints = firstConstraints
} else if (firstWidth > halfWidthMinusSpace) {
firstConstraints = constraints.copy(
minWidth = totalWidth - secondWidth - halfCenterSpace,
maxWidth = totalWidth - secondWidth - halfCenterSpace,
)
secondConstraints = constraints.copy(
minWidth = secondWidth,
maxWidth = secondWidth,
)
} else {
firstConstraints = constraints.copy(
minWidth = firstWidth,
maxWidth = firstWidth,
)
secondConstraints = constraints.copy(
minWidth = totalWidth - firstWidth - halfCenterSpace,
maxWidth = totalWidth - firstWidth - halfCenterSpace,
)
}
val firstPlaceable = first.measure(firstConstraints)
val secondPlaceable = second.measure(secondConstraints)
layout(constraints.maxWidth, max(firstPlaceable.height, secondPlaceable.height)) {
firstPlaceable.placeRelative(0, 0)
secondPlaceable.placeRelative(constraints.maxWidth - secondPlaceable.width, 0)
}
}
}