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

androidx.ui.foundation.gestures

Classes

DragDirection

Draggable Direction specifies the direction in which you can drag an draggable or scrollable.

ScrollableState

State of the scrollable composable.

ZoomableState

State of the zoomable composable modifier.

Top-level functions summary

ScrollableState
ScrollableState(onScrollDeltaConsumptionRequested: (Float) -> Float)

Create ScrollableState for scrollable with default FlingConfig and AnimationClockObservable

ZoomableState
ZoomableState(onZoomDelta: (Float) -> Unit)

Create ZoomableState with default AnimationClockObservable.

Extension functions summary

For Modifier
Modifier
Modifier.draggable(dragDirection: DragDirection, onDragStarted: (startedPosition: Offset) -> Unit = {}, onDragStopped: (velocity: Float) -> Unit = {}, enabled: Boolean = true, interactionState: InteractionState? = null, startDragImmediately: Boolean = false, onDragDeltaConsumptionRequested: (Float) -> Float)

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

Modifier
Modifier.scrollable(dragDirection: DragDirection, scrollableState: ScrollableState, onScrollStarted: (startedPosition: Offset) -> Unit = {}, onScrollStopped: (velocity: Float) -> Unit = {}, enabled: Boolean = true)

Enable scrolling and flinging of the modified UI element.

Modifier
Modifier.zoomable(zoomableState: ZoomableState, onZoomStopped: () -> Unit = null)

Enable zooming of the modified UI element.

Modifier
Modifier.zoomable(onZoomStopped: () -> Unit = null, onZoomDelta: (Float) -> Unit)

Enable zooming of the modified UI element.

Top-level functions

ScrollableState

@Composable fun ScrollableState(onScrollDeltaConsumptionRequested: (Float) -> Float): ScrollableState

Create ScrollableState for scrollable with default FlingConfig and AnimationClockObservable

Parameters
onScrollDeltaConsumptionRequested: (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

ZoomableState

@Composable fun ZoomableState(onZoomDelta: (Float) -> Unit): ZoomableState

Create ZoomableState 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(
    dragDirection: DragDirection,
    onDragStarted: (startedPosition: Offset) -> Unit = {},
    onDragStopped: (velocity: Float) -> Unit = {},
    enabled: Boolean = true,
    interactionState: InteractionState? = null,
    startDragImmediately: Boolean = false,
    onDragDeltaConsumptionRequested: (Float) -> Float
): Modifier

Configure touch dragging for the UI element in a single DragDirection. The drag distance is reported to onDragDeltaConsumptionRequested 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.state
import androidx.ui.foundation.Box
import androidx.ui.foundation.gestures.draggable
import androidx.ui.layout.offset
import androidx.ui.layout.preferredSize
import androidx.ui.layout.preferredWidth

// Draw a seekbar-like composable that has a black background
// with a red square that moves along the 300.dp drag distance
val squareSize = 50.dp
val max = 300.dp
val min = 0.dp
val (minPx, maxPx) = with(DensityAmbient.current) {
    min.toPx() to max.toPx()
}
// this is the  state we will update while dragging
var position by state { 0f }

// seekbar itself
Box(
    modifier = Modifier
        .preferredWidth(max + squareSize)
        .draggable(dragDirection = DragDirection.Horizontal) { delta ->
            // consume only delta that needed if we hit bounds
            val old = position
            position = (position + delta).coerceIn(minPx, maxPx)
            position - old
        },
    backgroundColor = Color.Black
) {
    val xOffset = with(DensityAmbient.current) { position.toDp() }
    Box(
        Modifier.offset(x = xOffset, y = 0.dp).preferredSize(squareSize),
        backgroundColor = Color.Red
    )
}
AnimatedFloat offers a standard implementation of flinging behavior:
import androidx.ui.animation.animatedFloat
import androidx.ui.foundation.Box
import androidx.ui.foundation.animation.AnchorsFlingConfig
import androidx.ui.foundation.animation.fling
import androidx.ui.foundation.gestures.draggable
import androidx.ui.layout.offset
import androidx.ui.layout.preferredSize
import androidx.ui.layout.preferredWidth

// Draw a seekbar-like composable that has a black background
// with a red square that moves along the 300.dp drag distance
// and only can take 3 positions:  min, max and in the middle
val squareSize = 50.dp
val max = 300.dp
val min = 0.dp
val (minPx, maxPx) = with(DensityAmbient.current) {
    min.toPx() to max.toPx()
}
// define anchors (final position) and fling behavior to go to these anchors after drag
val anchors = listOf(minPx, maxPx, maxPx / 2)
val flingConfig = AnchorsFlingConfig(anchors)
// define and set up animatedFloat as our dragging state
val position = animatedFloat(0f)
position.setBounds(minPx, maxPx)

// seekbar itself
Box(
    modifier = Modifier.preferredWidth(max + squareSize).draggable(
        startDragImmediately = position.isRunning,
        dragDirection = DragDirection.Horizontal,
        // launch fling with velocity to animate to the closes anchor
        onDragStopped = { position.fling(flingConfig, it) }
    ) { delta ->
        position.snapTo(position.value + delta)
        delta // consume all delta no matter the bounds to avoid nested dragging (as example)
    },
    backgroundColor = Color.Black
) {
    val xOffset = with(DensityAmbient.current) { position.value.toDp() }
    Box(
        Modifier.offset(x = xOffset, y = 0.dp).preferredSize(squareSize),
        backgroundColor = Color.Red
    )
}
Parameters
dragDirection: DragDirection direction in which drag should be happening
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
enabled: Boolean = true whether or not drag is enabled
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.
onDragDeltaConsumptionRequested: (Float) -> Float callback to be invoked when drag occurs. Users must update their state in this lambda and return amount of delta consumed

scrollable

fun Modifier.scrollable(
    dragDirection: DragDirection,
    scrollableState: ScrollableState,
    onScrollStarted: (startedPosition: Offset) -> Unit = {},
    onScrollStopped: (velocity: Float) -> Unit = {},
    enabled: Boolean = true
): Modifier

Enable scrolling and flinging of the modified UI element.

Although ScrollableState is required for this composable to work correctly, users of this composable should own, update and reflect their own state. When constructing ScrollableState, you must provide a ScrollableState.onScrollDeltaConsumptionRequested lambda, which will be invoked every time with the delta in pixels when scroll is happening (by gesture input, by smooth scrolling or flinging). In this lambda callers should update their own state and reflect it on UI. The amount of scrolling delta consumed must be returned from this lambda.

import androidx.compose.state
import androidx.ui.foundation.Box
import androidx.ui.foundation.Text
import androidx.ui.foundation.gestures.ScrollableState
import androidx.ui.foundation.gestures.scrollable
import androidx.ui.layout.preferredSize
import androidx.ui.text.TextStyle

// actual composable state
val offset = state { 0f }
// state for Scrollable, describes how to consume scrolling delta and update offset
Box(
    Modifier
        .preferredSize(200.dp)
        .scrollable(
            dragDirection = DragDirection.Vertical,
            scrollableState = ScrollableState { delta ->
                offset.value = offset.value + delta
                delta
            }
        ),
    backgroundColor = Color.LightGray,
    gravity = ContentGravity.Center
) {
    Text(offset.value.roundToInt().toString(), style = TextStyle(fontSize = 50.sp))
}
Parameters
dragDirection: DragDirection axis to scroll alongside
scrollableState: ScrollableState ScrollableState object that holds internal state of this Scrollable, invokes ScrollableState.onScrollDeltaConsumptionRequested callback and provides smooth scrolling capabilities
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
enabled: Boolean = true whether of not scrolling in enabled

zoomable

@Composable fun Modifier.zoomable(
    zoomableState: ZoomableState,
    onZoomStopped: () -> Unit = null
): Modifier

Enable zooming of the modified UI element.

ZoomableState.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.state
import androidx.ui.core.clipToBounds
import androidx.ui.core.drawLayer
import androidx.ui.foundation.Box
import androidx.ui.foundation.Text
import androidx.ui.foundation.clickable
import androidx.ui.foundation.drawBorder
import androidx.ui.foundation.gestures.ZoomableState
import androidx.ui.foundation.gestures.zoomable
import androidx.ui.layout.fillMaxSize
import androidx.ui.layout.preferredSize

Box(
    Modifier.preferredSize(700.dp).clipToBounds(),
    backgroundColor = Color.LightGray
) {
    var scale by state(StructurallyEqual) { 1f }
    val zoomableState = ZoomableState { scale *= it }

    Box(
        Modifier
            .zoomable(zoomableState)
            .clickable(
                indication = null,
                onDoubleClick = { zoomableState.smoothScaleBy(4f) },
                onClick = {}
            )
            .fillMaxSize()
            .drawBorder(1.dp, Color.Green),
        gravity = ContentGravity.Center
    ) {
        Text(
            "☠",
            fontSize = 32.sp,
            modifier = Modifier.drawLayer(scaleX = scale, scaleY = scale)
        )
    }
}
Parameters
zoomableState: ZoomableState ZoomableState object that holds the internal state of this zoomable, and provides smooth scaling capabilities.
onZoomStopped: () -> Unit = null callback to be invoked when zoom has stopped.

zoomable

@Composable fun Modifier.zoomable(
    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.state
import androidx.ui.core.clipToBounds
import androidx.ui.core.drawLayer
import androidx.ui.foundation.Box
import androidx.ui.foundation.Text
import androidx.ui.foundation.clickable
import androidx.ui.foundation.drawBorder
import androidx.ui.foundation.gestures.ZoomableState
import androidx.ui.foundation.gestures.zoomable
import androidx.ui.layout.fillMaxSize
import androidx.ui.layout.preferredSize

Box(
    Modifier.preferredSize(700.dp).clipToBounds(),
    backgroundColor = Color.LightGray
) {
    var scale by state(StructurallyEqual) { 1f }
    val zoomableState = ZoomableState { scale *= it }

    Box(
        Modifier
            .zoomable(zoomableState)
            .clickable(
                indication = null,
                onDoubleClick = { zoomableState.smoothScaleBy(4f) },
                onClick = {}
            )
            .fillMaxSize()
            .drawBorder(1.dp, Color.Green),
        gravity = ContentGravity.Center
    ) {
        Text(
            "☠",
            fontSize = 32.sp,
            modifier = Modifier.drawLayer(scaleX = scale, scaleY = scale)
        )
    }
}
Parameters
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.