Skip to content

Instantly share code, notes, and snippets.

@vganin
Last active April 11, 2024 06:31
Show Gist options
  • Star 21 You must be signed in to star a gist
  • Fork 2 You must be signed in to fork a gist
  • Save vganin/081b535f0c21b6fcf43c7a71bde91bdd to your computer and use it in GitHub Desktop.
Save vganin/081b535f0c21b6fcf43c7a71bde91bdd to your computer and use it in GitHub Desktop.
Jetpack Compose simple flex-wrap container
@Composable
fun FlowRow(
horizontalGap: Dp = 0.dp,
verticalGap: Dp = 0.dp,
alignment: Alignment.Horizontal = Alignment.Start,
content: @Composable () -> Unit,
) = Layout(content = content) { measurables, constraints ->
val horizontalGapPx = horizontalGap.toPx().roundToInt()
val verticalGapPx = verticalGap.toPx().roundToInt()
val rows = mutableListOf<Row>()
var rowConstraints = constraints
var rowPlaceables = mutableListOf<Placeable>()
measurables.forEach { measurable ->
val placeable = measurable.measure(Constraints())
if (placeable.measuredWidth !in rowConstraints.minWidth..rowConstraints.maxWidth) {
rows += Row(rowPlaceables, horizontalGapPx)
rowConstraints = constraints
rowPlaceables = mutableListOf()
}
val consumedWidth = placeable.measuredWidth + horizontalGapPx
rowConstraints = rowConstraints.offset(horizontal = -consumedWidth)
rowPlaceables.add(placeable)
}
rows += Row(rowPlaceables, horizontalGapPx)
val width = constraints.maxWidth
val height = (rows.sumBy { row -> row.height } + (rows.size - 1) * verticalGapPx)
.coerceAtMost(constraints.maxHeight)
layout(width, height) {
var y = 0
rows.forEach { row ->
val offset = alignment.align(row.width, width, layoutDirection)
var x = offset
row.placeables.forEach { placeable ->
placeable.placeRelative(x, y)
x += placeable.width + horizontalGapPx
}
y += row.height + verticalGapPx
}
}
}
private class Row(
val placeables: List<Placeable>,
val horizontalGapPx: Int,
) {
val width by lazy(mode = LazyThreadSafetyMode.NONE) {
placeables.sumBy { it.width } + (placeables.size - 1) * horizontalGapPx
}
val height by lazy(mode = LazyThreadSafetyMode.NONE) {
placeables.maxOfOrNull { it.height } ?: 0
}
}
@Composable
private fun Preview(alignment: Alignment.Horizontal) {
Box(Modifier.width(100.dp)) {
FlowRow(
horizontalGap = 8.dp,
verticalGap = 8.dp,
alignment = alignment,
) {
repeat(17) { index ->
Text(text = index.toString())
}
}
}
}
@Preview
@Composable
private fun PreviewAlignStart() = Preview(alignment = Alignment.Start)
@Preview
@Composable
private fun PreviewAlignCenter() = Preview(alignment = Alignment.CenterHorizontally)
@Preview
@Composable
private fun PreviewAlignEnd() = Preview(alignment = Alignment.End)
@austinevick
Copy link

I want to say that I am highly grateful to you for this solution. I spent over 8hrs looking for a custom wrap widget in Compose not untill I found your repo. Thank you very much for building this solution.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment