Timo Drick
03/12/2025, 8:57 AMZach Klippenstein (he/him) [MOD]
03/12/2025, 3:11 PMTimo Drick
03/12/2025, 5:24 PMTimo Drick
03/12/2025, 5:29 PM@Composable
fun SmartScrollableFlowRow(
    modifier: Modifier = Modifier,
    horizontalPadding: Dp = 4.dp,
    verticalPadding: Dp = 4.dp,
    scrollState: ScrollState,
    content: @Composable () -> Unit
) {
    Layout(
        modifier = modifier.horizontalScroll(scrollState),
        content = {
            content()
        },
    ) { measurables, constraints ->
        val paddingX = horizontalPadding.roundToPx()
        val paddingY = verticalPadding.roundToPx()
        var largestChildWidth = 0
        val placeables = measurables.map { measurable ->
            val placeable = measurable.measure(constraints)
            largestChildWidth = max(largestChildWidth, placeable.width)
            placeable
        }
        val width = max(scrollState.viewportSize, largestChildWidth)
        var posX = 0
        var posY = 0
        var maxRowHeight = 0
        var maxRowWidth = 0
        var heightSum = 0
        val offset = placeables.map {
            if (posX > 0 && (posX + it.width) > width) {
                //Next row
                posX = 0
                posY += paddingY + maxRowHeight
                maxRowHeight = 0
            }
            val pos = IntOffset(posX, posY)
            maxRowHeight = max(maxRowHeight, it.height)
            maxRowWidth = max(maxRowWidth, posX + it.width)
            heightSum = posY + it.height
            posX += it.width + paddingX
            pos
        }
        layout(maxRowWidth, heightSum) {
            placeables.forEachIndexed { index, placeable ->
                placeable.place(offset[index])
            }
        }
    }
}Timo Drick
03/12/2025, 6:35 PMTimo Drick
03/13/2025, 11:50 PMfun Modifier.intrinsicScrollModifier(scrollState: ScrollState) = horizontalScroll(scrollState) then object : LayoutModifier {
    override fun MeasureScope.measure(
        measurable: Measurable,
        constraints: Constraints
    ): MeasureResult {
        val intrinsic = measurable.minIntrinsicWidth(constraints.maxHeight)
        val maxViewport = max(constraints.minWidth, scrollState.viewportSize)
        val innerConstraints = constraints.copy(
            maxWidth = max(maxViewport, intrinsic)
        )
        val placeable = measurable.measure(innerConstraints)
        val width = max(placeable.width, intrinsic)
        return layout(width, placeable.height) {
            placeable.place(0, 0)
        }
    }
}Timo Drick
03/13/2025, 11:51 PMTimo Drick
03/13/2025, 11:51 PMval scrollState = rememberScrollState()
FlowRow(
    modifier = Modifier.fillMaxWidth()
        .intrinsicScrollModifier(scrollState)
) {
    repeat(5) {
        Text(
            modifier = Modifier.requiredSize(180.dp, 50.dp)
                .background(Color.LightGray),
            text = "Test $it"
        )
    }
}