基本概念
事件类型
主要有如下三种:
1 | - MotionEvent.ACTION_DOWN |
事件处理 API
1 | - Activity.dispatchTouchEvent() |
主要是描述 Touch 事件的分发,拦载,响应三者的关系。 Touch 事件发生时,会由根元素逐级分发直到最内层 View,然后反过来逐级响应该事件。
名词解释
dispatchTouchEvent()
分发:该方法封装了事件分发的整个过程,是事件分发的调度者和指挥官。
onInterceptTouchEvent()
拦载:该方法表示是否拦截事件,只有 ViewGroup 有该回调。
返回 true 表示拦截,返回 false 或者 super.onInterceptTouchEvent 表示不拦截
onTouchEvent()
响应:该方法表示响应并处理事件
消费:表示事件不再继续逐级向下传递分发,或者不再继续逐级向上响应。也就是事件在某个方法中被消费后,该方法就是整个分发、拦载、响应流程的终点。
处理顺序
分发顺序为逐级向下:dispatchTouchEvent: Activity(Window) -> ViewGroup -> View
响应顺序为逐级向上:onTouchEvent: View -> ViewGroup --> Activity
事件分发流程
DOWN 事件 U 型流程图
流程图是针对 ACTION_DOWN 事件的分析,而 ACTION_MOVE/ACTION_UP 和事件的消费有关,放到后面分析。
事件的正常流程是:由上向下分发,并由下向上响应的一个 U 型图。箭头上的值表示返回值,其中 super 表示返回父类实现。

流程图总结
- 所有
API返回值都为super,表示事件能走完整个U型流程。 - 分发和响应只要返回值为
true都表示消费该事件,流程不再继续流转。 Activity中的分发和响应
分发:只有返回 super 才能逐级向下分发,true/false 都表示事件最终被消费。
响应:不管返回什么值,都表示事件被最终消费。
ViewGroup和View的分发和响应返回false,都表示流转到上一级执行onTouchEvent(响应)。- 拦载
返回true表示拦载,事件不会继续分发, 当前ViewGroup直接响应事件,U型流程继续向前走。
返回false或者super表示不拦载,事件继续向下分发。
MOVE 和 UP 事件流程
ACTION_MOVE/ACTION_UP 和 ACTION_DOWN 事件的消费有关。在 U 型图中,ACTION_DOWN 事件在哪一层被消费,ACTION_MOVE/ACTION_UP 就只能到达这一层。
- 分发
如果DOWN事件被分发消费,UP事件只能逐级向下分发到这一层。 - 响应
如果DOWN事件被响应后消费,UP事件逐级向下分发到这一层,并在这一层响应后直接被消费。
示例 1
View 分发时消费 DOWN 事件。View 在 dispatchTouchEvent 中消费 DOWN 事件, MVOE/UP 也只能分发到 CustomView 这一层。

示例 2
ViewGroup 响应时消费 DOWN 事件。ViewGroup 在 onTouchEvent 中消费 DOWN 事件, MOVE/UP 只能分发到这一层,并在这一层响应后直接消费。

示例 Log 分析
示例结构图
CustomView :为自定义 ViewCustomLayout :为自定义 ViewGroupCustomLayout2 :继承 LinearLayout

示例完整布局文件:
1 |
|
正常流程,不做任何处理
所有 API 都返回的是 super.***,走完整个 U 型图的流程。如下示例为点击 CustomView 后,触发了事件逐级向下分发、拦载、逐级向上响应的完整流程,包含 ACTION_DOWN 和 ACTION_UP 事件。
1 | // 处理 ACTION_DOWN 事件,Activity 开始分发事件 |
dispatchTouchEvent 的返回值
- 当层返回
super.dispatchTouchEvent
不影响事件分发和响应,和正常流程完全一样 - 当层返回
false
分发:表示事件不再逐级向下传递分发。
响应:同时该层也不会响应事件,该层(不含)逐级向上所有层都响应事件。
如下示例为在 CustomLayout2 层直接返回 false:
1 | // 处理 ACTION_DOWN 事件 |
- 当层返回
true
表示该层消费了这次事件。
分发:事件不会继续在整个流程中传递分发
响应:所有层都不响应该事件(因为分发流程未走完,所以响应流程无法开始)
如下示例为在最底层的 CustomView 中直接返回 true
1 | // 处理 ACTION_DOWN 事件 |
onInterceptTouchEvent 的返回值
无法消费事件。
当层返回
false或者super.onInterceptTouchEvent
表示该层不做拦载,不影响事件分发和响应,和正常流程完全一样。当层返回
true
表示该层拦载了事件。
分发:事件不再逐级向下传递。
响应:该层(含)逐级向上所有层都响应事件。
如下示例为在 CustomLayout2 中直接返回 true
1 | // 处理 ACTION_DOWN 事件 |
onTouchEvent 返回值
分发:返回值不影响事件传递分发。
响应:决定该层(不含)逐级向上所有层是否响应事件。
当层返回
false或者super.onTouchEvent
表示不影响事件分发和响应,和正常流程完全一样。如果直接返回false仅仅是比super.onTouchEvent少调用一次父类的onTouchEvent。当层返回
true
表示该层消费了事件,即该层(不含)逐级向上所有层都不响应事件了。
该层(含)及之下层已经响应了事件。
如下示例为CustomLayout2中返回true:
1 | // 处理 ACTION_DOWN 事件 |
CustomLayout2 继承 CustomLayout
如果 CustomLayout2 继承 CustomLayout ,则在调用 supder.*** 会多执行一次 CustomLayout 相关方法。
- 正常流程
Log分析
1 | // 事件正常下发 |
CustomLayout的onTouchEvent返回true时
1 | // 事件正常下发 |
事件分发及响应的部分源码分析
Activity事件分发源码
1 | public boolean dispatchTouchEvent(MotionEvent ev) { |
大体的分发过程为:首先传递到 Activity,然后传给了 Activity 依附的 Window,接着由 Window 传给视图的顶层 View 也就是 DecorView,最后由 DecorView 向整个 ViewTree 分发。
在 getWindow().superDispathTouchEvent 就是用来分发事件到 DecorView 中。如果整个 ViewTree 分发没有消费事件,会调用 Activity 的 onTouchEvent。
ViewGroup和View事件分发伪代码
1 | // 点击事件产生后,会直接调用 dispatchTouchEvent() 方法 |
常见监听事件的消费
onTouch 监听
- 事件监听
1 | view.setOnTouchListener(new View.OnTouchListener(){ |
onTouch返回值
返回false表示不影响整个事件分发响应流程。
返回true表示dispatchTouchEvent分发消费这次事件。
对应的Log打印:
1 | // CustomView 监听 onTouch 事件后的分发流程 |
- 源码分析
源码可以看出,onTouch回调是在dispatchTouchEvent中调用的,所以返回true时,表示分发消费了事件。
1 | public boolean dispatchTouchEvent(MotionEvent event) { |
onLongClick 监听
- 事件监听
1 | view.setOnLongClickListener(new View.OnLongClickListener(){ |
onLongClick返回值
不管返回true还是false,都表示在onTouchEvent响应中消费该事件,并且是在ACTION_DOWN中执行onLongClick回调。分析见下面onTouchEvent部分源码。对应的Log打印:
1 | 17:14: 3237/com.yD/:ViewEventActivity:: dispatchTouchEvent: ev MotionEvent { action=ACTION_DOWN...} |
onClick 监听
- 事件监听
1 | view.setOnClickListener(new View.OnClickListener(){ |
- 事件消费
在onTouchEvent响应中消费该事件,并且是在ACTION_UP中执行onClick回调。对应的 Log 打印:
1 | // CustomView 监听 onClick 事件后的分发流程 |
- 源码分析
参考如下源码,onLongClick和onClick事件都是在onTouchEvent响应中回调了监听,其中:onLongClick是在ACTION_DOWN中处理监听回调onClick是在ACTION_UP中处理监听回调
1 | public boolean onTouchEvent(MotionEvent event) { |
ViewGroup 中拒绝拦载事件
可以通过设置 FLAG_DISALLOW_INTERCEPT 来要求 ViewGroup 拒绝拦载事件,源码如下:
1 | // ViewGroup.java |
通过 requestDisallowInterceptTouchEvent 设置好 FLAG 后,在 ViewGroup 的事件分发机制中可以看到:
1 | // ViewGroup.java |
参考链接
- http://www.jianshu.com/p/e99b5e8bd67b
- http://www.jianshu.com/p/7daf0feb6c2d
- http://blog.csdn.net/guolin_blog/article/details/9097463/
- http://blog.csdn.net/guolin_blog/article/details/9153747/
- http://www.cnblogs.com/duoduohuakai/p/3996385.html
- http://www.cnblogs.com/sunzn/archive/2013/05/10/3064129.html
- http://blog.csdn.net/carson_ho/article/details/54136311