image/svg+xml $ $ ing$ ing$ ces$ ces$ Res Res ea ea Res->ea ou ou Res->ou r r ea->r ch ch ea->ch r->ces$ r->ch ch->$ ch->ing$ T T T->ea ou->r

Managing input events with Jetpack compose

  1. Managing clicks
  2. Touch events
  3. Complex gestures management

Functions handling input events can be installed using methods on Modifier.

Managing clicks

Button has an argument onClick to register a click handler :

var counter by remember { mutableStateOf(0) } // in kg
Button(onClick={ counter ++ }) { Text("Counter: $counter") }

Other components can deal with clicks using Modifier.clickable:

Modifier?.clickable(
    enabled: Boolean?,
    onClickLabel: String?, // text used for the accessibility
    role: Role?,
    onClick: (() -> Unit)?
)

For example we can write a clickable image:

@Composable
fun ClickableImage() {
    var opacity by remember { mutableStateOf(100) }
    Image(painter = painterResource(id = R.drawable.mercator),
        modifier = Modifier.clickable {
            opacity = (100 + opacity - 10) % 100
        },
        contentDescription = "world map",
        alignment = Alignment.Center,
        contentScale = ContentScale.Fit,
        alpha = opacity / 100.0f
    )
}

Touch events

Signature of pointerInput:

fun Modifier?.pointerInput(
    vararg keys: Any?,
    block: (@ExtensionFunctionType suspend PointerInputScope.() -> Unit)?
): Modifier

Some useful coroutines to wait for events:

Example: an activity showing the mileage of fingers on the screen

@Composable
fun FingerMileageDisplayer(fingerMileage: Map<Long, Float>, fingerPressed: Map<Long, Boolean>) {
    Column(Modifier.fillMaxSize(), verticalArrangement = Arrangement.Center) {
        fingerMileage.forEach { entry ->
            Box(
                Modifier
                    .fillMaxWidth()
                    .background(if (fingerPressed[entry.key] == true) Color.Yellow else Color.LightGray)) {
                Text("${entry.value.toInt()}")
            }
        }
    }
}

@Composable
fun PointerInputTest() {
    var fingerMileage = remember { mutableStateMapOf<Long, Float>() }
    var fingerPressed = remember { mutableStateMapOf<Long, Boolean>() }
    Box(modifier = Modifier
        .fillMaxSize()
        .background(Color.Green)
        .border(1.0.dp, Color.Black)
        .pointerInput(Unit) {
            awaitPointerEventScope {
                while (true) {
                    awaitFirstDown()
                    Log.v("InputEvent", "FirstDown")
                    do {
                        val event = awaitPointerEvent()
                        event.changes.forEach { change ->
                            fingerPressed[change.id.value] = change.pressed
                            if (change.pressed) {
                                val offset = change.position - change.previousPosition
                                val distance = sqrt(offset.x * offset.x + offset.y * offset.y)
                                fingerMileage.merge(change.id.value, distance) { x, y -> x + y }
                            }
                            change.consumeAllChanges()
                        }
                        val pressed = event.changes.any { it.pressed }
                    } while (pressed)
                    Log.v("InputEvent", "End of event")
                }
            }
        }) {
        FingerMileageDisplayer(fingerMileage, fingerPressed)
    }
}

Rather than handling manually events, one can also use predefined detectors to make the task easier in PointerInputScope.

detectTapGestures allows the detection of taps on the screen:

suspend fun PointerInputScope?.detectTapGestures(
    onDoubleTap: ((Offset) -> Unit)? = null,
    onLongPress: ((Offset) -> Unit)? = null,
    onPress: (@ExtensionFunctionType suspend PressGestureScope.(Offset) -> Unit)? = NoPressGesture,
    onTap: ((Offset) -> Unit)? = null
): Unit

Complex gestures management

Compose proposes Modifier extension functions to manage complex gestures:

These functions do not modify the rendering for the component they are applied on. They are only used to update state variables (then one can modify rendering using these variables).
To update the state, special state handlers can be used: