androidkotlinandroid-jetpack-composeandroid-flexboxlayout

FlexboxLayout Jetpack Compose any options?


In XML way I had Google's FlexBoxLayout but last commit there was 2 years ago, does anyone know best way to do it in Jetpack Compose, or better a library of any kind?


Solution

  • Found that great solution, which works fine in my case:

    @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.sumOf { 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)
    

    Result for my scenario:

    Result for my scenario