Android: 事件分发

📅 2026/7/24 18:06:34 👁️ 阅读次数 📝 编程学习
Android: 事件分发

1. 事件分发 是什么?

事件分发是指 触摸事件(MotionEvent) 从屏幕产生后,系统如何将其传递给具体的 View 并决定由谁处理的过程。

本质上是 View 系统对触摸事件的一套传递与消费机制。


Android 的事件传递顺序:
Activity → ViewGroup → View

Activity → PhoneWindow → DecorView → ViewGroup → View

  • PhoneWindow 内部持有 DecorView(整个界面的根 View,继承自 FrameLayout)。
  • DecorView 的 superDispatchTouchEvent 会直接调父类 ViewGroup 的 dispatchTouchEvent,正式进入 ViewGroup 分发。

2. 三个核心方法

方法所属类作用返回值含义
dispatchTouchEvent()View / ViewGroup事件分发入口,决定事件流向true:事件被自己或子控件消耗,停止上级传递;false:自己及子控件均不处理,回传给上层
onInterceptTouchEvent()只 ViewGroup 有拦截事件,将事件拉回给自己处理true:拦截,事件交给自己的onTouchEventfalse(默认):不拦截,继续向子控件传递
onTouchEvent()View / ViewGroup实际处理触摸事件true:表示自己消耗了事件;false:未消耗,事件回传

3. 整体流程

因为 事件传递顺序 是以 Activity → ViewGroup → View,所以以这三个阶段来说明事件分发的具体流程

3.1 Activity 分发
publicbooleandispatchTouchEvent(MotionEventev){if(ev.getAction()==MotionEvent.ACTION_DOWN){onUserInteraction();}if(getWindow().superDispatchTouchEvent(ev)){returntrue;}returnonTouchEvent(ev);}

可以看出,这儿Activity.dispatchTouchEvent内部调用了windowdispatchTouchEvent方法,如果所有子View没有消费的话会调用Activity自身的onTouchEvent方法

Activity.dispatchTouchEvent() └→ getWindow().superDispatchTouchEvent(ev) // PhoneWindow └→ mDecor.superDispatchTouchEvent(ev) // DecorView └→ super.dispatchTouchEvent(ev) // 实际调的是 ViewGroup.dispatchTouchEvent()
  • 若 ViewGroup 最终消费事件 → 返回 true,事件终止。
  • 若 ViewGroup 没消费(返回 false)→ 会执行 Activity.onTouchEvent(ev)。
3.2 ViewGroup 分发

简陋流程,伪代码:

booleandispatchTouchEvent(MotionEventev){if(!disallowIntercept&&onInterceptTouchEvent(ev)){// 拦截:自己处理,调用自己的 onTouchEvent()returnsuper.dispatchTouchEvent(ev);// 实际上是 View.dispatchTouchEvent}// 不拦截:遍历所有子 View,找触摸点落在哪个子 View 上for(Viewchild:children){if(child 被触摸){if(child.dispatchTouchEvent(ev)){// 交给子 View 分发returntrue;// 子 View 消费了,自己就不处理了}}}// 子 View 都不消费,自己处理returnsuper.dispatchTouchEvent(ev);// 最终走到自己的 onTouchEvent()}

大致分为三步:

  • 拦截:重写 onInterceptTouchEvent 返回 true,事件就停止下传,自己处理。

  • 不拦截:找到具体子 View,调用子 View 的 dispatchTouchEvent。

  • 消费与冒泡:子 View 消费(返回 true)则结束;都不消费则“冒泡”回自身。

3.3 View 分发与消费
3.3.1 View 分发
booleandispatchTouchEvent(MotionEventevent){if(ENABLED&&mOnTouchListener!=null&&mOnTouchListener.onTouch(this,event)){returntrue;// onTouch 直接消费,不再往下}returnonTouchEvent(event);// 否则交给 onTouchEvent 处理}

判断当前的View有没有注册TouchListener,如果注册了,那就不往下走了,说明事件已经被消费了,如果没注册,那就继续往下走,执行onTouchEvent()来消费这个事件。

  • 若 onTouch 返回 true,事件立即被消费,不会触发 onTouchEvent,更不会触发 onClick。
  • 若 onTouch 返回 false 或没有注册,才会进入下一步:View 的消费(即 onTouchEvent)。
3.3.2 View消费
  • onTouchEvent 内部根据控件是否可点击来决定是否消费事件:
    • 可点击(CLICKABLE 或 LONG_CLICKABLE 为 true):
      • 在 ACTION_UP 时调用 performClick(),进而触发 onClick()。
      • 返回 true,表示事件被当前 View 消费。
    • 不可点击:
      • 返回 false,事件向上层传递(冒泡给父 View)。

完整执行顺序:onTouch → onTouchEvent → onClick

4. 相关结论&问题

结论:

  1. 事件序列的连续性
    • 事件序列:在一次事件分发中,以每个 DOWN 事件为开始, UP / CANCEL 事件为停止,在 DOWN -> MOVE -> MOVE -> UP / CANCEL 整个过程看做是一个事件序列
    • 一旦某个 View 决定不处理 DOWN 事件(onTouchEvent 返回 false),同一序列的后续事件(MOVE, UP)都不会再给它。
    • 某个 View 处理了 DOWN 事件,那么同序列的后续事件(MOVE, UP) 都会交给它
    • 如果 ViewGroup 拦截了非 DOWN 事件,子 View 会收到 CANCEL。
  2. ViewGroup 拦截注意事项
    • onInterceptTouchEvent 只存在于 ViewGroup,View 没有这个方法。
    • 子 View 可通过 requestDisallowInterceptTouchEvent(true) 阻止父 View 拦截。
  3. 返回值的含义
    • dispatchTouchEvent 返回 true:事件被当前层级或子层级消费。
    • dispatchTouchEvent 返回 false:自身及子层级都不处理,事件向上传递。
    • onTouchEvent 返回 true:表示当前 View 消费了事件,终止传递。
  4. onTouch 和 onClick 的关系
    • 注册了 OnTouchListener 且 onTouch() 返回 true → onClick 不会再调用。
    • 只有 onTouch() 返回 false,才会走到 onTouchEvent,最终触发 onClick。

问题:
Q1:ViewGroup 为什么没有 onTouchEvent?

有。ViewGroup 继承自 View,天然有onTouchEvent。当它拦截事件或没有子 View 消费时,就会执行自己的onTouchEvent

Q2:如何解决滑动冲突?

两种方案:

  • 外部拦截法:在父 View 的onInterceptTouchEvent中根据手势逻辑返回true/false
  • 内部拦截法:子 View 调用requestDisallowInterceptTouchEvent(true),父 View 配合不要拦截DOWN事件。

Q3:为什么推荐默认不拦截 DOWN 事件?

因为一旦父 View 拦截了DOWN,子 View 将收不到任何事件序列,后续 MOVE/UP 都不会传给它,容易出现事件丢失。

Q4:如果所有 View 都不消费事件,最终会怎样?

事件会回溯到 Activity,调用Activity.onTouchEvent(),如果它也不消费,系统最终丢弃该事件。