Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Android Jetpack Compose - ChipGroup with limited number of rows

There is requirements to create ChipGroup with maximum lines/rows allowed, for all items that are not shown there should be last chip with text like "+3 more items".

I have tried FlowRow from accompanist, which works fine, but number of rows cannot be limited there (or I don't know how to :))

Another option I have tried is to implement custom layout. I have managed to limit number of rows (implementation similar to FlowRow with additional conditions), but I'm not sure how to append last item from layout(...placementBlock: Placeable.PlacementScope.() -> Unit)

Any help is appreciated

like image 934
bagy94 Avatar asked Oct 25 '25 04:10

bagy94


1 Answers

In such cases SubcomposeLayout should be used: it allows you to insert a composable depending on already measured views.

Applying this to FlowRow would take more time, because of many other arguments, so I've took my own simplified variant as the base.

@Composable
fun ChipVerticalGrid(
    modifier: Modifier = Modifier,
    spacing: Dp,
    moreItemsView: @Composable (Int) -> Unit,
    content: @Composable () -> Unit,
) {
    SubcomposeLayout(
        modifier = modifier
    ) { constraints ->
        val contentConstraints = constraints.copy(minWidth = 0, minHeight = 0)
        var currentRow = 0
        var currentOrigin = IntOffset.Zero
        val spacingValue = spacing.toPx().toInt()
        val mainMeasurables = subcompose("content", content)
        val placeables = mutableListOf<Pair<Placeable, IntOffset>>()
        for (i in mainMeasurables.indices) {
            val measurable = mainMeasurables[i]
            val placeable = measurable.measure(contentConstraints)

            fun Placeable.didOverflowWidth() =
                currentOrigin.x > 0f && currentOrigin.x + width > contentConstraints.maxWidth

            if (placeable.didOverflowWidth()) {
                currentRow += 1
                val nextRowOffset = currentOrigin.y + placeable.height + spacingValue

                if (nextRowOffset + placeable.height > contentConstraints.maxHeight) {
                    var morePlaceable: Placeable
                    do {
                        val itemsLeft = mainMeasurables.count() - placeables.count()

                        morePlaceable = subcompose(itemsLeft) {
                            moreItemsView(itemsLeft)
                        }[0].measure(contentConstraints)
                        val didOverflowWidth = morePlaceable.didOverflowWidth()
                        if (didOverflowWidth) {
                            val removed = placeables.removeLast()
                            currentOrigin = removed.second
                        }
                    } while (didOverflowWidth)
                    placeables.add(morePlaceable to currentOrigin)
                    break
                }
                currentOrigin = currentOrigin.copy(x = 0, y = nextRowOffset)
            }

            placeables.add(placeable to currentOrigin)
            currentOrigin = currentOrigin.copy(x = currentOrigin.x + placeable.width + spacingValue)
        }
        layout(
            width = maxOf(constraints.minWidth, placeables.maxOfOrNull { it.first.width + it.second.x } ?: 0),
            height = maxOf(constraints.minHeight, placeables.lastOrNull()?.run { first.height + second.y } ?: 0),
        ) {
            placeables.forEach {
                val (placeable, origin) = it
                placeable.place(origin.x, origin.y)
            }
        }
    }
}

Usage:

val words = LoremIpsum().values.first().split(" ").map { it.filter { it.isLetter()} }
val itemView = @Composable { text: String ->
    Text(
        text,
        modifier = Modifier
            .background(color = Color.Gray, shape = CircleShape)
            .padding(vertical = 3.dp, horizontal = 5.dp)
    )
}

ChipVerticalGrid(
    spacing = 7.dp,
    moreItemsView = {
        itemView("$it more items")
    },
    modifier = Modifier
        .fillMaxWidth()
        .height(200.dp)
        .padding(7.dp)
) {
    words.forEach { word ->
        itemView(word)
    }
}

Result:

like image 180
Philip Dukhov Avatar answered Oct 26 '25 18:10

Philip Dukhov



Donate For Us

If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!