手势事件消费
在了解手势事件分发之后,我们接下来学习如何完成手势事件消费,我们看到 awaitPointerEvent
返回了一个 PointerEvent
实例。
actual data class PointerEvent internal constructor(
actual val changes: List<PointerInputChange>,
internal val motionEvent: MotionEvent?
)
从 PointerEvent 类的声明中可以看到包含了两个属性 changes 与 motionEvent。
motionEvent:实际上就是传统 View 系统中的 MotionEvent,由于被声明 internal ,说明官方并不希望我们直接拿来使用。
changes:其中包含了一次手势交互中所有手指的交互信息。在多指操作时,利用
changes
可以轻松定制多指手势处理。
可以看出单指交互的完整信息被封装在了一个 PointerInputChange 实例中,接下来我们看看 PointerInputChange 提供了哪些手势信息。
class PointerInputChange(
val id: PointerId, // 手指Id
val uptimeMillis: Long, // 当前手势事件的时间戳
val position: Offset, // 当前手势事件相对组件左上角的位置
val pressed: Boolean, // 当前手势是否按下
val previousUptimeMillis: Long, // 上一次手势事件的时间戳
val previousPosition: Offset, // 上一次手势事件相对组件左上角的位置
val previousPressed: Boolean, // 上一次手势是否按下
val consumed: ConsumedData, // 当前手势是否已被消费
val type: PointerType = PointerType.Touch // 手势类型(鼠标、手指、手写笔、橡皮)
)
利用这些丰富的手势信息,我们可以在上层定制实现各类复杂的交互手势。
可以看到其中的 consumed 成员记录着该事件是否已被消费,我们可以使用 PointerInputChange
提供的 consume 系列 API 来修改这个手势事件的消费标记。
changedToDown是否已经按下(按下手势已消费则返回false)
changedToDownIgnoreConsumed是否已经按下(忽略按下手势已消费标记)
changedToUp是否已经抬起(按下手势已消费则返回false)
changedToUpIgnoreConsumed是否已经抬起(忽略按下手势已消费标记)
positionChanged是否位置发生了改变(移动手势已消费则返回false)
positionChangedIgnoreConsumed是否位置发生了改变(忽略已消费标记)
positionChange位置改变量(移动手势已消费则返回Offset.Zero)
positionChangeIgnoreConsumed位置改变量(忽略移动手势已消费标记)
positionChangeConsumed当前移动手势是否已被消费
anyChangeConsumed当前按下手势或移动手势是否有被消费
consumeDownChange消费按下手势
consumePositionChange消费移动手势
consumeAllChanges消费按下与移动手势
isOutOfBounds当前手势是否在固定范围内
前面提到,我们可以通过设置 PointerEventPass
来定制嵌套组件间手势事件分发顺序。假设分发流程中组件 A 预先获取到了手势信息并进行消费,手势事件仍然会被之后的组件 B 获取得到。组件 B 在使用 positionChange
获取的偏移值时会返回 Offset.ZERO
,这是因为此时该手势事件已被标记为已消费的状态。当然组件 B 也可以通过 IgnoreConsumed 系列 API 突破已消费标记的限制获取到手势信息。
我们仍然通过前面使用的嵌套组件示例子来看看手势事件的消费。我们的嵌套组件中第一层组件使用 Inital,第二层组件使用 Final ,第三层组件使用 Main。

我们在第三层组件的手势事件监听中进行消费,因为我们知道手势事件会交由第一层, 再交由第三层,最后交由第二层。第三层组件处于本次手势分发流程的中间位置。
当我们在第三层组件消费了 ACTION_DOWN
后,之后处理的第二层组件接收的手势事件仍是被标记为消费状态的。
@Composable
fun ConsumeDemo() {
Box(
contentAlignment = Alignment.Center,
modifier = Modifier
.fillMaxSize()
.pointerInput(Unit) {
awaitPointerEventScope {
var event = awaitPointerEvent(PointerEventPass.Initial)
Log.d(TAG, "first layer, downChange: ${event.changes[0].consumed.downChange}")
}
}
) {
Box(
contentAlignment = Alignment.Center,
modifier = Modifier
.size(400.dp)
.background(Color.Blue)
.pointerInput(Unit) {
awaitPointerEventScope {
var event = awaitPointerEvent(PointerEventPass.Final)
Log.d(TAG, "second layer, downChange: ${event.changes[0].consumed.downChange}")
}
}
) {
Box(
Modifier
.size(200.dp)
.background(Color.Green)
.pointerInput(Unit) {
awaitPointerEventScope {
var event = awaitPointerEvent()
event.changes[0].consumeDownChange() // 消费手势事件
Log.d(TAG, "third layer, downChange: ${event.changes[0].consumed.downChange}")
}
}
)
}
}
}
介绍完 Compose 的手势事件分发与消费,想必大家已经对 awaitPointerEvent
这个低级别基础手势监听 API 已经有了足够的了解。然而在实际场景中我们还是应该更多的依赖上层封装完善的 API,因为当手势逻辑变得越来越复杂时,维护手势交互处理逻辑的难度也会越来越大。接下来我们来介绍 AwaitPointerEventScope
中基于 awaitPointerEvent
实现的几个常用手势监听挂起方法。