New Compose Multiplatform components arrived on Composables UICheck it out →

Modifier in Compose Foundation

draggable2D

Common

Configure touch dragging for the UI element in both orientations. The drag distance reported to [Draggable2DState], allowing users to react to the drag delta and update their state.

The common 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 are implementing dragging in a single orientation, consider using [draggable].

Last updated:

Installation

dependencies {
   implementation("androidx.compose.foundation:foundation:1.7.0-beta04")
}

Overloads

@Stable
@ExperimentalFoundationApi
fun Modifier.draggable2D(
    state: Draggable2DState,
    enabled: Boolean = true,
    interactionSource: MutableInteractionSource? = null,
    startDragImmediately: Boolean = false,
    onDragStarted: (startedPosition: Offset) -> Unit = NoOpOnDragStart,
    onDragStopped: (velocity: Velocity) -> Unit = NoOpOnDragStop,
    reverseDirection: Boolean = false
)

Parameters

namedescription
state[Draggable2DState] state of the draggable2D. Defines how drag events will be interpreted by the user land logic.
enabledwhether or not drag is enabled
interactionSource[MutableInteractionSource] that will be used to emit [DragInteraction.Start] when this draggable is being dragged.
startDragImmediatelywhen set to true, draggable2D 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.
onDragStartedcallback that will be invoked when drag is about to start at the starting position, allowing user to perform preparation for drag.
onDragStoppedcallback that will be invoked when drag is finished, allowing the user to react on velocity and process it.
reverseDirectionreverse 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.
@Stable
@ExperimentalFoundationApi
@Deprecated(
    "Please use overload without the suspend onDragStarted onDragStopped and callbacks",
    level = DeprecationLevel.HIDDEN

fun Modifier.draggable2D(
    state: Draggable2DState,
    enabled: Boolean = true,
    interactionSource: MutableInteractionSource? = null,
    startDragImmediately: Boolean = false,
    onDragStarted: suspend CoroutineScope.(startedPosition: Offset) -> Unit = NoOpOnDragStarted,
    onDragStopped: suspend CoroutineScope.(velocity: Velocity) -> Unit = NoOpOnDragStopped,
    reverseDirection: Boolean = false
)

Parameters

namedescription
state[Draggable2DState] state of the draggable2D. Defines how drag events will be interpreted by the user land logic.
enabledwhether or not drag is enabled
interactionSource[MutableInteractionSource] that will be used to emit [DragInteraction.Start] when this draggable is being dragged.
startDragImmediatelywhen set to true, draggable2D 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.
onDragStartedcallback that will be invoked when drag is about to start at the starting position, allowing user to suspend and perform preparation for drag, if desired.This suspend function is invoked with the draggable2D scope, allowing for async processing, if desired. Note that the scope used here is the one provided by the draggable2D node, for long-running work that needs to outlast the modifier being in the composition you should use a scope that fits the lifecycle needed.
onDragStoppedcallback that will be invoked when drag is finished, allowing the user to react on velocity and process it. This suspend function is invoked with the draggable2D scope, allowing for async processing, if desired. Note that the scope used here is the one provided by the draggable2D scope, for long-running work that needs to outlast the modifier being in the composition you should use a scope that fits the lifecycle needed.
reverseDirectionreverse 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.

Code Example

Draggable2DSample

@Composable
@Sampled
@OptIn(ExperimentalFoundationApi::class
fun Draggable2DSample() {
    // Draw a box that has a a grey background
    // with a red square that moves along 300.dp dragging in both directions
    val max = 200.dp
    val min = 0.dp
    val (minPx, maxPx) = with(LocalDensity.current) { min.toPx() to max.toPx() }
    // this is the offset we will update while dragging
    var offsetPositionX by remember { mutableStateOf(0f) }
    var offsetPositionY by remember { mutableStateOf(0f) }

    Box(
        modifier = Modifier
            .width(max)
            .height(max)
            .draggable2D(
                state = rememberDraggable2DState { delta ->
                    val newValueX = offsetPositionX + delta.x
                    val newValueY = offsetPositionY + delta.y
                    offsetPositionX = newValueX.coerceIn(minPx, maxPx)
                    offsetPositionY = newValueY.coerceIn(minPx, maxPx)
                }
            )
            .background(Color.LightGray)
    ) {
        Box(
            Modifier
                .offset {
                    IntOffset(
                        offsetPositionX.roundToInt(),
                        offsetPositionY.roundToInt()
                    )
                }
                .size(50.dp)
                .background(Color.Red)
        )
    }
}