总结,细化,推演
一、总结
四个事件,三个方法,两套机制
1.1 四个事件
- Down
- Move
- Up/Cancel
1.2 三个方法
- dispatchTouchEvent
- onInterceptTouchEvent
- onTouchEvent
1.3 两套机制
Talk is cheap, show you the code.
ViewGroup处理机制:
public boolean dispatchTouchEvent(MotionEvent ev) {
if (!onInterceptTouchEvent(ev)) { // 1. 判断是否拦截
for (int i = childrenCount - 1; i >= 0; i--) { // 2. 逆序遍历子View,寻找意欲消费该事件的子View
if (children[i].dispatchTouchEvent(ev)) {
return true;
}
}
}
return super.dispatchTouchEvent(ev); // 3. 如果没有子View消费事件,则走View处理机制
}
View处理机制:
public boolean dispatchTouchEvent(MotionEvent ev) {
if (onTouchListener != null) {
return onTouchListener.onTouch(ev) || onTouchEvent(ev);
}
return onTouchEvent(ev);
}
注意
1、上述机制是极度简化后的(后文详述)
2、ViewGroup本身也是View
二、细化
2.1 四个事件
只考虑单点触控的情况下,一次手势操作产生的事件序列如下图所示:
基本事件序列
注意
事件消费以手势为单位,如果View不消费Down事件,则它将不再收到其它事件;反之,如果它消费了Down事件,则必然以一个Up或Cancel事件结束。
2.2 三个方法
dispatchTouchEvent、onInterceptTouchEvent和onTouchEvent都接收一个TouchEvent作为参数,返回值类型都是boolean。
dispatchTouchEvent自上而下分发,onTouchEvent自下而上处理,onInterceptTouchEvent居中拦截,构成了Android事件的处理机制。
2.2.1 dispatchTouchEvent
dispatchTouchEvent定义在Activity、ViewGroup和View中,控制TouchEvent走向,其目的就是找出哪个View应该处理该TouchEvent,简言之,它用于事件的分发。
Activity.dispatchTouchEvent总是最先收到事件,经过一系列下发后,传递给布局。本文只考虑布局对手势事件的处理。
dispatchTouchEvent有几个原则非常关键
- 其对Down事件的返回值表征是否消费该事件(对其它事件的返回值无所谓)
- 自上而下分发,采用逐层负责制
- 分发之前先判断是否要拦截
2.2.2 onInterceptTouchEvent
onInterceptTouchEvent定义在ViewGroup中,用于拦截事件。
onInterceptTouchEvent被调用需要满足两个条件:
- 当前传递的是Down事件或者有子View消费了之前的Down事件
- 允许拦截
如果onInterceptTouchEvent拦截的是Down事件,Down事件不会继续向下传递;如果拦截的是Move或Up事件,则会下发Cancel事件给下层。
onInterceptTouchEvent一旦返回true,就不会再调用。
2.2.3 onTouchEvent
onTouchEvent定义在Activity、ViewGroup和View中,消费事件。
2.3 两套机制
1.3节中所示的机制确切来说是不正确的,实际的事件传递机制要复杂得多。
Android事件处理涉及三个操作:分发、拦截与消费。
- 分发
- dispatchTouchEvent负责分发
- 分发自上而下
- 分发前判断是否拦截
- dispatchTouchEvent对Down事件返回true表征View/ViewGroup消费该事件
- dispatchTouchEvent对其余事件的返回值对分发没有任何影响
- ViewGroup会逆序遍历边界包含触控点的所有子View,调用其dispatchTouchEvent判断其是否消费
- 一旦找到消费事件的子View
- ViewGroup会停止遍历
- 后续事件会直接分发给该子View,不再遍历
- 拦截
- onInterceptTouchEvent负责拦截
- onInterceptTouchEvent返回true表征拦截
- 如果ViewGroup A拦截Down事件
- 所有事件由拦截事件的ViewGroup处理,不向下分发
- 如果ViewGroup A拦截Move/Up事件
- 事件转为Cancel向下分发
- A.onTouchEvent在本次事件中不会被调用
- A.dispatchTouchEvent会将后续事件直接传递给A.onTouchEvent,不向下分发
- 一旦onInterceptTouchEvent返回true,就不会再被调用
- 消费
- OnTouchListener或onTouchEvent负责消费
- OnTouchListener优先于onTouchEvent
- 如果OnTouchListener.onTouch返回true,则onTouchEvent不会再被调用
- 如果没有注册OnTouchListener或OnTouchListener.onTouch返回false,onTouchEvent会再被调用
第三章通过推演详述这两套机制。
三、推演
假定有一个如下布局:L里包了一个R,R里包了B、T1、T2三个子View,T2叠在T1上。
L继承自LinearLayout,R继承自RelativeLayout,B继承自Button,T1、T2继承自TextView,在它们各事件方法的头部和尾部分别插入日志以观察其行为:
// public boolean onInterceptTouchEvent(MotionEvent ev) {
// public boolean onTouchEvent(MotionEvent event) {
public boolean dispatchTouchEvent(MotionEvent ev) {
L.e(MotionEvent.actionToString(ev.getAction()));
boolean result = super.dispatchTouchEvent(ev);
L.e(MotionEvent.actionToString(ev.getAction()) + " " + result);
return result;
}
默认情况
在T1的右侧(未被T2覆盖的部分)滑动手指,生成如下日志:
1. L.dispatchTouchEvent(L.java:41) ==> ACTION_DOWN
2. L.onInterceptTouchEvent(L.java:33) ==> ACTION_DOWN
3. L.onInterceptTouchEvent(L.java:35) ==> ACTION_DOWN false
4. R.dispatchTouchEvent(R.java:45) ==> ACTION_DOWN
5. R.onInterceptTouchEvent(R.java:37) ==> ACTION_DOWN
6. R.onInterceptTouchEvent(R.java:39) ==> ACTION_DOWN false
7. T1.dispatchTouchEvent(T1.java:35) ==> ACTION_DOWN
8. T1.onTouchEvent(T1.java:43) ==> ACTION_DOWN
9. T1.onTouchEvent(T1.java:45) ==> ACTION_DOWN false
10. T1.dispatchTouchEvent(T1.java:37) ==> ACTION_DOWN false
11. R.onTouchEvent(R.java:53) ==> ACTION_DOWN
12. R.onTouchEvent(R.java:55) ==> ACTION_DOWN false
13. R.dispatchTouchEvent(R.java:47) ==> ACTION_DOWN false
14. L.onTouchEvent(L.java:49) ==> ACTION_DOWN
15. L.onTouchEvent(L.java:51) ==> ACTION_DOWN false
16. L.dispatchTouchEvent(L.java:43) ==> ACTION_DOWN false
从日志中可以看出,事件传递经历了如下步骤:
- 首先产生的是Down事件,传递给L.dispatchTouchEvent
- L随之调用自身的onInterceptTouchEvent方法
- L.onInterceptTouchEvent返回false,表明L不拦截
- L于是把事件分发给R(调用R.dispatchTouchEvent)
- 同样的,R先调用自身的onInterceptTouchEvent
- R.onInterceptTouchEvent返回false,表明R也不拦截
- R于是把事件分发给T1(调用T1.dispatchTouchEvent)
- T1是普通View,不再需要向下分发,于是调用自身的onTouchEvent
(自此,事件分发完成,开始处理事件) - T1.onTouchEvent返回false
- T1.dispatchTouchEvent收到T1.onTouchEvent的返回值,同样返回false,表明T1不消费Down事件
(第9、10步参见1.3节View处理机制) - R根据T1.dispatchTouchEvent的返回值判断T1没有消费事件,于是尝试自己处理该事件,调用自身的onTouchEvent
- R.onTouchEvent返回false
- 从而导致R.dispatchTouchEvent返回false,表明R不消费Down事件
- 由于R没有消费事件,L尝试自己处理事件,调用自身的onTouchEvent
- L.onTouchEvent返回false
- 从而导致L.dispatchTouchEvent返回false
(由于Down事件没有被消费,因此它们都将不再收到其它事件,事件处理结束)
结论
1、dispatchTouchEvent自上而下分发
2、onTouchEvent自下而上消费
3、ViewGroup.dispatchTouchEvent向下分发之前,会先调用onInterceptTouchEvent
4、如果Down事件没有被消费,将不再收到其余事件(Move,Up)
消费事件
如果View是clickable的,或者View注册了OnTouchListener且其onTouch方法返回true,或者实现了自定义的返回值为true的onTouchEvent,则认为View消费了事件。
根据我们的假定,B继承自Button,它是clickable的,因此可用于做消费事件的实验,但为了同时说明ViewGroup寻找子View的机制,我们在T1上注册一个OnTouchListener回调并返回true。
在T2上点击,日志如下:
1. L.dispatchTouchEvent(L.java:41) ==> ACTION_DOWN
2. L.onInterceptTouchEvent(L.java:33) ==> ACTION_DOWN
3. L.onInterceptTouchEvent(L.java:35) ==> ACTION_DOWN false
4. R.dispatchTouchEvent(R.java:43) ==> ACTION_DOWN
5. R.onInterceptTouchEvent(R.java:35) ==> ACTION_DOWN
6. R.onInterceptTouchEvent(R.java:37) ==> ACTION_DOWN false
7. T2.dispatchTouchEvent(T2.java:35) ==> ACTION_DOWN
8. T2.onTouchEvent(T2.java:43) ==> ACTION_DOWN
9. T2.onTouchEvent(T2.java:45) ==> ACTION_DOWN false
10. T2.dispatchTouchEvent(T2.java:37) ==> ACTION_DOWN false
11. T1.dispatchTouchEvent(T1.java:35) ==> ACTION_DOWN
12. MainActivity$1.onTouch(MainActivity.java:21) ==> ACTION_DOWN true
13. T1.dispatchTouchEvent(T1.java:37) ==> ACTION_DOWN true
14. R.dispatchTouchEvent(R.java:45) ==> ACTION_DOWN true
15. L.dispatchTouchEvent(L.java:43) ==> ACTION_DOWN true
16. L.dispatchTouchEvent(L.java:41) ==> ACTION_UP
17. L.onInterceptTouchEvent(L.java:33) ==> ACTION_UP
18. L.onInterceptTouchEvent(L.java:35) ==> ACTION_UP false
19. R.dispatchTouchEvent(R.java:43) ==> ACTION_UP
20. R.onInterceptTouchEvent(R.java:35) ==> ACTION_UP
21. R.onInterceptTouchEvent(R.java:37) ==> ACTION_UP false
22. T1.dispatchTouchEvent(T1.java:35) ==> ACTION_UP
23. MainActivity$1.onTouch(MainActivity.java:21) ==> ACTION_UP true
24. T1.dispatchTouchEvent(T1.java:37) ==> ACTION_UP true
25. R.dispatchTouchEvent(R.java:45) ==> ACTION_UP true
26. L.dispatchTouchEvent(L.java:43) ==> ACTION_UP true
日志分析:
日志1至日志10与默认情况无异,下面主要说说几处不同。
11至13:事件透过T2继续向T1分发,且T1注册的回调返回了true,因此T1消费了Down事件
14至15:由于有子View消费了事件,因此L、R的onTouchEvent方法没有被调用
16至26:由于Down事件被消费,因此系统继续下发后续事件(Up)
(手指滑动小于20像素,不会产生Move事件,本例为简化没有令其产生)
另外注意,R没有将Up事件传递给T2,而是直接传给了T1
结论
1、如果消费了Down事件,则会继续收到后续事件
2、ViewGroup会以子View添加顺序的逆序查找边界包含触控点的所有子View
(2.1 事实上,如果遍历时发现某子View消费了该事件,ViewGroup会停止遍历)
3、一旦查找到消费事件的子View,后续事件会直接传递给它,不会再遍历
关于结论中2.1的论证,可以做这样一个实验:
在T2上注册OnTouchListener回调,实验方式不变,会发现事件不会传递给T1,而是由T2消费Down和Up事件。
如何判断事件是否被消费?
关于事件是否被消费,有说法说是看onTouchEvent是否返回true,这是不对的,实际上要看dispatchTouchEvent是否返回true,之所以会有这种说法,是因为默认情况下如果onTouchEvent返回true,则dispatchTouchEvent必然返回true(见1.3)。
做两个实验验证:
- 令T1.onTouchEvent返回true,而T1.dispatchTouchEvent返回false
- 令T1.onTouchEvent返回false,而T1.dispatchTouchEvent返回true
实验一日志输出除第9条外与默认情况完全一样,这里从略。
实验二日志如下:
1. L.dispatchTouchEvent(L.java:41) ==> ACTION_DOWN
2. L.onInterceptTouchEvent(L.java:33) ==> ACTION_DOWN
3. L.onInterceptTouchEvent(L.java:35) ==> ACTION_DOWN false
4. R.dispatchTouchEvent(R.java:45) ==> ACTION_DOWN
5. R.onInterceptTouchEvent(R.java:37) ==> ACTION_DOWN
6. R.onInterceptTouchEvent(R.java:39) ==> ACTION_DOWN false
7. T1.dispatchTouchEvent(T1.java:35) ==> ACTION_DOWN
8. T1.onTouchEvent(T1.java:44) ==> ACTION_DOWN
9. T1.onTouchEvent(T1.java:46) ==> ACTION_DOWN false
10. T1.dispatchTouchEvent(T1.java:38) ==> ACTION_DOWN true
11. R.dispatchTouchEvent(R.java:47) ==> ACTION_DOWN true
12. L.dispatchTouchEvent(L.java:43) ==> ACTION_DOWN true
13. L.dispatchTouchEvent(L.java:41) ==> ACTION_UP
14. L.onInterceptTouchEvent(L.java:33) ==> ACTION_UP
15. L.onInterceptTouchEvent(L.java:35) ==> ACTION_UP false
16. R.dispatchTouchEvent(R.java:45) ==> ACTION_UP
17. R.onInterceptTouchEvent(R.java:37) ==> ACTION_UP
18. R.onInterceptTouchEvent(R.java:39) ==> ACTION_UP false
19. T1.dispatchTouchEvent(T1.java:35) ==> ACTION_UP
20. T1.onTouchEvent(T1.java:44) ==> ACTION_UP
21. T1.onTouchEvent(T1.java:46) ==> ACTION_UP false
22. T1.dispatchTouchEvent(T1.java:38) ==> ACTION_UP true
23. R.dispatchTouchEvent(R.java:47) ==> ACTION_UP true
24. L.dispatchTouchEvent(L.java:43) ==> ACTION_UP true
从日志中可以看出,
(实验1)虽然T1.onTouchEvent返回true,但由于T1.dispatchTouchEvent返回false,系统仍然认为T1没有消费Down事件,因此T1不再收到后续事件。
(实验2)反之,强制使T1.dispatchTouchEvent返回true后,T1(及T1的父控件L、R)会收到后续事件(Up),
由此可知
dispatchTouchEvent返回true <=> View消费了事件
逐层负责制
为了说明事件下发是采用逐层负责制,做两个实验(点击T1):
- 令T1.dispatchTouchEvent返回false(默认情况),R.dispatchTouchEvent返回true(点击T1)
- 令R.dispatchTouchEvent返回false,L.dispatchTouchEvent返回true(点击B)
实验1日志如下:
1. L.dispatchTouchEvent(L.java:41) ==> ACTION_DOWN
2. L.onInterceptTouchEvent(L.java:33) ==> ACTION_DOWN
3. L.onInterceptTouchEvent(L.java:35) ==> ACTION_DOWN false
4. R.dispatchTouchEvent(R.java:43) ==> ACTION_DOWN
5. R.onInterceptTouchEvent(R.java:35) ==> ACTION_DOWN
6. R.onInterceptTouchEvent(R.java:37) ==> ACTION_DOWN false
7. T1.dispatchTouchEvent(T1.java:35) ==> ACTION_DOWN
8. T1.onTouchEvent(T1.java:43) ==> ACTION_DOWN
9. T1.onTouchEvent(T1.java:45) ==> ACTION_DOWN false
10. T1.dispatchTouchEvent(T1.java:37) ==> ACTION_DOWN false
11. R.onTouchEvent(R.java:52) ==> ACTION_DOWN
12. R.onTouchEvent(R.java:54) ==> ACTION_DOWN false
13. R.dispatchTouchEvent(R.java:46) ==> ACTION_DOWN true
14. L.dispatchTouchEvent(L.java:43) ==> ACTION_DOWN true
15. L.dispatchTouchEvent(L.java:41) ==> ACTION_UP
16. L.onInterceptTouchEvent(L.java:33) ==> ACTION_UP
17. L.onInterceptTouchEvent(L.java:35) ==> ACTION_UP false
18. R.dispatchTouchEvent(R.java:43) ==> ACTION_UP
19. R.onTouchEvent(R.java:52) ==> ACTION_UP
20. R.onTouchEvent(R.java:54) ==> ACTION_UP false
21. R.dispatchTouchEvent(R.java:46) ==> ACTION_UP true
22. L.dispatchTouchEvent(L.java:43) ==> ACTION_UP true
实验2日志如下:
1. L.dispatchTouchEvent(L.java:41) ==> ACTION_DOWN
2. L.onInterceptTouchEvent(L.java:33) ==> ACTION_DOWN
3. L.onInterceptTouchEvent(L.java:35) ==> ACTION_DOWN false
4. R.dispatchTouchEvent(R.java:43) ==> ACTION_DOWN
5. R.onInterceptTouchEvent(R.java:35) ==> ACTION_DOWN
6. R.onInterceptTouchEvent(R.java:37) ==> ACTION_DOWN false
7. B.dispatchTouchEvent(B.java:35) ==> ACTION_DOWN
8. B.onTouchEvent(B.java:43) ==> ACTION_DOWN
9. B.onTouchEvent(B.java:45) ==> ACTION_DOWN true
10. B.dispatchTouchEvent(B.java:37) ==> ACTION_DOWN true
11. R.dispatchTouchEvent(R.java:46) ==> ACTION_DOWN false
12. L.onTouchEvent(L.java:50) ==> ACTION_DOWN
13. L.onTouchEvent(L.java:52) ==> ACTION_DOWN false
14. L.dispatchTouchEvent(L.java:44) ==> ACTION_DOWN true
15. L.dispatchTouchEvent(L.java:41) ==> ACTION_UP
16. L.onTouchEvent(L.java:50) ==> ACTION_UP
17. L.onTouchEvent(L.java:52) ==> ACTION_UP false
18. L.dispatchTouchEvent(L.java:44) ==> ACTION_UP true
从实验1日志可以看出,T1没有消费Down事件,R消费了Down事件,所以Up事件继续传递给R,但R不再向T1分发。
从实验2日志可以看出,虽然B消费了Down事件,但由于R.dispatchTouchEvent返回了false,表征R没有消费Down事件,因此系统不再向R下发后续事件,也就不会向B下发后续事件。
结论
如果ViewGroup没有消费Down事件,即使其子View消费了Down事件,该ViewGroup及其子View都不会再收到后续事件。
拦截机制
做两个实验:
- (拦截Down事件)令L.onInterceptTouchEvent(ACTION_DOWN)返回true,同时L.dispatchTouchEvent(ACTION_DOWN)返回true,其它默认(点击T1)
- (拦截Move事件)令L.onInterceptTouchEvent(ACTION_MOVE)返回true,其它默认(点击B)
实验1日志输出如下:
1. L.dispatchTouchEvent(L.java:44) ==> ACTION_DOWN
2. L.onInterceptTouchEvent(L.java:33) ==> ACTION_DOWN
3. L.onInterceptTouchEvent(L.java:38) ==> ACTION_DOWN true
4. L.onTouchEvent(L.java:55) ==> ACTION_DOWN
5. L.onTouchEvent(L.java:57) ==> ACTION_DOWN false
6. L.dispatchTouchEvent(L.java:49) ==> ACTION_DOWN true
7. L.dispatchTouchEvent(L.java:44) ==> ACTION_UP
8. L.onTouchEvent(L.java:55) ==> ACTION_UP
9. L.onTouchEvent(L.java:57) ==> ACTION_UP false
10. L.dispatchTouchEvent(L.java:49) ==> ACTION_UP false
实验2日志输出如下:
1. L.dispatchTouchEvent(L.java:44) ==> ACTION_DOWN
2. L.onInterceptTouchEvent(L.java:33) ==> ACTION_DOWN
3. L.onInterceptTouchEvent(L.java:38) ==> ACTION_DOWN false
4. R.dispatchTouchEvent(R.java:43) ==> ACTION_DOWN
5. R.onInterceptTouchEvent(R.java:35) ==> ACTION_DOWN
6. R.onInterceptTouchEvent(R.java:37) ==> ACTION_DOWN false
7. B.dispatchTouchEvent(B.java:35) ==> ACTION_DOWN
8. B.onTouchEvent(B.java:43) ==> ACTION_DOWN
9. B.onTouchEvent(B.java:45) ==> ACTION_DOWN true
10. B.dispatchTouchEvent(B.java:37) ==> ACTION_DOWN true
11. R.dispatchTouchEvent(R.java:45) ==> ACTION_DOWN true
12. L.dispatchTouchEvent(L.java:46) ==> ACTION_DOWN true
13. L.dispatchTouchEvent(L.java:44) ==> ACTION_MOVE
14. L.onInterceptTouchEvent(L.java:33) ==> ACTION_MOVE
15. L.onInterceptTouchEvent(L.java:38) ==> ACTION_MOVE true
16. R.dispatchTouchEvent(R.java:43) ==> ACTION_CANCEL
17. R.onInterceptTouchEvent(R.java:35) ==> ACTION_CANCEL
18. R.onInterceptTouchEvent(R.java:37) ==> ACTION_CANCEL false
19. B.dispatchTouchEvent(B.java:35) ==> ACTION_CANCEL
20. B.onTouchEvent(B.java:43) ==> ACTION_CANCEL
21. B.onTouchEvent(B.java:45) ==> ACTION_CANCEL true
22. B.dispatchTouchEvent(B.java:37) ==> ACTION_CANCEL true
23. R.dispatchTouchEvent(R.java:45) ==> ACTION_CANCEL true
24. L.dispatchTouchEvent(L.java:46) ==> ACTION_MOVE true
25. L.dispatchTouchEvent(L.java:44) ==> ACTION_MOVE
26. L.onTouchEvent(L.java:52) ==> ACTION_MOVE
27. L.onTouchEvent(L.java:54) ==> ACTION_MOVE false
28. L.dispatchTouchEvent(L.java:46) ==> ACTION_MOVE false
29. L.dispatchTouchEvent(L.java:44) ==> ACTION_UP
30. L.onTouchEvent(L.java:52) ==> ACTION_UP
31. L.onTouchEvent(L.java:54) ==> ACTION_UP false
32. L.dispatchTouchEvent(L.java:46) ==> ACTION_UP false
实验1日志分析:
- (日志1至6)L拦截了Down事件,事件由L进行处理,不再向下分发
- (日志7至10)系统下发Up事件给L后,L不再调用自身的onInterceptTouchEvent。
实验2日志分析:
- L拦截了第一个Move事件
- 该事件转为Cancel事件,继续向下分发
- L.onTouchEvent不会被调用
- 后续事件(Move/Up)直接由L处理
- 不再调用onInterceptTouchEvent
- 也不再向下分发
结论
1、 如果ViewGroup拦截了Down事件,则该ViewGroup不向下分发任何事件,该ViewGroup会消费整个手势事件。
2、如果ViewGroup拦截了Move事件,则该事件会转为Cancel事件继续向下分发,但不会调用自身的onTouchEvent,而后续的事件则会直接有该ViewGroup处理,不在询问是否拦截,也不再向下分发
3、一旦onInterceptTouchEvent返回true,就不会再被调用
4、 如果子View没有消费事件而ViewGroup消费了Down事件,则后续不会再调用ViewGroup的onInterceptTouchEvent
网友评论
我那个结论的前提是“强制使ViewGroup不消费事件”