Custom Layouts

Custom Layouts with the Layout Composable

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:

ui/LayoutDemos.kt: a wrapping layout in three stepsKotlin
@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:

Output of 101
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.