美文网首页
13 Compose 源码解析之 Touch 事件分发

13 Compose 源码解析之 Touch 事件分发

作者: 我爱田Hebe | 来源:发表于2022-09-05 09:31 被阅读0次

先看下 Compose 跟原生 View 在 Activity 中的区别

再看下 TouchEvent 传递流程:

Compose 事件分发跟原生 View 的区别还是在 View 事件分发流程这块。虽然 setContent 的时候添加到 DecorView 中的是 ComposeView , 但参与组合的还是 AndroidComposeView。 Compose TouchEvent 分发就是从 AndroidComposeView#dispatchTouchEvent 开始的。

AndroidComposeView#dispatchTouchEvent 方法可谓一目了然,但真正的 Compose 事件分发是在 handleMotionEvent() 中实现的。

    override fun dispatchTouchEvent(motionEvent: MotionEvent): Boolean {
        // 检测 MotionEvent 是否合法
        if (isBadMotionEvent(motionEvent)) {
            return false // Bad MotionEvent. Don't handle it.
        }
        //处理 MotionEvent , Compose 事件分发实现
        val processResult = handleMotionEvent(motionEvent) 
        //如果事件被消耗,设置父 View 不可拦截事件传递
        if (processResult.anyMovementConsumed) {
            parent.requestDisallowInterceptTouchEvent(true)
        }
        //返回事件是否被消耗
        return processResult.dispatchedToAPointerInputModifier 
    }

1 MotionEvent 转换加工

Compose 中并不直接处理 MotionEvent ,首先将 MotionEvent 转换成 PointerInputEvent ,将每个触摸点的信息转换成 PointerInputEventData 保存到 PointerInputEvent.pointers 中。

internal actual class  (
    actual val uptime: Long,//事件发生的事件
    actual val pointers: List<PointerInputEventData>, //每个触摸点的触摸数据
    val motionEvent: MotionEvent //PointerInputEvent 的事件源
)
internal data class PointerInputEventData(
    val id: PointerId,
    val uptime: Long,
    val positionOnScreen: Offset,
    val position: Offset,
    val down: Boolean,
    val type: PointerType,
    val historical: List<HistoricalChange> = mutableListOf()
)

转换后的 PointerInputEvent 交给 PointerInputEventProcessor 继续处理。

PointerInputChangeEventProducer 中 previousPointerInputData 保存上次事件每个 PointId 对应的 PointerInputChange 信息, 遍历 pointerEvent.pointers 对比 previousPointerInputData 生成每个 pointer 对应的 PointerInputChange 并保存到 InternalPointerEvent.changes 中。

internal actual class InternalPointerEvent constructor(
    actual val changes: Map<PointerId, PointerInputChange>,
    val motionEvent: MotionEvent
) {
    actual constructor(
        changes: Map<PointerId, PointerInputChange>,
        pointerInputEvent: PointerInputEvent
    ) : this(changes, pointerInputEvent.motionEvent)
}
class PointerInputChange(
    val id: PointerId,
    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
)

MotionEvent 的加工处理到此并没有结束,但现有的数据已经可以确定每个触控点(pointer)的类型(type)、是否是按下操作(pressed)以及事件需要传递到那些组件中(postion)。

2 确定事件分发路径

每个触控点按下都是这个触控点触摸事件的开始,所以要为每个按下的触控点确定事件分发路径。

        internalPointerEvent.changes.values.forEach { pointerInputChange ->
            if (isHover || pointerInputChange.changedToDownIgnoreConsumed()) {
                val isTouchEvent = pointerInputChange.type == PointerType.Touch
                //确定触摸点事件分发路径保存到 hitResult 中
                root.hitTest(pointerInputChange.position, hitResult, isTouchEvent)
                if (hitResult.isNotEmpty()) {
                    // hitPathTracker 保存本次事件所有触摸点的分发路径
                    hitPathTracker.addHitPath(pointerInputChange.id, hitResult)
                    hitResult.clear()
                }
            }
        }
        //删除分发路径中已经 Detach compose 组件
        hitPathTracker.removeDetachedPointerInputFilters()

root 是 LayoutNode 类型,它就是我们经常说的 Compose 树的根节点。

确定分发路径的过程是从根节点开始将所有包含了事件发生坐标的子节点的 modifier.pointerFilter 添加到 HitTestResult.value 中。

有背景部分的流程只有代码中实现了 modifier.pointerInput() 才会执行到。

因为LayoutNodeWrapper.hitTest() 是抽象方法 ,在 LayoutNode modifier 属性 set 方法中可以看出只有代码中实现了 modifier.pointerInput() 的 LayoutNode 的 outerLayoutNodeWrapper 属性才会包装 PointerInputDelegationWrapper,否则就会继续向子节点传递 hitTest()

3 事件分发

现在要做的只剩下在 hitPathTracker 保存的路径中分发事件了,事件在分发之前会在 buildCache() 中转换成 PointerEvent 类型。

        return dispatchIfNeeded {
            val event = pointerEvent!!
            val size = coordinates!!.size
            // Dispatch on the tunneling pass.
            pointerInputFilter.onPointerEvent(event, PointerEventPass.Initial, size)

            // Dispatch to children.
            if (pointerInputFilter.isAttached) {
                children.forEach {
                    it.dispatchMainEventPass(
                        // Pass only the already-filtered and position-translated changes down to
                        // children
                        relevantChanges,
                        coordinates!!,
                        internalPointerEvent,
                        isInBounds
                    )
                }
            }

            if (pointerInputFilter.isAttached) {
                // Dispatch on the bubbling pass.
                pointerInputFilter.onPointerEvent(event, PointerEventPass.Main, size)
            }
        }

从代码可以看出分发是递归调用,事件由父到子传递,再由子到父处理,这点跟 View 体系是一样的。

事件消耗

本文的开始的第一段代码中有检测事件事件是否被消耗的代码。


/**
 * Consume the up or down change of this [PointerInputChange] if there is an up or down change to
 * consume.
 */
fun PointerInputChange.consumeDownChange() {
    if (pressed != previousPressed) {
        consumed.downChange = true
    }
}

/**
 * Consume position change if there is any
 */
fun PointerInputChange.consumePositionChange() {
    if (positionChange() != Offset.Zero) {
        consumed.positionChange = true
    }
}

事件是通过上面两个 api 消耗的,pointerInput 的 detectXXX api 的源码中都能看到这两个 api 的使用。

这也代表着只要在 pointerInput 中用了 detectXXX api 即使方法体为空也会消耗事件(不知道后面官方会不会改)。

作者:给大佬们点赞
链接:https://juejin.cn/post/7135348786364153863

相关文章

  • 13 Compose 源码解析之 Touch 事件分发

    先看下 Compose 跟原生 View 在 Activity 中的区别 再看下 TouchEvent 传递流程...

  • Android事件分发机制完全解析

    Android事件分发机制完全解析,带你从源码的角度彻底理解(上)Android事件分发机制完全解析,带你从源码的...

  • Android点击事件分发

    TouchEventDispatchDemo 点击事件分发 基本事件分发为: 源码解析 Activity Acti...

  • # View事件分发(二)

    View事件分发(二) 事件分发的源码解析 Activity对点击事件的分发过程 点击事件用MotionEvent...

  • View事件分发相关结论的源码解析

    View事件分发相关结论的源码解析 了解过View事件分发源码的同学或多或少都知道一些事件分发的相关结论,比如某个...

  • 有趣的链接

    Android事件分析 可能是讲解Android事件分发最好的文章 Android事件分发机制完全解析,带你从源码...

  • Android事件分发学习路线

    Android事件分发机制,大表哥带你慢慢深入(图解很赞) Android View 事件分发机制源码解析(鸿洋写...

  • Android事件分发与消费机制

    一、Touch 事件分析: 事件分发:dispatchTouchEvent return true:事件会分发给当...

  • Android 事件分发机制

    搬砖参考一文读懂Android View事件分发机制Android事件分发机制完全解析,带你从源码的角度彻底理解(...

  • Android 事件分发机制

    参考文献1.Android事件分发机制完全解析,带你从源码的角度彻底理解(上)2. android面试-事件分发

网友评论

      本文标题:13 Compose 源码解析之 Touch 事件分发

      本文链接:https://www.haomeiwen.com/subject/kwuvnrtx.html