基本概念
事件类型
主要有如下三种:
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
:为自定义 View
CustomLayout
:为自定义 ViewGroup
CustomLayout2
:继承 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