Dmitry Zaytsev
05/08/2026, 6:53 AMColumn with attachments, a Flow with reactions, etc.
Taking the case of a Flow with reactions: I use onGlobalPositioned on the last reaction to get its coordinates, store them in a MutableState, and compare them with the status width. This works somehow but triggers a double layout pass because onGlobalPositioned fires only after placement.
Is there a way to get the coordinates of non-direct children in a single layout pass without rewriting standard Composables?Jonathan
05/08/2026, 3:02 PMLayout ? You’d be able to directly query the sizes of all children/grand-children and allows you to perform Pixel perfect placement.Alex Vanyo
05/08/2026, 5:33 PMLayout, and maybe with some additional tools like alignment lines.
that goes into some examples including the badge use case at 11:05 - full code is hereDmitry Zaytsev
05/08/2026, 9:37 PMFlowRow {
reactions.forEachIndexed { index, item ->
Reaction(
reaction = item,
)
}
}
To determine whether there is enough space on the last line of reactions for the status I need coordinates of the last Reaction. However, this information only becomes available only after place is called in the custom Layout.Jonathan
05/08/2026, 9:41 PMDmitry Zaytsev
05/08/2026, 9:59 PMFlowRow is depicted in green and the Reactions in blue. The bubble layout only knows the bounds of the FlowRow, but to place the status correctly I need the coordinates of Reaction #6.Jonathan
05/08/2026, 10:10 PMAlex Vanyo
05/08/2026, 10:25 PMDmitry Zaytsev
05/09/2026, 7:08 AMDmitry Zaytsev
05/13/2026, 5:47 AMFlowRowAlex Vanyo
05/16/2026, 1:20 AMFlowRow - alignment lines can propagate up through other layouts. Something like this seems close to what you're looking for:
private val LastItemTop = HorizontalAlignmentLine(::minOf)
private val LastItemBottom = HorizontalAlignmentLine(::maxOf)
private val LastItemEnd = VerticalAlignmentLine(::maxOf)
@Preview(widthDp = 100)
@Preview(widthDp = 120)
@Preview(widthDp = 140)
@Preview(widthDp = 160)
@Preview(widthDp = 180)
@Preview(widthDp = 200)
@Composable
internal fun FlowRowWithBadge(modifier: Modifier = Modifier) {
Layout(
modifier = modifier.fillMaxWidth(),
content = {
FlowRow(
modifier = Modifier.fillMaxWidth(),
horizontalArrangement = Arrangement.spacedBy(8.dp),
verticalArrangement = Arrangement.spacedBy(8.dp),
) {
repeat(10) { index ->
val isLast = index == 9
Button(
onClick = {},
contentPadding = PaddingValues(0.dp),
modifier = Modifier
.size(32.dp)
.then(
if (isLast) {
Modifier.layout { measurable, constraints ->
val placeable = measurable.measure(constraints)
layout(
placeable.width,
placeable.height,
alignmentLines = mapOf(
LastItemTop to 0,
LastItemBottom to placeable.height,
LastItemEnd to placeable.width,
),
) {
placeable.place(0, 0)
}
}
} else {
Modifier
}
),
) {
Text(text = "$index", style = MaterialTheme.typography.bodySmall)
}
}
}
Text(
text = "Status: Online",
modifier = Modifier
.background(Color.Green)
.padding(horizontal = 8.dp, vertical = 4.dp),
style = MaterialTheme.typography.labelLarge,
)
},
) { measurables, constraints ->
val flowRowMeasurable = measurables[0]
val badgeMeasurable = measurables[1]
val flowRowPlaceable = flowRowMeasurable.measure(constraints)
val badgePlaceable = badgeMeasurable.measure(constraints.copy(minWidth = 0))
val lastItemTop = flowRowPlaceable[LastItemTop]
val lastItemBottom = flowRowPlaceable[LastItemBottom]
val lastItemEnd = flowRowPlaceable[LastItemEnd]
val spacing = 8.dp.roundToPx()
val canFitOnLastLine = if (lastItemBottom != AlignmentLine.Unspecified && lastItemEnd != AlignmentLine.Unspecified) {
val remainingWidth = constraints.maxWidth - lastItemEnd - spacing
remainingWidth >= badgePlaceable.width
} else {
false
}
val width = constraints.maxWidth
val height = if (canFitOnLastLine) {
max(flowRowPlaceable.height, lastItemTop + badgePlaceable.height)
} else {
flowRowPlaceable.height + spacing + badgePlaceable.height
}
layout(width, height) {
flowRowPlaceable.place(0, 0)
if (canFitOnLastLine) {
badgePlaceable.place(
x = width - badgePlaceable.width,
y = max(lastItemTop, lastItemBottom - badgePlaceable.height),
)
} else {
badgePlaceable.place(
x = width - badgePlaceable.width,
y = flowRowPlaceable.height + spacing,
)
}
}
}
}