AwaitPointerEventScope


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 functions

suspend PointerEvent

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

Cmn
open suspend T
<T : Any?> withTimeout(timeMillis: Long, block: suspend AwaitPointerEventScope.() -> T)

Runs block and returns its results.

Cmn
open suspend T?
<T : Any?> withTimeoutOrNull(timeMillis: Long, block: suspend AwaitPointerEventScope.() -> T)

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

Cmn

Public properties

PointerEvent

The PointerEvent from the most recent touch event.

Cmn
open Size
Cmn
IntSize

The measured size of the pointer input region.

Cmn
ViewConfiguration

The ViewConfiguration used to tune gesture detectors.

Cmn

Extension functions

suspend PointerInputChange?

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

Cmn
suspend PointerInputChange?

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

Cmn
suspend PointerInputChange?
AwaitPointerEventScope.awaitHorizontalTouchSlopOrCancellation(
    pointerId: PointerId,
    onTouchSlopReached: (change: PointerInputChange, overSlop: Float) -> Unit
)

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

Cmn
suspend PointerInputChange?

Waits for a long press by examining pointerId.

Cmn
suspend PointerInputChange?
AwaitPointerEventScope.awaitTouchSlopOrCancellation(
    pointerId: PointerId,
    onTouchSlopReached: (change: PointerInputChange, overSlop: Offset) -> Unit
)

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

Cmn
suspend PointerInputChange?

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

Cmn
suspend PointerInputChange?
AwaitPointerEventScope.awaitVerticalTouchSlopOrCancellation(
    pointerId: PointerId,
    onTouchSlopReached: (change: PointerInputChange, overSlop: Float) -> Unit
)

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

Cmn
suspend Boolean
AwaitPointerEventScope.drag(
    pointerId: PointerId,
    onDrag: (PointerInputChange) -> Unit
)

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

Cmn
suspend Boolean
AwaitPointerEventScope.horizontalDrag(
    pointerId: PointerId,
    onDrag: (PointerInputChange) -> Unit
)

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

Cmn
suspend Boolean
AwaitPointerEventScope.verticalDrag(
    pointerId: PointerId,
    onDrag: (PointerInputChange) -> Unit
)

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

Cmn
suspend PointerInputChange
AwaitPointerEventScope.awaitFirstDown(
    requireUnconsumed: Boolean,
    pass: PointerEventPass
)

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

Cmn
suspend PointerInputChange?

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

Cmn

Inherited functions

From androidx.compose.ui.unit.Density
open Int

Convert Dp to Int by rounding

Cmn
open Int

Convert Sp to Int by rounding

Cmn
open Dp

Convert an Int pixel value to Dp.

Cmn
open Dp

Convert a Float pixel value to a Dp

Cmn
open DpSize

Convert a Size to a DpSize.

Cmn
open Float

Convert Dp to pixels.

Cmn
open Float

Convert Sp to pixels.

Cmn
open Rect

Convert a DpRect to a Rect.

Cmn
open Size

Convert a DpSize to a Size.

Cmn
open TextUnit

Convert an Int pixel value to Sp.

Cmn
open TextUnit

Convert a Float pixel value to a Sp

Cmn
From androidx.compose.ui.unit.FontScaling
open Dp

Convert Sp to Dp.

Cmn
open TextUnit

Convert Dp to Sp.

Cmn

Inherited properties

From androidx.compose.ui.unit.Density
Float

The logical density of the display.

Cmn
From androidx.compose.ui.unit.FontScaling
Float

Current user preference for the scaling factor for fonts.

Cmn

Public functions

awaitPointerEvent

suspend fun awaitPointerEvent(pass: PointerEventPass = PointerEventPass.Main): PointerEvent

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.

withTimeout

open suspend fun <T : Any?> withTimeout(timeMillis: Long, block: suspend AwaitPointerEventScope.() -> T): T

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

withTimeoutOrNull

open suspend fun <T : Any?> withTimeoutOrNull(timeMillis: Long, block: suspend AwaitPointerEventScope.() -> T): T?

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

Public properties

currentEvent

val currentEventPointerEvent

The PointerEvent from the most recent touch event.

extendedTouchPadding

open val extendedTouchPaddingSize

size

val sizeIntSize

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.

viewConfiguration

val viewConfigurationViewConfiguration

The ViewConfiguration used to tune gesture detectors.

Extension functions

awaitDragOrCancellation

suspend fun AwaitPointerEventScope.awaitDragOrCancellation(pointerId: PointerId): PointerInputChange?

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.offset
import androidx.compose.foundation.layout.size
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.ui.geometry.Offset
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.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
                        }
                    }
                }
            }
    )
}

awaitHorizontalDragOrCancellation

suspend fun AwaitPointerEventScope.awaitHorizontalDragOrCancellation(
    pointerId: PointerId
): PointerInputChange?

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.input.pointer.pointerInput
import androidx.compose.ui.input.pointer.positionChange
import androidx.compose.ui.layout.onSizeChanged
import androidx.compose.ui.unit.IntOffset

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
                        }
                    }
                }
            }
    )
}

awaitHorizontalTouchSlopOrCancellation

suspend fun AwaitPointerEventScope.awaitHorizontalTouchSlopOrCancellation(
    pointerId: PointerId,
    onTouchSlopReached: (change: PointerInputChange, overSlop: Float) -> Unit
): PointerInputChange?

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.input.pointer.pointerInput
import androidx.compose.ui.input.pointer.positionChange
import androidx.compose.ui.layout.onSizeChanged
import androidx.compose.ui.unit.IntOffset

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:

awaitLongPressOrCancellation

suspend fun AwaitPointerEventScope.awaitLongPressOrCancellation(
    pointerId: PointerId
): PointerInputChange?

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.draw.clipToBounds
import androidx.compose.ui.input.pointer.pointerInput

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:

awaitTouchSlopOrCancellation

suspend fun AwaitPointerEventScope.awaitTouchSlopOrCancellation(
    pointerId: PointerId,
    onTouchSlopReached: (change: PointerInputChange, overSlop: Offset) -> Unit
): PointerInputChange?

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.offset
import androidx.compose.foundation.layout.size
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.ui.geometry.Offset
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.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:

awaitVerticalDragOrCancellation

suspend fun AwaitPointerEventScope.awaitVerticalDragOrCancellation(
    pointerId: PointerId
): PointerInputChange?

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.input.pointer.pointerInput
import androidx.compose.ui.input.pointer.positionChange
import androidx.compose.ui.layout.onSizeChanged
import androidx.compose.ui.unit.IntOffset

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
                        }
                    }
                }
            }
    )
}

awaitVerticalTouchSlopOrCancellation

suspend fun AwaitPointerEventScope.awaitVerticalTouchSlopOrCancellation(
    pointerId: PointerId,
    onTouchSlopReached: (change: PointerInputChange, overSlop: Float) -> Unit
): PointerInputChange?

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.input.pointer.pointerInput
import androidx.compose.ui.input.pointer.positionChange
import androidx.compose.ui.layout.onSizeChanged
import androidx.compose.ui.unit.IntOffset

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:

suspend fun AwaitPointerEventScope.drag(
    pointerId: PointerId,
    onDrag: (PointerInputChange) -> Unit
): Boolean

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.offset
import androidx.compose.foundation.layout.size
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.ui.geometry.Offset
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.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:

suspend fun AwaitPointerEventScope.horizontalDrag(
    pointerId: PointerId,
    onDrag: (PointerInputChange) -> Unit
): Boolean

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.input.pointer.pointerInput
import androidx.compose.ui.input.pointer.positionChange
import androidx.compose.ui.layout.onSizeChanged
import androidx.compose.ui.unit.IntOffset

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
                        }
                    }
                }
            }
    )
}
suspend fun AwaitPointerEventScope.verticalDrag(
    pointerId: PointerId,
    onDrag: (PointerInputChange) -> Unit
): Boolean

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.input.pointer.pointerInput
import androidx.compose.ui.input.pointer.positionChange
import androidx.compose.ui.layout.onSizeChanged
import androidx.compose.ui.unit.IntOffset

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:

suspend fun AwaitPointerEventScope.awaitFirstDown(
    requireUnconsumed: Boolean = true,
    pass: PointerEventPass = PointerEventPass.Main
): PointerInputChange

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.

waitForUpOrCancellation

suspend fun AwaitPointerEventScope.waitForUpOrCancellation(
    pass: PointerEventPass = PointerEventPass.Main
): PointerInputChange?

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.