欢迎光临散文网 会员登陆 & 注册

手势事件消费

2023-04-02 23:51 作者:哆啦a梦的道具师  | 我要投稿

在了解手势事件分发之后,我们接下来学习如何完成手势事件消费,我们看到 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 实现的几个常用手势监听挂起方法。

手势事件消费的评论 (共 条)

分享到微博请遵守国家法律