https://kotlinlang.org logo
#compose
Title
# compose
s

sindrenm

02/08/2023, 4:36 PM
Perhaps I'm completely overthinking this, but I'm trying to make a
Row
-like that only places the items it can actually fully fit, and that “reports back” on the number of items it was able to place. I came up with something like what I have in the thread, which works great for the fitting part, but it doesn't seem to call the callback with the correct number. I'm sure it's something obvious. Any ideas?
Copy code
@Composable
fun FittingRow(
    modifier: Modifier = Modifier,
    itemSpacing: Dp = 0.dp,
    onPlaced: (numberOfItems: Int) -> Unit = {},
    content: @Composable () -> Unit,
) {
    Layout(
        modifier = modifier.wrapContentSize(),
        content = content,
    ) { measurables, constraints ->
        val placeables = measurables.map { it.measure(constraints) }
        val maxWidth = constraints.maxWidth

        layout(maxWidth, placeables.maxOf { it.height }) {
            var xPositionOfNext = 0
            var numberOfPlacedItems = 0

            placeables.forEach {
                if (xPositionOfNext + it.width > maxWidth) {
                    onPlaced(numberOfPlacedItems)
                    return@forEach
                }

                it.place(xPositionOfNext, 0)

                numberOfPlacedItems++
                xPositionOfNext += it.width + itemSpacing.toPx().toInt()
            }
        }
    }
}
The result from calling this with seven circles where only five would fit:
Copy code
@Composable
private fun Items(items: List<Int>, modifier: Modifier = Modifier) {
    Column(
        modifier,
        horizontalAlignment = Alignment.CenterHorizontally,
        verticalArrangement = Arrangement.spacedBy(16.dp),
    ) {
        var placedItems by remember { mutableStateOf(0) }

        FittingRow(itemSpacing = 0.dp, onPlaced = { placedItems = it }) {
            items.forEach { Trophy(it) }
        }

        Text("Placed items: $placedItems")
    }
}
a

Alex Vanyo

02/08/2023, 4:57 PM
I think you’re seeing a recomposition loop: https://developer.android.com/jetpack/compose/phases#recomp-loop A preview will render just the first composition, which composes using the initial value of
placedItems
as
0
. If you run that example on a device, you’ll like see
Placed items: 0
for a single frame, and then the updated value on the next frame. That one frame delay is generally something you want to avoid, so I’m curious to hear what
onPlaced
is being used for
s

sindrenm

02/08/2023, 8:36 PM
Hmm, no, it doesn't seem to work on device, either, actually, even after several frames of waiting. Interestingly, though, updates from Live Edit actually does seem to update the number, but our users are not likely to use Live Edit that much. 😅
The number is used to determine whether we should show a “See all” button outside of the row. If we can fit all the elements inside the row, then we don't want the button, but if we can't, then we want to send the users to a page where they can see the rest.
s

sindrenm

02/09/2023, 7:46 AM
Hmm, so from what I can tell, the main difference there is that they make a list of only the placeables that will fit first, then loop through only those and place them, then do the callback with the number of items in the list. Worth trying out when I get to the office. Thanks!
Oh yeah, and it makes the width of the layout the width of all the placeables, too, which is also a good idea. 👍
This seems to have done the trick, actually! Thanks a bunch! 😁
3 Views