Google is committed to advancing racial equity for Black communities. See how.

androidx.compose.foundation.gestures

Classes

ScrollableController

Controller to control the scrollable modifier with.

ZoomableController

Controller to control zoomable modifier with.

Top-level functions summary

ScrollableController
rememberScrollableController(consumeScrollDelta: (Float) -> Float)

Create and remember ScrollableController for scrollable with default FlingConfig and AnimationClockObservable

ZoomableController
rememberZoomableController(onZoomDelta: (Float) -> Unit)

Create and remember ZoomableController with default AnimationClockObservable.

Extension functions summary

For Modifier
Modifier
Modifier.draggable(orientation: Orientation, enabled: Boolean = true, reverseDirection: Boolean = false, interactionState: InteractionState? = null, startDragImmediately: Boolean = false, canDrag: (Direction) -> Boolean = { enabled }, onDragStarted: (startedPosition: Offset) -> Unit = {}, onDragStopped: (velocity: Float) -> Unit = {}, onDrag: Density.(Float) -> Unit)

Configure touch dragging for the UI element in a single Orientation.

Modifier
Modifier.scrollable(orientation: Orientation, controller: ScrollableController, enabled: Boolean = true, reverseDirection: Boolean = false, canScroll: (Direction) -> Boolean = { enabled }, onScrollStarted: (startedPosition: Offset) -> Unit = {}, onScrollStopped: (velocity: Float) -> Unit = {})

Configure touch scrolling and flinging for the UI element in a single Orientation.

Modifier
Modifier.zoomable(controller: ZoomableController, enabled: Boolean = true, onZoomStarted: () -> Unit = null, onZoomStopped: () -> Unit = null)

Enable zooming of the modified UI element.

Modifier
Modifier.zoomable(enabled: Boolean = true, onZoomStarted: () -> Unit = null, onZoomStopped: () -> Unit = null, onZoomDelta: (Float) -> Unit)

Enable zooming of the modified UI element.

Top-level functions

rememberScrollableController

@Composable fun rememberScrollableController(consumeScrollDelta: (Float) -> Float): ScrollableController

Create and remember ScrollableController for scrollable with default FlingConfig and AnimationClockObservable

Parameters
consumeScrollDelta: (Float) -> Float callback invoked when scrollable drag/fling/smooth scrolling occurs. The callback receives the delta in pixels. Callers should update their state in this lambda and return amount of delta consumed

rememberZoomableController

@Composable fun rememberZoomableController(onZoomDelta: (Float) -> Unit): ZoomableController

Create and remember ZoomableController with default AnimationClockObservable.

Parameters
onZoomDelta: (Float) -> Unit callback to be invoked when pinch/smooth zooming occurs. The callback receives the delta as the ratio of the new size compared to the old. Callers should update their state and UI in this callback.

Extension functions

draggable

fun Modifier.draggable(
    orientation: Orientation,
    enabled: Boolean = true,
    reverseDirection: Boolean = false,
    interactionState: InteractionState? = null,
    startDragImmediately: Boolean = false,
    canDrag: (Direction) -> Boolean = { enabled },
    onDragStarted: (startedPosition: Offset) -> Unit = {},
    onDragStopped: (velocity: Float) -> Unit = {},
    onDrag: Density.(Float) -> Unit
): Modifier

Configure touch dragging for the UI element in a single Orientation. The drag distance is reported to onDrag as a single Float value in pixels.

The common usecase for this component is when you need to be able to drag something inside the component on the screen and represent this state via one float value

If you need to control the whole dragging flow, consider using dragGestureFilter instead.

If you are implementing scroll/fling behavior, consider using scrollable.

import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.background
import androidx.compose.foundation.gestures.draggable
import androidx.compose.foundation.layout.offsetPx
import androidx.compose.foundation.layout.preferredSize
import androidx.compose.foundation.layout.preferredWidth
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember

// Draw a seekbar-like composable that has a black background
// with a red square that moves along the 300.dp drag distance
val max = 300.dp
val min = 0.dp
// this is the  state we will update while dragging
val offsetPosition = remember { mutableStateOf(0f) }

// seekbar itself
Box(
    modifier = Modifier
        .preferredWidth(max)
        .draggable(orientation = Orientation.Horizontal) { delta ->
            val newValue = offsetPosition.value + delta
            offsetPosition.value = newValue.coerceIn(min.toPx(), max.toPx())
        }
        .background(Color.Black)
) {
    Box(
        Modifier.offsetPx(x = offsetPosition).preferredSize(50.dp).background(Color.Red)
    )
}
Parameters
orientation: Orientation orientation of the drag
enabled: Boolean = true whether or not drag is enabled
reverseDirection: Boolean = false reverse the direction of the scroll, so top to bottom scroll will behave like bottom to top and left to right will behave like right to left.
interactionState: InteractionState? = null InteractionState that will be updated when this draggable is being dragged, using Interaction.Dragged.
startDragImmediately: Boolean = false when set to true, draggable will start dragging immediately and prevent other gesture detectors from reacting to "down" events (in order to block composed press-based gestures). This is intended to allow end users to "catch" an animating widget by pressing on it. It's useful to set it when value you're dragging is settling / animating.
canDrag: (Direction) -> Boolean = { enabled } callback to indicate whether or not dragging is allowed for given Direction
onDragStarted: (startedPosition: Offset) -> Unit = {} callback that will be invoked when drag has been started after touch slop has been passed, with starting position provided
onDragStopped: (velocity: Float) -> Unit = {} callback that will be invoked when drag stops, with velocity provided
onDrag: Density.(Float) -> Unit callback to be invoked when the drag occurs with the delta dragged from the previous event. Density provided in the scope for the convenient conversion between px and dp

scrollable

fun Modifier.scrollable(
    orientation: Orientation,
    controller: ScrollableController,
    enabled: Boolean = true,
    reverseDirection: Boolean = false,
    canScroll: (Direction) -> Boolean = { enabled },
    onScrollStarted: (startedPosition: Offset) -> Unit = {},
    onScrollStopped: (velocity: Float) -> Unit = {}
): Modifier

Configure touch scrolling and flinging for the UI element in a single Orientation.

Users should update their state via ScrollableController.consumeScrollDelta and reflect their own state in UI when using this component.

ScrollableController is required for this modifier to work correctly. When constructing ScrollableController, you must provide a ScrollableController.consumeScrollDelta lambda, which will be invoked whenever scroll happens (by gesture input, by smooth scrolling or flinging) with the delta in pixels. The amount of scrolling delta consumed must be returned from this lambda to ensure proper nested scrolling.

import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.Text
import androidx.compose.foundation.background
import androidx.compose.foundation.gestures.rememberScrollableController
import androidx.compose.foundation.gestures.scrollable
import androidx.compose.foundation.layout.preferredSize
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.ui.text.TextStyle

// actual composable state
val offset = remember { mutableStateOf(0f) }
// state for Scrollable, describes how to consume scrolling delta and update offset
Box(
    Modifier
        .preferredSize(200.dp)
        .scrollable(
            orientation = Orientation.Vertical,
            controller = rememberScrollableController { delta ->
                offset.value = offset.value + delta
                delta
            }
        )
        .background(Color.LightGray),
    alignment = Alignment.Center
) {
    Text(offset.value.roundToInt().toString(), style = TextStyle(fontSize = 50.sp))
}
Parameters
orientation: Orientation orientation of the scrolling
controller: ScrollableController ScrollableController object that is responsible for redirecting scroll deltas to ScrollableController.consumeScrollDelta callback and provides smooth scrolling capabilities
enabled: Boolean = true whether or not scrolling in enabled
reverseDirection: Boolean = false reverse the direction of the scroll, so top to bottom scroll will behave like bottom to top and left to right will behave like right to left.
canScroll: (Direction) -> Boolean = { enabled } callback to indicate whether or not scroll is allowed for given Direction
onScrollStarted: (startedPosition: Offset) -> Unit = {} callback to be invoked when scroll has started from the certain position on the screen
onScrollStopped: (velocity: Float) -> Unit = {} callback to be invoked when scroll stops with amount of velocity unconsumed provided

zoomable

fun Modifier.zoomable(
    controller: ZoomableController,
    enabled: Boolean = true,
    onZoomStarted: () -> Unit = null,
    onZoomStopped: () -> Unit = null
): Modifier

Enable zooming of the modified UI element.

ZoomableController.onZoomDelta will be invoked with the change in proportion of the UI element's size at each change in either ratio of the gesture or smooth scaling. Callers should update their state and UI in this callback.

import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.Text
import androidx.compose.foundation.background
import androidx.compose.foundation.border
import androidx.compose.foundation.clickable
import androidx.compose.foundation.gestures.rememberZoomableController
import androidx.compose.foundation.gestures.zoomable
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.preferredSize
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.ui.draw.clipToBounds
import androidx.compose.ui.drawLayer

Box(
    Modifier.preferredSize(300.dp).clipToBounds().background(Color.LightGray)
) {
    var scale by remember { mutableStateOf(1f) }
    val zoomableController = rememberZoomableController { scale *= it }
    Box(
        Modifier
            .zoomable(zoomableController)
            .clickable(
                indication = null,
                onDoubleClick = { zoomableController.smoothScaleBy(4f) },
                onClick = {}
            )
            .fillMaxSize()
            .border(1.dp, Color.Green),
        alignment = Alignment.Center
    ) {
        Text(
            "☠",
            fontSize = 32.sp,
            modifier = Modifier.drawLayer(scaleX = scale, scaleY = scale)
        )
    }
}
Parameters
controller: ZoomableController ZoomableController object that holds the internal state of this zoomable, and provides smooth scaling capabilities.
enabled: Boolean = true whether zooming by gestures is enabled or not
onZoomStarted: () -> Unit = null callback to be invoked when zoom has started.
onZoomStopped: () -> Unit = null callback to be invoked when zoom has stopped.

zoomable

fun Modifier.zoomable(
    enabled: Boolean = true,
    onZoomStarted: () -> Unit = null,
    onZoomStopped: () -> Unit = null,
    onZoomDelta: (Float) -> Unit
): Modifier

Enable zooming of the modified UI element.

onZoomDelta will be invoked with the change in proportion of the UI element's size at each change in either position of the gesture or smooth scaling. Callers should update their state and UI in this callback.

import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.Text
import androidx.compose.foundation.background
import androidx.compose.foundation.border
import androidx.compose.foundation.clickable
import androidx.compose.foundation.gestures.rememberZoomableController
import androidx.compose.foundation.gestures.zoomable
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.preferredSize
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.ui.draw.clipToBounds
import androidx.compose.ui.drawLayer

Box(
    Modifier.preferredSize(300.dp).clipToBounds().background(Color.LightGray)
) {
    var scale by remember { mutableStateOf(1f) }
    val zoomableController = rememberZoomableController { scale *= it }
    Box(
        Modifier
            .zoomable(zoomableController)
            .clickable(
                indication = null,
                onDoubleClick = { zoomableController.smoothScaleBy(4f) },
                onClick = {}
            )
            .fillMaxSize()
            .border(1.dp, Color.Green),
        alignment = Alignment.Center
    ) {
        Text(
            "☠",
            fontSize = 32.sp,
            modifier = Modifier.drawLayer(scaleX = scale, scaleY = scale)
        )
    }
}
Parameters
enabled: Boolean = true whether zooming by gestures is enabled or not
onZoomStarted: () -> Unit = null callback to be invoked when zoom has started.
onZoomStopped: () -> Unit = null callback to be invoked when zoom has stopped.
onZoomDelta: (Float) -> Unit callback to be invoked when pinch/smooth zooming occurs. The callback receives the delta as the ratio of the new size compared to the old. Callers should update their state and UI in this callback.