在传统 View 体系中,我们接触最多的就是
dispatchTouchEvent (父节点 → 子节点) └── onInterceptTouchEvent onTouchEvent (子节点 → 父节点)而在 Compose 中,事件分发不再依赖 ViewGroup 的拦截模型,而是基于Modifier 链 + PointerInput 节点 + 多阶段事件传递。
PointerEventPass.Initial (父节点 → 子节点) 父级优先观察或预处理事件 PointerEventPass.Main (子节点 → 父节点) 子组件优先处理事件,最常用 PointerEventPass.Final (父节点 → 子节点) 父级查看事件是否已被消费,做收尾处理监听和处理事件的方法(以 Main 为例):
Modifier.pointerInput(Unit) { awaitPointerEventScope { while (true) { val change = awaitPointerEvent(PointerEventPass.Main).changes.first() when { change.changedToDown() -> {} // down 事件 change.positionChanged() -> {} // moved 事件 change.changedToUp() -> {} // up 事件 } logi("事件是否已消费: ${change.isConsumed}") change.consume() // 消费事件 } } }值得注意的是,传统 View 体系在事件被消费后续就不会回调,而 compose 的事件不管有没有消费都会有回调,通过 isConsumed 字段来判断是否已被消费。compose 的事件分发体系相对来说信息更完整
一般情况下,优先使用高层 API:
Modifier.clickable { } Modifier.draggable(...) Modifier.scrollable(...)高层手势的API 和 底层手势 API 混用时需要注意顺序,一般是先高层再底层
Modifier .clickable { } .pointerInput(Unit) { }如果反了的话
Modifier .pointerInput(Unit) { } .clickable { }pointerInput 接收到的事件就很有可能是 isConsumed 状态 从而影响判断
总结:
Compose 的事件分发可以用一句话概括:同一个指针事件会经过 Initial、Main、Final 三个阶段,在父子节点之间来回传递,组件通过消费事件来影响后续处理。
理解了这三个阶段,就能更好地处理 Compose 中的点击冲突、嵌套滚动、自定义拖拽、多指手势以及复杂组件交互。