事件分发机制

本质是 MotionEvent 在 View 树中“从外到内下发,再从内到外回传”的责任链过程。核心是三个方法:

返回值含义:


一、传递顺序

大致顺序是:

InputManager -> ViewRootImpl -> DecorView
-> Activity.dispatchTouchEvent()
-> PhoneWindow.superDispatchTouchEvent()
-> DecorView
-> ViewGroup.dispatchTouchEvent()
-> ... -> 子 View.dispatchTouchEvent()
-> View.onTouchEvent()

简化理解就是:

Activity -> Window -> DecorView -> ViewGroup -> View

事件先由外层向内层分发;如果内层不消费,再向外层回传,最终可能交给 Activity 的 onTouchEvent()。


二、ViewGroup 的分发逻辑

ViewGroup 的 dispatchTouchEvent() 大致流程:

  1. 如果是 ACTION_DOWN,重置状态,清除之前的触摸目标。
  2. 判断是否拦截:
    • 调用 onInterceptTouchEvent()。
    • 如果返回 true,表示拦截,不再分发给子 View。
    • 如果返回 false,继续遍历子 View。
  3. 遍历子 View,通常是倒序遍历,即后添加的 View 先收到事件。
  4. 如果某个子 View 的 dispatchTouchEvent() 返回 true,说明它消费了事件,ViewGroup 记录这个子 View 为 mFirstTouchTarget。
  5. 如果没有任何子 View 消费,或者被拦截,就调用自己的 onTouchEvent()。
  6. 如果自己也不消费,返回 false,事件向上回传。

关键点:
一旦 ACTION_DOWN 被某个子 View 消费,后续的 ACTION_MOVE、ACTION_UP 会优先继续传给这个子 View,直到事件序列结束。


三、View 的处理逻辑

View 的 dispatchTouchEvent() 大致流程:

  1. 如果设置了 OnTouchListener,先调用 onTouch()。
  2. 如果 onTouch() 返回 true,事件被消费,不再调用 onTouchEvent()。
  3. 否则调用 onTouchEvent()。
  4. 如果 View 是 clickable、longClickable 等可点击状态,onTouchEvent() 通常返回 true,并在 ACTION_UP 时触发 performClick(),进而调用 OnClickListener。

所以点击事件的优先级大致是:

OnTouchListener > onTouchEvent > OnClickListener

如果 View 不可点击,默认 onTouchEvent() 可能返回 false,事件会回传给父容器。


四、事件序列与 ACTION_CANCEL

一个完整触摸事件序列通常是:

ACTION_DOWN -> ACTION_MOVE -> ... -> ACTION_UP

如果中途被父 ViewGroup 拦截,子 View 会收到:

ACTION_CANCEL

ACTION_CANCEL 表示这个事件序列不再交给子 View 处理,子 View 应该清理按压状态、取消点击等。

因此:
ACTION_DOWN 是事件序列的起点,决定后续 MOVE/UP 归谁处理。


五、onInterceptTouchEvent 的特点


六、滑动冲突解决思路

常见两种方式:

1. 外部拦截法

父 ViewGroup 重写 onInterceptTouchEvent():

2. 内部拦截法

子 View 调用:

parent.requestDisallowInterceptTouchEvent(true);

让父容器不拦截事件,子 View 自己处理滑动。当子 View 需要父容器处理时,再允许父容器拦截。

实现原理: here

复杂场景可以使用 NestedScrolling 机制。


七、总结

Android 事件分发就是:

逐层调用 dispatchTouchEvent();ViewGroup 通过 onInterceptTouchEvent() 决定是否拦截;View 通过 onTouchEvent() 决定是否消费。 谁在 ACTION_DOWN 时返回 true,谁就拥有这一整段事件序列的处理权。