Layout exposes the measure-and-place pass: the lambda gets the children as measurables and the parent's constraints, measures each child, and reports its size in layout(width, height) { }, where it places them. TagFlow wraps genre chips onto new lines:
@Composable
fun TagFlow(modifier: Modifier = Modifier, gap: Int = 24, content: @Composable () -> Unit) {
Layout(content, modifier) { measurables, constraints ->
val loose = constraints.copy(minWidth = 0, minHeight = 0)
val placeables = measurables.map { it.measure(loose) } // 1. measure children once
var x = 0; var y = 0; var rowHeight = 0
val positions = placeables.map { p -> // 2. decide positions
if (x > 0 && x + p.width > constraints.maxWidth) { // no room: wrap
x = 0; y += rowHeight + gap; rowHeight = 0
}
val pos = x to y
x += p.width + gap; rowHeight = maxOf(rowHeight, p.height)
pos
}
Log.d("BookNestUi", "TagFlow: ${placeables.size} chips, height ${y + rowHeight}px")
layout(constraints.maxWidth, y + rowHeight) { // 3. report size, place
placeables.forEachIndexed { i, p -> p.place(positions[i].first, positions[i].second) }
}
}
}Positions are in pixels. Measuring a child twice throws, which keeps layout linear; for sizes needed up front, use intrinsics (Modifier.height(IntrinsicSize.Min)) or SubcomposeLayout. Foundation already has FlowRow; this version shows the mechanics. With six chips, logcat reports:
D/BookNestUi: TagFlow: 6 chips, height 276px
Two rows of 126 px (the chips' 48 dp minimum touch height at 2.625 px per dp) plus the 24 px gap; the chips are at the bottom of the figure in ConstraintLayout in Compose.