{% setvar book_path %}/reference/androidx/_book.yaml{% endsetvar %} {% include "_shared/_reference-head-tags.html" %}

AwaitPointerEventScope

@RestrictsSuspension
public interface AwaitPointerEventScope extends Density


Receiver scope for awaiting pointer events in a call to PointerInputScope.awaitPointerEventScope.

This is a restricted suspension scope. Code in this scope is always called un-dispatched and may only suspend for calls to awaitPointerEvent. These functions resume synchronously and the caller may mutate the result before the next await call to affect the next stage of the input processing pipeline.

Summary

Public methods

abstract @NonNull PointerEvent

Suspend until a PointerEvent is reported to the specified input pass.

abstract @NonNull PointerEvent

The PointerEvent from the most recent touch event.

default @NonNull Size
abstract @NonNull IntSize

The measured size of the pointer input region.

abstract @NonNull ViewConfiguration

The ViewConfiguration used to tune gesture detectors.

default @NonNull T
<T extends Object> withTimeout(
    long timeMillis,
    @ExtensionFunctionType @NonNull SuspendFunction1<@NonNull AwaitPointerEventScope, @NonNull T> block
)

Runs block and returns its results.

default T
<T extends Object> withTimeoutOrNull(
    long timeMillis,
    @ExtensionFunctionType @NonNull SuspendFunction1<@NonNull AwaitPointerEventScope, @NonNull T> block
)

Runs block and returns the result of block or null if timeMillis has passed before timeMillis.

Extension functions

default final PointerInputChange

Reads pointer input events until a drag is detected or all pointers are up.

default final PointerInputChange

Reads pointer input events until a horizontal drag is detected or all pointers are up.

default final PointerInputChange

Waits for horizontal drag motion to pass touch slop, using pointerId as the pointer to examine.

default final PointerInputChange

Waits for a long press by examining pointerId.

default final PointerInputChange
DragGestureDetectorKt.awaitTouchSlopOrCancellation(
    @NonNull AwaitPointerEventScope receiver,
    @NonNull PointerId pointerId,
    @NonNull Function2<@NonNull PointerInputChange, @NonNull OffsetUnit> onTouchSlopReached
)

Waits for drag motion to pass touch slop, using pointerId as the pointer to examine.

default final PointerInputChange

Reads pointer input events until a vertical drag is detected or all pointers are up.

default final PointerInputChange

Waits for vertical drag motion to pass touch slop, using pointerId as the pointer to examine.

default final boolean
DragGestureDetectorKt.drag(
    @NonNull AwaitPointerEventScope receiver,
    @NonNull PointerId pointerId,
    @NonNull Function1<@NonNull PointerInputChangeUnit> onDrag
)

Reads position change events for pointerId and calls onDrag for every change in position.

default final boolean
DragGestureDetectorKt.horizontalDrag(
    @NonNull AwaitPointerEventScope receiver,
    @NonNull PointerId pointerId,
    @NonNull Function1<@NonNull PointerInputChangeUnit> onDrag
)

Reads horizontal position change events for pointerId and calls onDrag for every change in position.

default final boolean
DragGestureDetectorKt.verticalDrag(
    @NonNull AwaitPointerEventScope receiver,
    @NonNull PointerId pointerId,
    @NonNull Function1<@NonNull PointerInputChangeUnit> onDrag
)

Reads vertical position change events for pointerId and calls onDrag for every change in position.

default final @NonNull PointerInputChange
TapGestureDetectorKt.awaitFirstDown(
    @NonNull AwaitPointerEventScope receiver,
    boolean requireUnconsumed,
    @NonNull PointerEventPass pass
)

Reads events until the first down is received in the given pass.

default final PointerInputChange

Reads events in the given pass until all pointers are up or the gesture was canceled.

Inherited methods

From androidx.compose.ui.unit.Density
abstract float

The logical density of the display.

abstract float

Current user preference for the scaling factor for fonts.

default int

Convert Dp to Int by rounding

default int

Convert Sp to Int by rounding

default @NonNull Dp

Convert Sp to Dp.

default @NonNull Dp
orgKt.toDp(int receiver)

Convert an Int pixel value to Dp.

default @NonNull Dp
orgKt.toDp(float receiver)

Convert a Float pixel value to a Dp

default @NonNull DpSize

Convert a Size to a DpSize.

default float
orgKt.toPx(@NonNull Dp receiver)

Convert Dp to pixels.

default float

Convert Sp to pixels.

default @NonNull Rect

Convert a DpRect to a Rect.

default @NonNull Size

Convert a DpSize to a Size.

default @NonNull TextUnit
orgKt.toSp(@NonNull Dp receiver)

Convert Dp to Sp.

default @NonNull TextUnit
orgKt.toSp(int receiver)

Convert an Int pixel value to Sp.

default @NonNull TextUnit
orgKt.toSp(float receiver)

Convert a Float pixel value to a Sp

Public methods

awaitPointerEvent

abstract @NonNull PointerEvent awaitPointerEvent(@NonNull PointerEventPass pass)

Suspend until a PointerEvent is reported to the specified input pass. pass defaults to PointerEventPass.Main.

awaitPointerEvent resumes synchronously in the restricted suspension scope. This means that callers can react immediately to input after awaitPointerEvent returns and affect both the current frame and the next handler or phase of the input processing pipeline. Callers should mutate the returned PointerEvent before awaiting another event to consume aspects of the event before the next stage of input processing runs.

getCurrentEvent

abstract @NonNull PointerEvent getCurrentEvent()

The PointerEvent from the most recent touch event.

getExtendedTouchPadding

default @NonNull Size getExtendedTouchPadding()

getSize

abstract @NonNull IntSize getSize()

The measured size of the pointer input region. Input events will be reported with a coordinate space of (0, 0) to (size.width, size,height) as the input region, with (0, 0) indicating the upper left corner.

getViewConfiguration

abstract @NonNull ViewConfiguration getViewConfiguration()

The ViewConfiguration used to tune gesture detectors.

withTimeout

default @NonNull T <T extends Object> withTimeout(
    long timeMillis,
    @ExtensionFunctionType @NonNull SuspendFunction1<@NonNull AwaitPointerEventScope, @NonNull T> block
)

Runs block and returns its results. An PointerEventTimeoutCancellationException is thrown if timeMillis has passed before block completes.

withTimeoutOrNull

default T <T extends Object> withTimeoutOrNull(
    long timeMillis,
    @ExtensionFunctionType @NonNull SuspendFunction1<@NonNull AwaitPointerEventScope, @NonNull T> block
)

Runs block and returns the result of block or null if timeMillis has passed before timeMillis.

Extension functions

DragGestureDetectorKt.awaitDragOrCancellation

default final PointerInputChange DragGestureDetectorKt.awaitDragOrCancellation(
    @NonNull AwaitPointerEventScope receiver,
    @NonNull PointerId pointerId
)

Reads pointer input events until a drag is detected or all pointers are up. When the final pointer is raised, the up event is returned. When a drag event is detected, the drag change will be returned. Note that if pointerId has been raised, another pointer that is down will be used, if available, so the returned PointerInputChange.id may differ from pointerId. If the position change in the any direction has been consumed by the PointerEventPass.Main pass, then the drag is considered canceled and null is returned. If pointerId is not down when awaitDragOrCancellation is called, then null is returned.

Example Usage:

import androidx.compose.foundation.background
import androidx.compose.foundation.gestures.awaitDragOrCancellation
import androidx.compose.foundation.gestures.awaitEachGesture
import androidx.compose.foundation.gestures.awaitFirstDown
import androidx.compose.foundation.gestures.awaitTouchSlopOrCancellation
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.offset
import androidx.compose.foundation.layout.size
import androidx.compose.foundation.layout.width
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.ui.Modifier
import androidx.compose.ui.geometry.Offset
import androidx.compose.ui.geometry.Size
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.input.pointer.pointerInput
import androidx.compose.ui.input.pointer.positionChange
import androidx.compose.ui.layout.onSizeChanged
import androidx.compose.ui.unit.IntOffset
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.toSize

val offsetX = remember { mutableStateOf(0f) }
val offsetY = remember { mutableStateOf(0f) }
var size by remember { mutableStateOf(Size.Zero) }
Box(
    Modifier.fillMaxSize()
        .onSizeChanged { size = it.toSize() }
) {
    Box(
        Modifier.offset { IntOffset(offsetX.value.roundToInt(), offsetY.value.roundToInt()) }
            .size(50.dp)
            .background(Color.Blue)
            .pointerInput(Unit) {
                awaitEachGesture {
                    val down = awaitFirstDown()
                    var change = awaitTouchSlopOrCancellation(down.id) { change, over ->
                        val original = Offset(offsetX.value, offsetY.value)
                        val summed = original + over
                        val newValue = Offset(
                            x = summed.x.coerceIn(0f, size.width - 50.dp.toPx()),
                            y = summed.y.coerceIn(0f, size.height - 50.dp.toPx())
                        )
                        change.consume()
                        offsetX.value = newValue.x
                        offsetY.value = newValue.y
                    }
                    while (change != null && change.pressed) {
                        change = awaitDragOrCancellation(change.id)
                        if (change != null && change.pressed) {
                            val original = Offset(offsetX.value, offsetY.value)
                            val summed = original + change.positionChange()
                            val newValue = Offset(
                                x = summed.x.coerceIn(0f, size.width - 50.dp.toPx()),
                                y = summed.y.coerceIn(0f, size.height - 50.dp.toPx())
                            )
                            change.consume()
                            offsetX.value = newValue.x
                            offsetY.value = newValue.y
                        }
                    }
                }
            }
    )
}

DragGestureDetectorKt.awaitHorizontalDragOrCancellation

default final PointerInputChange DragGestureDetectorKt.awaitHorizontalDragOrCancellation(
    @NonNull AwaitPointerEventScope receiver,
    @NonNull PointerId pointerId
)

Reads pointer input events until a horizontal drag is detected or all pointers are up. When the final pointer is raised, the up event is returned. When a drag event is detected, the drag change will be returned. Note that if pointerId has been raised, another pointer that is down will be used, if available, so the returned PointerInputChange.id may differ from pointerId. If the position change has been consumed by the PointerEventPass.Main pass, then the drag is considered canceled and null is returned. If pointerId is not down when awaitHorizontalDragOrCancellation is called, then null is returned.

Example Usage:

import androidx.compose.foundation.background
import androidx.compose.foundation.gestures.awaitEachGesture
import androidx.compose.foundation.gestures.awaitFirstDown
import androidx.compose.foundation.gestures.awaitHorizontalDragOrCancellation
import androidx.compose.foundation.gestures.awaitHorizontalTouchSlopOrCancellation
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.fillMaxHeight
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.offset
import androidx.compose.foundation.layout.width
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.ui.Modifier
import androidx.compose.ui.geometry.Offset
import androidx.compose.ui.geometry.Size
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.input.pointer.pointerInput
import androidx.compose.ui.input.pointer.positionChange
import androidx.compose.ui.layout.onSizeChanged
import androidx.compose.ui.unit.IntOffset
import androidx.compose.ui.unit.dp

val offsetX = remember { mutableStateOf(0f) }
val offsetY = remember { mutableStateOf(0f) }
var width by remember { mutableStateOf(0f) }
Box(
    Modifier.fillMaxSize()
        .onSizeChanged { width = it.width.toFloat() }
) {
    Box(
        Modifier.offset { IntOffset(offsetX.value.roundToInt(), offsetY.value.roundToInt()) }
            .fillMaxHeight()
            .width(50.dp)
            .background(Color.Blue)
            .pointerInput(Unit) {
                awaitEachGesture {
                    val down = awaitFirstDown()
                    var change =
                        awaitHorizontalTouchSlopOrCancellation(down.id) { change, over ->
                            val originalX = offsetX.value
                            val newValue =
                                (originalX + over).coerceIn(0f, width - 50.dp.toPx())
                            change.consume()
                            offsetX.value = newValue
                        }
                    while (change != null && change.pressed) {
                        change = awaitHorizontalDragOrCancellation(change.id)
                        if (change != null && change.pressed) {
                            val originalX = offsetX.value
                            val newValue = (originalX + change.positionChange().x)
                                .coerceIn(0f, width - 50.dp.toPx())
                            change.consume()
                            offsetX.value = newValue
                        }
                    }
                }
            }
    )
}

DragGestureDetectorKt.awaitHorizontalTouchSlopOrCancellation

default final PointerInputChange DragGestureDetectorKt.awaitHorizontalTouchSlopOrCancellation(
    @NonNull AwaitPointerEventScope receiver,
    @NonNull PointerId pointerId,
    @NonNull Function2<@NonNull PointerInputChange, @NonNull FloatUnit> onTouchSlopReached
)

Waits for horizontal drag motion to pass touch slop, using pointerId as the pointer to examine. If pointerId is raised, another pointer from those that are down will be chosen to lead the gesture, and if none are down, null is returned.

onTouchSlopReached is called after ViewConfiguration.touchSlop motion in the horizontal direction with the change that caused the motion beyond touch slop and the pixels beyond touch slop. onTouchSlopReached should consume the position change if it accepts the motion. If it does, then the method returns that PointerInputChange. If not, touch slop detection will continue. If pointerId is not down when awaitHorizontalTouchSlopOrCancellation is called, then null is returned.

import androidx.compose.foundation.background
import androidx.compose.foundation.gestures.awaitEachGesture
import androidx.compose.foundation.gestures.awaitFirstDown
import androidx.compose.foundation.gestures.awaitHorizontalDragOrCancellation
import androidx.compose.foundation.gestures.awaitHorizontalTouchSlopOrCancellation
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.fillMaxHeight
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.offset
import androidx.compose.foundation.layout.width
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.ui.Modifier
import androidx.compose.ui.geometry.Offset
import androidx.compose.ui.geometry.Size
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.input.pointer.pointerInput
import androidx.compose.ui.input.pointer.positionChange
import androidx.compose.ui.layout.onSizeChanged
import androidx.compose.ui.unit.IntOffset
import androidx.compose.ui.unit.dp

val offsetX = remember { mutableStateOf(0f) }
val offsetY = remember { mutableStateOf(0f) }
var width by remember { mutableStateOf(0f) }
Box(
    Modifier.fillMaxSize()
        .onSizeChanged { width = it.width.toFloat() }
) {
    Box(
        Modifier.offset { IntOffset(offsetX.value.roundToInt(), offsetY.value.roundToInt()) }
            .fillMaxHeight()
            .width(50.dp)
            .background(Color.Blue)
            .pointerInput(Unit) {
                awaitEachGesture {
                    val down = awaitFirstDown()
                    var change =
                        awaitHorizontalTouchSlopOrCancellation(down.id) { change, over ->
                            val originalX = offsetX.value
                            val newValue =
                                (originalX + over).coerceIn(0f, width - 50.dp.toPx())
                            change.consume()
                            offsetX.value = newValue
                        }
                    while (change != null && change.pressed) {
                        change = awaitHorizontalDragOrCancellation(change.id)
                        if (change != null && change.pressed) {
                            val originalX = offsetX.value
                            val newValue = (originalX + change.positionChange().x)
                                .coerceIn(0f, width - 50.dp.toPx())
                            change.consume()
                            offsetX.value = newValue
                        }
                    }
                }
            }
    )
}
Returns
PointerInputChange

The PointerInputChange that was consumed in onTouchSlopReached or null if all pointers are raised before touch slop is detected or another gesture consumed the position change.

Example Usage:

DragGestureDetectorKt.awaitLongPressOrCancellation

default final PointerInputChange DragGestureDetectorKt.awaitLongPressOrCancellation(
    @NonNull AwaitPointerEventScope receiver,
    @NonNull PointerId pointerId
)

Waits for a long press by examining pointerId.

If that pointerId is raised (that is, the user lifts their finger), but another finger (PointerId) is down at that time, another pointer will be chosen as the lead for the gesture, and if none are down, null is returned.

import androidx.compose.foundation.BorderStroke
import androidx.compose.foundation.background
import androidx.compose.foundation.border
import androidx.compose.foundation.gestures.awaitEachGesture
import androidx.compose.foundation.gestures.awaitFirstDown
import androidx.compose.foundation.gestures.awaitLongPressOrCancellation
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.size
import androidx.compose.foundation.layout.wrapContentSize
import androidx.compose.material.Text
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.clipToBounds
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.input.pointer.pointerInput
import androidx.compose.ui.unit.dp

var count by remember { mutableStateOf(0) }

Column {
    Text("Long Press to increase count. Long Press count: $count")
    Box(
        Modifier.fillMaxSize()
            .wrapContentSize(Alignment.Center)
            .size(192.dp)
            .pointerInput(Unit) {
                awaitEachGesture {
                    val down = awaitFirstDown(requireUnconsumed = false)
                    awaitLongPressOrCancellation(down.id)?.let {
                        count++
                    }
                }
            }
            .clipToBounds()
            .background(Color.Blue)
            .border(BorderStroke(2.dp, Color.Black))
    )
}
Returns
PointerInputChange

The latest PointerInputChange associated with a long press or null if all pointers are raised before a long press is detected or another gesture consumed the change.

Example Usage:

DragGestureDetectorKt.awaitTouchSlopOrCancellation

default final PointerInputChange DragGestureDetectorKt.awaitTouchSlopOrCancellation(
    @NonNull AwaitPointerEventScope receiver,
    @NonNull PointerId pointerId,
    @NonNull Function2<@NonNull PointerInputChange, @NonNull OffsetUnit> onTouchSlopReached
)

Waits for drag motion to pass touch slop, using pointerId as the pointer to examine. If pointerId is raised, another pointer from those that are down will be chosen to lead the gesture, and if none are down, null is returned. If pointerId is not down when awaitTouchSlopOrCancellation is called, then null is returned.

onTouchSlopReached is called after ViewConfiguration.touchSlop motion in the any direction with the change that caused the motion beyond touch slop and the Offset beyond touch slop that has passed. onTouchSlopReached should consume the position change if it accepts the motion. If it does, then the method returns that PointerInputChange. If not, touch slop detection will continue.

import androidx.compose.foundation.background
import androidx.compose.foundation.gestures.awaitDragOrCancellation
import androidx.compose.foundation.gestures.awaitEachGesture
import androidx.compose.foundation.gestures.awaitFirstDown
import androidx.compose.foundation.gestures.awaitTouchSlopOrCancellation
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.offset
import androidx.compose.foundation.layout.size
import androidx.compose.foundation.layout.width
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.ui.Modifier
import androidx.compose.ui.geometry.Offset
import androidx.compose.ui.geometry.Size
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.input.pointer.pointerInput
import androidx.compose.ui.input.pointer.positionChange
import androidx.compose.ui.layout.onSizeChanged
import androidx.compose.ui.unit.IntOffset
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.toSize

val offsetX = remember { mutableStateOf(0f) }
val offsetY = remember { mutableStateOf(0f) }
var size by remember { mutableStateOf(Size.Zero) }
Box(
    Modifier.fillMaxSize()
        .onSizeChanged { size = it.toSize() }
) {
    Box(
        Modifier.offset { IntOffset(offsetX.value.roundToInt(), offsetY.value.roundToInt()) }
            .size(50.dp)
            .background(Color.Blue)
            .pointerInput(Unit) {
                awaitEachGesture {
                    val down = awaitFirstDown()
                    var change = awaitTouchSlopOrCancellation(down.id) { change, over ->
                        val original = Offset(offsetX.value, offsetY.value)
                        val summed = original + over
                        val newValue = Offset(
                            x = summed.x.coerceIn(0f, size.width - 50.dp.toPx()),
                            y = summed.y.coerceIn(0f, size.height - 50.dp.toPx())
                        )
                        change.consume()
                        offsetX.value = newValue.x
                        offsetY.value = newValue.y
                    }
                    while (change != null && change.pressed) {
                        change = awaitDragOrCancellation(change.id)
                        if (change != null && change.pressed) {
                            val original = Offset(offsetX.value, offsetY.value)
                            val summed = original + change.positionChange()
                            val newValue = Offset(
                                x = summed.x.coerceIn(0f, size.width - 50.dp.toPx()),
                                y = summed.y.coerceIn(0f, size.height - 50.dp.toPx())
                            )
                            change.consume()
                            offsetX.value = newValue.x
                            offsetY.value = newValue.y
                        }
                    }
                }
            }
    )
}
Returns
PointerInputChange

The PointerInputChange that was consumed in onTouchSlopReached or null if all pointers are raised before touch slop is detected or another gesture consumed the position change.

Example Usage:

DragGestureDetectorKt.awaitVerticalDragOrCancellation

default final PointerInputChange DragGestureDetectorKt.awaitVerticalDragOrCancellation(
    @NonNull AwaitPointerEventScope receiver,
    @NonNull PointerId pointerId
)

Reads pointer input events until a vertical drag is detected or all pointers are up. When the final pointer is raised, the up event is returned. When a drag event is detected, the drag change will be returned. Note that if pointerId has been raised, another pointer that is down will be used, if available, so the returned PointerInputChange.id may differ from pointerId. If the position change has been consumed by the PointerEventPass.Main pass, then the drag is considered canceled and null is returned. If pointerId is not down when awaitVerticalDragOrCancellation is called, then null is returned.

Example Usage:

import androidx.compose.foundation.background
import androidx.compose.foundation.gestures.awaitEachGesture
import androidx.compose.foundation.gestures.awaitFirstDown
import androidx.compose.foundation.gestures.awaitVerticalDragOrCancellation
import androidx.compose.foundation.gestures.awaitVerticalTouchSlopOrCancellation
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.offset
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.ui.Modifier
import androidx.compose.ui.geometry.Offset
import androidx.compose.ui.geometry.Size
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.input.pointer.pointerInput
import androidx.compose.ui.input.pointer.positionChange
import androidx.compose.ui.layout.onSizeChanged
import androidx.compose.ui.unit.IntOffset
import androidx.compose.ui.unit.dp

val offsetX = remember { mutableStateOf(0f) }
val offsetY = remember { mutableStateOf(0f) }
var height by remember { mutableStateOf(0f) }
Box(
    Modifier.fillMaxSize()
        .onSizeChanged { height = it.height.toFloat() }
) {
    Box(
        Modifier.offset { IntOffset(offsetX.value.roundToInt(), offsetY.value.roundToInt()) }
            .fillMaxWidth()
            .height(50.dp)
            .background(Color.Blue)
            .pointerInput(Unit) {
                awaitEachGesture {
                    val down = awaitFirstDown()
                    var change =
                        awaitVerticalTouchSlopOrCancellation(down.id) { change, over ->
                            val originalY = offsetY.value
                            val newValue = (originalY + over)
                                .coerceIn(0f, height - 50.dp.toPx())
                            change.consume()
                            offsetY.value = newValue
                        }
                    while (change != null && change.pressed) {
                        change = awaitVerticalDragOrCancellation(change.id)
                        if (change != null && change.pressed) {
                            val originalY = offsetY.value
                            val newValue = (originalY + change.positionChange().y)
                                .coerceIn(0f, height - 50.dp.toPx())
                            change.consume()
                            offsetY.value = newValue
                        }
                    }
                }
            }
    )
}

DragGestureDetectorKt.awaitVerticalTouchSlopOrCancellation

default final PointerInputChange DragGestureDetectorKt.awaitVerticalTouchSlopOrCancellation(
    @NonNull AwaitPointerEventScope receiver,
    @NonNull PointerId pointerId,
    @NonNull Function2<@NonNull PointerInputChange, @NonNull FloatUnit> onTouchSlopReached
)

Waits for vertical drag motion to pass touch slop, using pointerId as the pointer to examine. If pointerId is raised, another pointer from those that are down will be chosen to lead the gesture, and if none are down, null is returned. If pointerId is not down when awaitVerticalTouchSlopOrCancellation is called, then null is returned.

onTouchSlopReached is called after ViewConfiguration.touchSlop motion in the vertical direction with the change that caused the motion beyond touch slop and the pixels beyond touch slop. onTouchSlopReached should consume the position change if it accepts the motion. If it does, then the method returns that PointerInputChange. If not, touch slop detection will continue.

import androidx.compose.foundation.background
import androidx.compose.foundation.gestures.awaitEachGesture
import androidx.compose.foundation.gestures.awaitFirstDown
import androidx.compose.foundation.gestures.awaitVerticalDragOrCancellation
import androidx.compose.foundation.gestures.awaitVerticalTouchSlopOrCancellation
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.offset
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.ui.Modifier
import androidx.compose.ui.geometry.Offset
import androidx.compose.ui.geometry.Size
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.input.pointer.pointerInput
import androidx.compose.ui.input.pointer.positionChange
import androidx.compose.ui.layout.onSizeChanged
import androidx.compose.ui.unit.IntOffset
import androidx.compose.ui.unit.dp

val offsetX = remember { mutableStateOf(0f) }
val offsetY = remember { mutableStateOf(0f) }
var height by remember { mutableStateOf(0f) }
Box(
    Modifier.fillMaxSize()
        .onSizeChanged { height = it.height.toFloat() }
) {
    Box(
        Modifier.offset { IntOffset(offsetX.value.roundToInt(), offsetY.value.roundToInt()) }
            .fillMaxWidth()
            .height(50.dp)
            .background(Color.Blue)
            .pointerInput(Unit) {
                awaitEachGesture {
                    val down = awaitFirstDown()
                    var change =
                        awaitVerticalTouchSlopOrCancellation(down.id) { change, over ->
                            val originalY = offsetY.value
                            val newValue = (originalY + over)
                                .coerceIn(0f, height - 50.dp.toPx())
                            change.consume()
                            offsetY.value = newValue
                        }
                    while (change != null && change.pressed) {
                        change = awaitVerticalDragOrCancellation(change.id)
                        if (change != null && change.pressed) {
                            val originalY = offsetY.value
                            val newValue = (originalY + change.positionChange().y)
                                .coerceIn(0f, height - 50.dp.toPx())
                            change.consume()
                            offsetY.value = newValue
                        }
                    }
                }
            }
    )
}
Returns
PointerInputChange

The PointerInputChange that was consumed in onTouchSlopReached or null if all pointers are raised before touch slop is detected or another gesture consumed the position change.

Example Usage:

DragGestureDetectorKt.drag

default final boolean DragGestureDetectorKt.drag(
    @NonNull AwaitPointerEventScope receiver,
    @NonNull PointerId pointerId,
    @NonNull Function1<@NonNull PointerInputChangeUnit> onDrag
)

Reads position change events for pointerId and calls onDrag for every change in position. If pointerId is raised, a new pointer is chosen from those that are down and if none exist, the method returns. This does not wait for touch slop.

import androidx.compose.foundation.background
import androidx.compose.foundation.gestures.awaitEachGesture
import androidx.compose.foundation.gestures.awaitFirstDown
import androidx.compose.foundation.gestures.awaitTouchSlopOrCancellation
import androidx.compose.foundation.gestures.drag
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.offset
import androidx.compose.foundation.layout.size
import androidx.compose.foundation.layout.width
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.ui.Modifier
import androidx.compose.ui.geometry.Offset
import androidx.compose.ui.geometry.Size
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.input.pointer.pointerInput
import androidx.compose.ui.input.pointer.positionChange
import androidx.compose.ui.layout.onSizeChanged
import androidx.compose.ui.unit.IntOffset
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.toSize

val offsetX = remember { mutableStateOf(0f) }
val offsetY = remember { mutableStateOf(0f) }
var size by remember { mutableStateOf(Size.Zero) }
Box(
    Modifier.fillMaxSize()
        .onSizeChanged { size = it.toSize() }
) {
    Box(
        Modifier.offset { IntOffset(offsetX.value.roundToInt(), offsetY.value.roundToInt()) }
            .size(50.dp)
            .background(Color.Blue)
            .pointerInput(Unit) {
                awaitEachGesture {
                    val down = awaitFirstDown()
                    val change = awaitTouchSlopOrCancellation(down.id) { change, over ->
                        val original = Offset(offsetX.value, offsetY.value)
                        val summed = original + over
                        val newValue = Offset(
                            x = summed.x.coerceIn(0f, size.width - 50.dp.toPx()),
                            y = summed.y.coerceIn(0f, size.height - 50.dp.toPx())
                        )
                        change.consume()
                        offsetX.value = newValue.x
                        offsetY.value = newValue.y
                    }
                    if (change != null) {
                        drag(change.id) {
                            val original = Offset(offsetX.value, offsetY.value)
                            val summed = original + it.positionChange()
                            val newValue = Offset(
                                x = summed.x.coerceIn(0f, size.width - 50.dp.toPx()),
                                y = summed.y.coerceIn(0f, size.height - 50.dp.toPx())
                            )
                            it.consume()
                            offsetX.value = newValue.x
                            offsetY.value = newValue.y
                        }
                    }
                }
            }
    )
}
Returns
boolean

true if the drag completed normally or false if the drag motion was canceled by another gesture detector consuming position change events.

Example Usage:

DragGestureDetectorKt.horizontalDrag

default final boolean DragGestureDetectorKt.horizontalDrag(
    @NonNull AwaitPointerEventScope receiver,
    @NonNull PointerId pointerId,
    @NonNull Function1<@NonNull PointerInputChangeUnit> onDrag
)

Reads horizontal position change events for pointerId and calls onDrag for every change in position. If pointerId is raised, a new pointer is chosen from those that are down and if none exist, the method returns. This does not wait for touch slop.

Example Usage:

import androidx.compose.foundation.background
import androidx.compose.foundation.gestures.awaitEachGesture
import androidx.compose.foundation.gestures.awaitFirstDown
import androidx.compose.foundation.gestures.awaitHorizontalTouchSlopOrCancellation
import androidx.compose.foundation.gestures.horizontalDrag
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.fillMaxHeight
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.offset
import androidx.compose.foundation.layout.width
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.ui.Modifier
import androidx.compose.ui.geometry.Offset
import androidx.compose.ui.geometry.Size
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.input.pointer.pointerInput
import androidx.compose.ui.input.pointer.positionChange
import androidx.compose.ui.layout.onSizeChanged
import androidx.compose.ui.unit.IntOffset
import androidx.compose.ui.unit.dp

val offsetX = remember { mutableStateOf(0f) }
val offsetY = remember { mutableStateOf(0f) }
var width by remember { mutableStateOf(0f) }
Box(
    Modifier.fillMaxSize()
        .onSizeChanged { width = it.width.toFloat() }
) {
    Box(
        Modifier.offset { IntOffset(offsetX.value.roundToInt(), offsetY.value.roundToInt()) }
            .fillMaxHeight()
            .width(50.dp)
            .background(Color.Blue)
            .pointerInput(Unit) {
                awaitEachGesture {
                    val down = awaitFirstDown()
                    val change =
                        awaitHorizontalTouchSlopOrCancellation(down.id) { change, over ->
                            val originalX = offsetX.value
                            val newValue =
                                (originalX + over).coerceIn(0f, width - 50.dp.toPx())
                            change.consume()
                            offsetX.value = newValue
                        }
                    if (change != null) {
                        horizontalDrag(change.id) {
                            val originalX = offsetX.value
                            val newValue = (originalX + it.positionChange().x)
                                .coerceIn(0f, width - 50.dp.toPx())
                            it.consume()
                            offsetX.value = newValue
                        }
                    }
                }
            }
    )
}

DragGestureDetectorKt.verticalDrag

default final boolean DragGestureDetectorKt.verticalDrag(
    @NonNull AwaitPointerEventScope receiver,
    @NonNull PointerId pointerId,
    @NonNull Function1<@NonNull PointerInputChangeUnit> onDrag
)

Reads vertical position change events for pointerId and calls onDrag for every change in position. If pointerId is raised, a new pointer is chosen from those that are down and if none exist, the method returns. This does not wait for touch slop

import androidx.compose.foundation.background
import androidx.compose.foundation.gestures.awaitEachGesture
import androidx.compose.foundation.gestures.awaitFirstDown
import androidx.compose.foundation.gestures.awaitVerticalTouchSlopOrCancellation
import androidx.compose.foundation.gestures.verticalDrag
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.offset
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.ui.Modifier
import androidx.compose.ui.geometry.Offset
import androidx.compose.ui.geometry.Size
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.input.pointer.pointerInput
import androidx.compose.ui.input.pointer.positionChange
import androidx.compose.ui.layout.onSizeChanged
import androidx.compose.ui.unit.IntOffset
import androidx.compose.ui.unit.dp

val offsetX = remember { mutableStateOf(0f) }
val offsetY = remember { mutableStateOf(0f) }
var height by remember { mutableStateOf(0f) }
Box(
    Modifier.fillMaxSize()
        .onSizeChanged { height = it.height.toFloat() }
) {
    Box(
        Modifier.offset { IntOffset(offsetX.value.roundToInt(), offsetY.value.roundToInt()) }
            .fillMaxWidth()
            .height(50.dp)
            .background(Color.Blue)
            .pointerInput(Unit) {
                awaitEachGesture {
                    val down = awaitFirstDown()
                    val change =
                        awaitVerticalTouchSlopOrCancellation(down.id) { change, over ->
                            val originalY = offsetY.value
                            val newValue = (originalY + over)
                                .coerceIn(0f, height - 50.dp.toPx())
                            change.consume()
                            offsetY.value = newValue
                        }
                    if (change != null) {
                        verticalDrag(change.id) {
                            val originalY = offsetY.value
                            val newValue = (originalY + it.positionChange().y)
                                .coerceIn(0f, height - 50.dp.toPx())
                            it.consume()
                            offsetY.value = newValue
                        }
                    }
                }
            }
    )
}
Returns
boolean

true if the vertical drag completed normally or false if the drag motion was canceled by another gesture detector consuming position change events.

Example Usage:

TapGestureDetectorKt.awaitFirstDown

default final @NonNull PointerInputChange TapGestureDetectorKt.awaitFirstDown(
    @NonNull AwaitPointerEventScope receiver,
    boolean requireUnconsumed,
    @NonNull PointerEventPass pass
)

Reads events until the first down is received in the given pass. If requireUnconsumed is true and the first down is already consumed in the pass, that gesture is ignored.

TapGestureDetectorKt.waitForUpOrCancellation

default final PointerInputChange TapGestureDetectorKt.waitForUpOrCancellation(
    @NonNull AwaitPointerEventScope receiver,
    @NonNull PointerEventPass pass
)

Reads events in the given pass until all pointers are up or the gesture was canceled. The gesture is considered canceled when a pointer leaves the event region, a position change has been consumed or a pointer down change event was already consumed in the given pass. If the gesture was not canceled, the final up change is returned or null if the event was canceled.