scrollIndicator

A modifier that draws and handles interactions for a scroll indicator (e.g., a scrollbar) defined

scrollIndicator

Compose Modifier

Common
fun Modifier.scrollIndicator(
    factory: ScrollIndicatorFactory,
    state: ScrollIndicatorState,
    orientation: Orientation,
): Modifier

A modifier that draws and handles interactions for a scroll indicator (e.g., a scrollbar) defined by the provided factory.

The ScrollIndicatorFactory is responsible for creating the UI and behavior of the indicator, while the state provides the necessary data, like scroll offset and content size, to correctly represent the scroll position.

Parameters

factoryThe ScrollIndicatorFactory that creates the scroll indicator.
stateThe ScrollIndicatorState for the scrollable component.
orientationThe scrolling orientation of the container. The indicator will be drawn and interact based on this orientation.

Code Examples

VisualScrollbarSample

@Composable
fun VisualScrollbarSample() {
    // A basic implementation of ScrollIndicator that draws a simple visual scrollbar.
    data class BasicVisualScrollbarFactory(
        val thumbThickness: Dp = 8.dp,
        val padding: Dp = 4.dp,
        val thumbColor: Color = Color.Gray,
        val thumbAlpha: Float = 0.5f,
    ) : ScrollIndicatorFactory {
        // The node is the core of the ScrollIndicator, handling the drawing logic.
        override fun createNode(
            state: ScrollIndicatorState,
            orientation: Orientation,
        ): DelegatableNode {
            return object : Modifier.Node(), DrawModifierNode {
                override fun ContentDrawScope.draw() {
                    // Draw the original content.
                    drawContent()
                    // Don't draw the scrollbar if the content fits within the viewport.
                    if (state.contentSize <= state.viewportSize) return
                    val visibleContentRatio = state.viewportSize.toFloat() / state.contentSize
                    // Calculate the thumb's size and position along the scrolling axis.
                    val thumbLength = state.viewportSize * visibleContentRatio
                    val thumbPosition = state.scrollOffset * visibleContentRatio
                    val thumbThicknessPx = thumbThickness.toPx()
                    val paddingPx = padding.toPx()
                    // Determine the scrollbar size and thumb position based on the orientation.
                    val (topLeft, size) =
                        when (orientation) {
                            Orientation.Vertical -> {
                                val x = size.width - thumbThicknessPx - paddingPx
                                Offset(x, thumbPosition) to Size(thumbThicknessPx, thumbLength)
                            }
                            Orientation.Horizontal -> {
                                val y = size.height - thumbThicknessPx - paddingPx
                                Offset(thumbPosition, y) to Size(thumbLength, thumbThicknessPx)
                            }
                        }
                    // Draw the scrollbar thumb.
                    drawRect(color = thumbColor, topLeft = topLeft, size = size, alpha = thumbAlpha)
                }
            }
        }
    }
    val scrollState = rememberScrollState()
    val scrollbarFactory = remember { BasicVisualScrollbarFactory() }
    val scrollbarModifier =
        scrollState.scrollIndicatorState?.let {
            Modifier.scrollIndicator(
                factory = scrollbarFactory,
                state = it,
                orientation = Orientation.Vertical,
            )
        } ?: Modifier
    Column(modifier = Modifier.fillMaxSize().then(scrollbarModifier).verticalScroll(scrollState)) {
        repeat(50) { Text(text = "Item ${it + 1}", modifier = Modifier.padding(8.dp)) }
    }
}