PointerEvent


Describes a pointer input change event that has occurred at a particular point in time.

Summary

Public constructors

Cmn
android

Public functions

List<PointerInputChange>
android
PointerEvent
copy(changes: List<PointerInputChange>, motionEvent: MotionEvent?)
android

Public properties

PointerButtons

The state of buttons (e.g. mouse or stylus buttons) during this event.

Cmn
android
List<PointerInputChange>

The changes.

Cmn
android
Int

Returns MotionEvent's classification.

android
PointerKeyboardModifiers

The state of modifier keys during this event.

Cmn
android
MotionEvent?

The underlying Android MotionEvent that triggered this PointerEvent.

android
PointerEventType

The primary reason the PointerEvent was sent.

Cmn
android

Extension functions

Offset

Returns the centroid of all pointers that are down and were previously down.

Cmn
Float

Returns the average distance from the centroid for all pointers that are currently and were previously down.

Cmn
Offset

Returns the change in the centroid location between the previous and the current pointers that are down.

Cmn
Float

Returns the rotation, in degrees, of the pointers between the PointerInputChange.previousPosition and PointerInputChange.position states.

Cmn
Float

Uses the change of the centroid size between the PointerInputChange.previousPosition and PointerInputChange.position to determine how much zoom was intended.

Cmn

Public constructors

PointerEvent

PointerEvent(changes: List<PointerInputChange>)
Parameters
changes: List<PointerInputChange>

The changes.

Public functions

component1

fun component1(): List<PointerInputChange>

copy

fun copy(changes: List<PointerInputChange>, motionEvent: MotionEvent?): PointerEvent

Public properties

buttons

val buttonsPointerButtons

The state of buttons (e.g. mouse or stylus buttons) during this event.

changes

val changesList<PointerInputChange>

The changes.

classification

val classificationInt

Returns MotionEvent's classification.

keyboardModifiers

val keyboardModifiersPointerKeyboardModifiers

The state of modifier keys during this event.

motionEvent

val motionEventMotionEvent?

The underlying Android MotionEvent that triggered this PointerEvent.

This property provides access to the raw MotionEvent for retrieving platform-specific information not yet exposed by the Compose PointerEvent API (e.g., stylus tilt angle).

Important Considerations:

  1. Read-Only: The returned MotionEvent is strictly read-only. Modifying it will lead to unpredictable behavior.

  2. Transient: Do not store a reference to this MotionEvent. The Android framework may recycle it, rendering its state undefined and causing errors if accessed later. Access the data only within the scope where the PointerEvent is received.

  3. Metadata Only: This MotionEvent should not be used for primary input handling logic (e.g., determining pointer position or button presses). Rely on the properties of PointerEvent and PointerInputChange for this purpose. The MotionEvent is intended solely for accessing supplemental metadata.

  4. Nullability: This property will be null in two cases:

    • The PointerEvent was fabricated within Compose (i.e., not directly from a system input event).

    • The PointerEvent has already been dispatched within the Compose input system. (See androidx.compose.ui.samples.PointerEventMotionEventSample for details).

import androidx.compose.foundation.gestures.awaitEachGesture
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
import androidx.compose.ui.input.pointer.pointerInput

@Composable
fun MyComposable() {
    Box(
        Modifier.fillMaxSize().pointerInput(Unit) {
            awaitEachGesture {
                val pointerEvent = awaitPointerEvent()

                // Use MotionEvent only for additional metadata.
                performAction(pointerEvent.motionEvent?.getAxisValue(MotionEvent.AXIS_TILT))

                awaitPointerEvent()
                // Do NOT try to access the MotionEvent of the first pointEvent after awaits
                // another PointerEvent. The PointerEvent's MotionEvent is set to null when it
                // finishes dispatch.
                // In the following line, pointerEvent.motionEvent returns null.
                performAction(pointerEvent.motionEvent?.getAxisValue(MotionEvent.AXIS_TILT))
            }
        }
    )
}

type

val typePointerEventType

The primary reason the PointerEvent was sent.

Extension functions

calculateCentroid

fun PointerEvent.calculateCentroid(useCurrent: Boolean = true): Offset

Returns the centroid of all pointers that are down and were previously down. If no pointers are down, Offset.Unspecified is returned. If useCurrent is true, the centroid of the PointerInputChange.position is returned and if false, the centroid of the PointerInputChange.previousPosition is returned. Only pointers that are down in both the previous and current state are used to calculate the centroid.

Example Usage:

import androidx.compose.foundation.gestures.awaitEachGesture
import androidx.compose.foundation.gestures.awaitFirstDown
import androidx.compose.foundation.gestures.calculateCentroid
import androidx.compose.foundation.gestures.calculateCentroidSize
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.drawBehind
import androidx.compose.ui.geometry.Offset
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.input.pointer.pointerInput

var centroidSize by remember { mutableStateOf(0f) }
var position by remember { mutableStateOf(Offset.Zero) }
Box(
    Modifier.drawBehind {
            // Draw a circle where the gesture is
            drawCircle(Color.Blue, centroidSize, center = position)
        }
        .pointerInput(Unit) {
            awaitEachGesture {
                awaitFirstDown().also { position = it.position }
                do {
                    val event = awaitPointerEvent()
                    val size = event.calculateCentroidSize()
                    if (size != 0f) {
                        centroidSize = event.calculateCentroidSize()
                    }
                    val centroid = event.calculateCentroid()
                    if (centroid != Offset.Unspecified) {
                        position = centroid
                    }
                } while (event.changes.any { it.pressed })
            }
        }
        .fillMaxSize()
)

calculateCentroidSize

fun PointerEvent.calculateCentroidSize(useCurrent: Boolean = true): Float

Returns the average distance from the centroid for all pointers that are currently and were previously down. If no pointers are down, 0 is returned. If useCurrent is true, the size of the PointerInputChange.position is returned and if false, the size of PointerInputChange.previousPosition is returned. Only pointers that are down in both the previous and current state are used to calculate the centroid size.

Example Usage:

import androidx.compose.foundation.gestures.awaitEachGesture
import androidx.compose.foundation.gestures.awaitFirstDown
import androidx.compose.foundation.gestures.calculateCentroid
import androidx.compose.foundation.gestures.calculateCentroidSize
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.drawBehind
import androidx.compose.ui.geometry.Offset
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.input.pointer.pointerInput

var centroidSize by remember { mutableStateOf(0f) }
var position by remember { mutableStateOf(Offset.Zero) }
Box(
    Modifier.drawBehind {
            // Draw a circle where the gesture is
            drawCircle(Color.Blue, centroidSize, center = position)
        }
        .pointerInput(Unit) {
            awaitEachGesture {
                awaitFirstDown().also { position = it.position }
                do {
                    val event = awaitPointerEvent()
                    val size = event.calculateCentroidSize()
                    if (size != 0f) {
                        centroidSize = event.calculateCentroidSize()
                    }
                    val centroid = event.calculateCentroid()
                    if (centroid != Offset.Unspecified) {
                        position = centroid
                    }
                } while (event.changes.any { it.pressed })
            }
        }
        .fillMaxSize()
)
fun PointerEvent.calculatePan(): Offset

Returns the change in the centroid location between the previous and the current pointers that are down. Pointers that are newly down or raised are not considered in the centroid movement.

Example Usage:

import androidx.compose.foundation.background
import androidx.compose.foundation.gestures.awaitEachGesture
import androidx.compose.foundation.gestures.awaitFirstDown
import androidx.compose.foundation.gestures.calculatePan
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.fillMaxSize
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.graphics.Color
import androidx.compose.ui.graphics.graphicsLayer
import androidx.compose.ui.input.pointer.pointerInput
import androidx.compose.ui.unit.IntOffset

val offsetX = remember { mutableStateOf(0f) }
val offsetY = remember { mutableStateOf(0f) }
Box(
    Modifier.offset { IntOffset(offsetX.value.roundToInt(), offsetY.value.roundToInt()) }
        .graphicsLayer()
        .background(Color.Blue)
        .pointerInput(Unit) {
            awaitEachGesture {
                awaitFirstDown()
                do {
                    val event = awaitPointerEvent()
                    val offset = event.calculatePan()
                    offsetX.value += offset.x
                    offsetY.value += offset.y
                } while (event.changes.any { it.pressed })
            }
        }
        .fillMaxSize()
)

calculateRotation

fun PointerEvent.calculateRotation(): Float

Returns the rotation, in degrees, of the pointers between the PointerInputChange.previousPosition and PointerInputChange.position states. Only the pointers that are down in both previous and current states are considered.

Example Usage:

import androidx.compose.foundation.background
import androidx.compose.foundation.gestures.awaitEachGesture
import androidx.compose.foundation.gestures.awaitFirstDown
import androidx.compose.foundation.gestures.calculateRotation
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.graphics.graphicsLayer
import androidx.compose.ui.input.pointer.pointerInput

var angle by remember { mutableStateOf(0f) }
Box(
    Modifier.graphicsLayer(rotationZ = angle)
        .background(Color.Blue)
        .pointerInput(Unit) {
            awaitEachGesture {
                awaitFirstDown()
                do {
                    val event = awaitPointerEvent()
                    val rotation = event.calculateRotation()
                    angle += rotation
                } while (event.changes.any { it.pressed })
            }
        }
        .fillMaxSize()
)
fun PointerEvent.calculateZoom(): Float

Uses the change of the centroid size between the PointerInputChange.previousPosition and PointerInputChange.position to determine how much zoom was intended.

Example Usage:

import androidx.compose.foundation.background
import androidx.compose.foundation.gestures.awaitEachGesture
import androidx.compose.foundation.gestures.awaitFirstDown
import androidx.compose.foundation.gestures.calculateZoom
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.graphics.graphicsLayer
import androidx.compose.ui.input.pointer.pointerInput

var zoom by remember { mutableStateOf(1f) }
Box(
    Modifier.graphicsLayer(scaleX = zoom, scaleY = zoom)
        .background(Color.Blue)
        .pointerInput(Unit) {
            awaitEachGesture {
                awaitFirstDown()
                do {
                    val event = awaitPointerEvent()
                    zoom *= event.calculateZoom()
                } while (event.changes.any { it.pressed })
            }
        }
        .fillMaxSize()
)