Scroll modifiers
The verticalScroll and horizontalScroll modifiers provide the simplest way to allow the user to scroll an element when the bounds of its contents are larger than its maximum size constraints. With the verticalScroll and horizontalScroll modifiers you don't need to translate or offset the contents.
@Composable private fun ScrollBoxes() { Column( modifier = Modifier .background(Color.LightGray) .size(100.dp) .verticalScroll(rememberScrollState()) ) { repeat(10) { Text("Item $it", modifier = Modifier.padding(2.dp)) } } }
The ScrollState lets you change the scroll position or get its current state. To create it with default parameters, use rememberScrollState().
@Composable private fun ScrollBoxesSmooth() { // Smoothly scroll 100px on first composition val state = rememberScrollState() LaunchedEffect(Unit) { state.animateScrollTo(100) } Column( modifier = Modifier .background(Color.LightGray) .size(100.dp) .padding(horizontal = 8.dp) .verticalScroll(state) ) { repeat(10) { Text("Item $it", modifier = Modifier.padding(2.dp)) } } }
Scrollable area modifier
The scrollableArea modifier is a fundamental building block for creating custom scrollable containers. It provides a higher-level abstraction over the scrollable modifier, handling common requirements like gesture delta interpretation, content clipping, and overscroll effects.
While scrollableArea is used for custom implementations, you should generally prefer ready-made solutions like verticalScroll, horizontalScroll, or composables like LazyColumn for standard scrolling lists. These higher-level components are simpler for common use cases and are themselves built by using scrollableArea.
Difference between scrollableArea and scrollable modifiers
The main difference between scrollableArea and scrollable lies in how they interpret user scroll gestures:
scrollable(raw delta): The delta directly reflects the physical movement of the user's input (e.g., pointer drag) on the screen.scrollableArea(content-oriented delta): Thedeltais semantically inverted to represent the selected change in the scroll position to make the content appear to move with the user's gesture, which is usually the opposite of the pointer movement.
Think of it like this: scrollable tells you how the pointer moved, while scrollableArea translates that pointer movement into how the content should move within a typical scrollable view. This inversion is why scrollableArea feels more natural when implementing a standard scrollable container.
The following table summarizes the delta signs for common scenarios:
User gesture | delta reported to | delta reported to |
|---|---|---|
Pointer moves UP | Negative | Positive |
Pointer moves DOWN | Positive | Negative |
Pointer moves LEFT | Negative | Positive (Negative for RTL) |
Pointer moves RIGHT | Positive | Negative (Positive for RTL) |
(*) Note on scrollableArea delta sign: The sign of the delta from scrollableArea is not just a simple inversion. It intelligently considers:
- Orientation: Vertical or horizontal.
LayoutDirection: LTR or RTL (especially important for horizontal scrolling).reverseScrollingflag: Whether scroll direction is inverted.
In addition to inverting the scroll delta, scrollableArea also clips the content to the bounds of the layout and handles the rendering of overscroll effects. By default, it uses the effect provided by LocalOverscrollFactory. You can customize or disable this by using the scrollableArea overload that accepts an OverscrollEffect parameter.
When to use scrollableArea modifier
You should use the scrollableArea modifier when you need to build a custom scrolling component that isn't adequately served by the horizontalScroll or verticalScroll modifiers or Lazy layouts. This often involves cases with:
- Custom layout logic: When the arrangement of items changes dynamically based on the scroll position.
- Unique visual effects: Applying transformations, scaling, or other effects to children as they scroll.
- Direct control: Needing fine-grained control over the scrolling mechanics beyond what
verticalScrollor Lazy layouts expose.
Create custom wheel-like lists using scrollableArea
The following sample demonstrates using scrollableArea to build a custom vertical list where items scale down as they move away from the center, creating a "wheel-like" visual effect. This kind of scroll-dependent transformation is a perfect use case for scrollableArea.
scrollableArea.
@Composable private fun ScrollableAreaSample() { // ... Layout( modifier = Modifier .size(150.dp) .scrollableArea(scrollState, Orientation.Vertical) .background(Color.LightGray), // ... ) { measurables, constraints -> // ... // Update the maximum scroll value to not scroll beyond limits and stop when scroll // reaches the end. scrollState.maxValue = (totalHeight - viewportHeight).coerceAtLeast(0) // Position the children within the layout. layout(constraints.maxWidth, viewportHeight) { // The current vertical scroll position, in pixels. val scrollY = scrollState.value val viewportCenterY = scrollY + viewportHeight / 2 var placeableLayoutPositionY = 0 placeables.forEach { placeable -> // This sample applies a scaling effect to items based on their distance // from the center, creating a wheel-like effect. // ... // Place the item horizontally centered with a layer transformation for // scaling to achieve wheel-like effect. placeable.placeRelativeWithLayer( x = constraints.maxWidth / 2 - placeable.width / 2, // Offset y by the scroll position to make placeable visible in the viewport. y = placeableLayoutPositionY - scrollY, ) { scaleX = scaleFactor scaleY = scaleFactor } // Move to the next item's vertical position. placeableLayoutPositionY += placeable.height } } } } // ...
Scrollable modifier
The scrollable modifier differs from the scroll modifiers in that scrollable detects the scroll gestures and captures the deltas, but does not offset its contents automatically. This is instead delegated to the user through ScrollableState , which is required for this modifier to work correctly.
When constructing ScrollableState you must provide a consumeScrollDelta function which will be invoked on each scroll step (by gesture input, smooth scrolling or flinging) with the delta in pixels. This function must return the amount of scrolling distance consumed, to ensure the event is properly propagated in cases where there are nested elements that have the scrollable modifier.
The following snippet detects the gestures and displays a numerical value for an offset, but does not offset any elements:
@Composable private fun ScrollableSample() { // actual composable state var offset by remember { mutableFloatStateOf(0f) } Box( Modifier .size(150.dp) .scrollable( orientation = Orientation.Vertical, // Scrollable state: describes how to consume // scrolling delta and update offset state = rememberScrollableState { delta -> offset += delta delta } ) .background(Color.LightGray), contentAlignment = Alignment.Center ) { Text(offset.toString()) } }
Recommended for you
- Note: link text is displayed when JavaScript is off
- Understand gestures
- Migrate
CoordinatorLayoutto Compose - Using Views in Compose