I have a Row, which contains "Spacer - Box - Spacer - Box - etc" inside. All Boxes have same width and backgound with rounded corners. But when there isn't enough space left, the last Box is clipped and due to it's rounded background it looks like its width differs from other boxes.
I've tried to use requiredWidth instead of width, but in this case the box overlaps other boxes and spacers (requiredWidth centers the box). The only thing that helps, is adding horizontal scroll. But i don't need that view to be scrollable. Is there any way to achieve the desired result?
Some pictures for better explanation.
Default Composables or any Composable that is not assigned Modifier.clipToBounds()
, Modifier.clip
or Modifier.graphicsLayer{clip}
do not clip content. You can draw anything outside a Composable using draw Modifiers for instance when no clip is set, it also applies changing position with Modifier.offset or Modifier.layout, etc.
What happens is Composables are measured with Constraints and Row and Column uses
var usedSpace = 0
for(measurable in measurable list) {measurable->
measurable.measure(maxWith=constraints.maxWidth/Height - usedSpace)
usedSpace += placeable.width/height
}
Because of that when there is no avilable space last child Composable might shrink down to 0.dp or 0 px.
When you assign Modifier.requiredWidth()
you change maxWidth of Constraints to any value you assign but when layout width of parent Composable doesn't match its Constraints width it tries to center content. You can finde more detailed explanation about it in this answer.
If you don't want to center you can use Modifier.wrapContentWidth(alignment=Start, unbounded=true)
unBounded
param increases Constraints maxWidth to Constraints.Infinity
and content can be measured with their desired width and alignment sets its position instead of center.
You can also use Modifier.layout
same way wrapContentWidth does, under the hood every size Modifier calls measurement and layout()
.
I post 2 alternatives, you can comment out wrapContentWidth to see they both act same. And inside layout modifier you can inspect which Constraints.maxWith comes to next child and how and with what width they are set.
@Preview
@Composable
private fun LayoutPlacingTest() {
Column(
modifier = Modifier.fillMaxSize().padding(20.dp)
) {
Row(
modifier = Modifier
.border(4.dp, Color.Gray)
.width(300.dp)
.background(Color.Yellow)
) {
repeat(3) { index ->
Box(
modifier = Modifier
// .wrapContentWidth(align = Alignment.Start, unbounded = true)
.layout { measurable, constraints ->
val placeable = measurable.measure(
constraints.copy(maxWidth = Constraints.Infinity)
)
println("placeable: ${placeable.width}, Constraints: $constraints")
layout(placeable.width, placeable.height) {
val x = ((placeable.width - constraints.maxWidth) / 2).coerceAtLeast(0)
placeable.placeRelative(x, 0)
}
}
.size(100.dp)
.background(Color.Red, RoundedCornerShape(16.dp))
)
if (index != 2) {
Spacer(Modifier.width(20.dp))
}
}
}
}
}