0%

事件分发机制

事件分发机制

dispatchTouchEvent

ViewGroup 的 dispatchTouchEvent 方法

伪代码

1
2
3
4
5
6
7
8
9
10
11
public boolean dispatchTouchEvent(){
boolean consume = false;
if (onInterceptTouchEvent(ev)){
consume = super.dispatchTouchEvent();
}else{
consume = child.dispatchTouchEvent(ev);
if (!consume)
consume = super.dispatchTouchEvent(ev)
}
return consume;
}

解释:
在 dispatchTouchEvent 方法中,onInterceptTouchEvent 方法会判断当前 ViewGroup 是否会拦截,若拦截,则交给自己处理,若不拦截,则交给子 view。若子 view 不能处理,则还是由自己来处理。
上面的只是一个概述,还有很多细节要去深究

源代码

dispatchTouchEvent
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
if (actionMasked == MotionEvent.ACTION_DOWN) {
// 在action_down事件时, 会重置所有的标志位, mFirstTouchTarget会置为null
cancelAndClearTouchTargets(ev);
resetTouchState();
}

final boolean intercepted;
// mFirstTouchTarget:子元素成功处理时,mFirstTouchTarget 会被赋值病指向子元素
// mFirstTouchTarget赋值在addTouchTarget方法中
if (actionMasked == MotionEvent.ACTION_DOWN || mFirstTouchTarget != null) {
final boolean disallowIntercept = (mGroupFlags & FLAG_DISALLOW_INTERCEPT) != 0;
if (!disallowIntercept) {
intercepted = onInterceptTouchEvent(ev);
ev.setAction(action);
} else {
intercepted = false;
}
} else {
// mFirstTouchTarget == null, 并且这不是一个down事件, 说明
// down事件没有被ViewGroup拦击, down事件也没有被子view消费,
// 后续的事件也没有被子View消费, 所以后续的事件交给ViewGroup
intercepted = true;
}

.... //省略代码

// Check for cancelation.
final boolean canceled = resetCancelNextUpFlag(this)
|| actionMasked == MotionEvent.ACTION_CANCEL;
// Update list of touch targets for pointer down, if needed.
final boolean split = (mGroupFlags & FLAG_SPLIT_MOTION_EVENTS) != 0;
TouchTarget newTouchTarget = null;
boolean alreadyDispatchedToNewTouchTarget = false;

if (!canceled && !intercepted) {
... //省略部分代码
if (actionMasked == MotionEvent.ACTION_DOWN
|| (split && actionMasked == MotionEvent.ACTION_POINTER_DOWN)
|| actionMasked == MotionEvent.ACTION_HOVER_MOVE) {
...
for (int i = childrenCount - 1; i >= 0; i--) {

... //省略部分代码

newTouchTarget = getTouchTarget(child);
if (newTouchTarget != null) {
// Child is already receiving touch within its bounds.
// Give it the new pointer in addition to the ones it is handling.
newTouchTarget.pointerIdBits |= idBitsToAssign;
break;
}

... // 省略部分代码

if (dispatchTransformedTouchEvent(ev, false, child, idBitsToAssign)) {
// Child wants to receive touch within its bounds.
// 子view消费了这个事件, 更新mFirstTouchTarget, 跳出循环

.... // 省略部分代码
newTouchTarget = addTouchTarget(child, idBitsToAssign);
alreadyDispatchedToNewTouchTarget = true;
break;
}

... //省略代码
}
}
... // 省略部分代码
}

... // 省略部分代码

// Dispatch to touch targets.
if (mFirstTouchTarget == null) {
// No touch targets so treat this as an ordinary view.
handled = dispatchTransformedTouchEvent(ev, canceled, null,
TouchTarget.ALL_POINTER_IDS);
} else {
TouchTarget target = mFirstTouchTarget;
while (target != null) {
final TouchTarget next = target.next;
if (alreadyDispatchedToNewTouchTarget && target == newTouchTarget) {
handled = true;
} else {
final boolean cancelChild = resetCancelNextUpFlag(target.child)
|| intercepted;
if (dispatchTransformedTouchEvent(ev, cancelChild,
target.child, target.pointerIdBits)) {
handled = true;
}
...
target = next;
}
}
... // 省略代码
}

return handled;
dispatchTransformedTouchEvent
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
private boolean dispatchTransformedTouchEvent(MotionEvent event, boolean cancel,
View child, int desiredPointerIdBits) {
final boolean handled;

final int oldAction = event.getAction();
// 如果cancel为true,即事件序列中途被ViewGroup拦截。 则生成 ACTION_CANCEL 事件。下面会说到
if (cancel || oldAction == MotionEvent.ACTION_CANCEL) {
event.setAction(MotionEvent.ACTION_CANCEL);
if (child == null) {
handled = super.dispatchTouchEvent(event);
} else {
handled = child.dispatchTouchEvent(event);
}
event.setAction(oldAction);
return handled;
}

...
// 在这里,如果child参数为null,则super.xxx交给ViewGroup自己处理;如果child不为空,则交给child处理
if (child == null) {
handled = super.dispatchTouchEvent(transformedEvent);
} else {
...
handled = child.dispatchTouchEvent(transformedEvent);
}

... // 省略代码
}
mFirstTarget

mFirstTouchTarget 是一个 TouchTarget 对象,通过注释的说明:”触摸目标的链接列表中的第一个触摸目标”,可以得出:mFirstTouchTarget 是”触摸目标”链表的头部。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
if (!canceled && !intercepted) {
if (actionMasked == MotionEvent.ACTION_DOWN
|| (split && actionMasked == MotionEvent.ACTION_POINTER_DOWN)
|| actionMasked == MotionEvent.ACTION_HOVER_MOVE) {

final int childrenCount = mChildrenCount;
if (newTouchTarget == null && childrenCount != 0) {
final ArrayList<View> preorderedList = buildTouchDispatchChildList();
for (int i = childrenCount - 1; i >= 0; i--) {
final int childIndex = getAndVerifyPreorderedIndex(
childrenCount, i, customOrder);
final View child = getAndVerifyPreorderedView(
preorderedList, children, childIndex);

if (dispatchTransformedTouchEvent(ev, false, child, idBitsToAssign)) {
...
newTouchTarget = addTouchTarget(child, idBitsToAssign);
alreadyDispatchedToNewTouchTarget = true;
break;
}
}
}
...
}
}

if (mFirstTouchTarget == null) {
handled = dispatchTransformedTouchEvent(ev, canceled, null,
TouchTarget.ALL_POINTER_IDS);
} else {
TouchTarget predecessor = null;
TouchTarget target = mFirstTouchTarget;
while (target != null) {
if (alreadyDispatchedToNewTouchTarget && target == newTouchTarget) {
handled = true;
} else {
final boolean cancelChild = resetCancelNextUpFlag(target.child)
|| intercepted;
if (dispatchTransformedTouchEvent(ev, cancelChild,
target.child, target.pointerIdBits)) {
handled = true;
}
if (cancelChild) {
if (predecessor == null) {
mFirstTouchTarget = next;
} else {
predecessor.next = next;
}
target.recycle();
target = next;
continue;
}
}
predecessor = target;
target = next;
}
}

addTouchTarget(..) 方法是 mFirstTouchTarget 赋值的地方。

我们看 dispatchTouchEvent 中 mFirstTouchTarget 的使用。发现它只在 MotionEvent.ACTION_DOWN 事件的时候赋值。

然后所有的事件都会判断 mFirstTouchTarget 是否为 null ,为 null 则有 ViewGroup 处理, 不为 null 则由 mFirstTouchTarget 对应的 子view 处理。
由此也就可以得出结论:

  1. 如果 子view 消费了 TOUCH_DOWN 事件,后续的事件,如果 ViewGroup 不拦截,则都交给这个 子View 尝试消费。因为mFirstTouchTarget只有在TOUCH_DOWN事件才会赋值。 如果这个 子view 对后续的某个事件不消费(onTouchEvent返回false) , 那 ViewGroup 也不会尝试消费这个事件, dispatchTouchEvent 直接返回 false
  2. 如果 子view 消费了 TOUCH_DOWN 事件,则 mFirstTouchTarget != null 成立,事件序列的后续事件只会给这个子view或者ViewGroup处理,而不可能给其他的子view
  3. 如果 子view 消费了 TOUCH_DOWN 事件,则 mFirstTouchTarget != null 成立,事件序列的后续事件会经过 ViewGroup 的 onInterceptTouchEvent(ev) 事件判断,如果 ViewGroup 要拦截。则子view会执行 mFirstTouchTarget=next 。单点触摸条件下,next为null,那实际上触发 mFirstTouchTarget = null ,ViewGroup 直接拦截,这个事件序列的后续事件都直接交给 ViewGroup 处理
addTouchTarget

mFirstTarget 赋值的地方

1
2
3
4
5
6
private TouchTarget addTouchTarget(@NonNull View child, int pointerIdBits) {
final TouchTarget target = TouchTarget.obtain(child, pointerIdBits);
target.next = mFirstTouchTarget;
mFirstTouchTarget = target;
return target;
}

需要注意的点

ActionDown
  • ActionDown 事件是一个事件序列的开始。当 ActionDown 事件被 ViewGroup 拦截,则这个事件剩下的事件将不会通过 onInterceptTouchEvent 方法,而是直接交给 ViewGroup 处理
  • ActionDown 事件一定会经过 onInterceptTouchEvent 方法, 并且在 dispatchTouchEvent 方法中每次都会触发重置标志位
ActionMove、ActionUp 等其他事件
  • 若 ActionDown 没有被 ViewGroup 拦截, 而是被子 view 消费, 则这个事件序列的其他事件每次都会经过 ViewGroup 的 onInterceptTouchEvent 方法判断是否拦截
  • 若 ActionDown 没有被 ViewGroup 拦截, 也没有被子 View 消费, 则这个事件序列的其他事件不会经过 onInterceptTouchEvent 方法, 而是直接交给 ViewGroup 处理。 ( 因为mFirstTouchTarget==null)

(以上结论是分析源代码, onInterceptTouchEvent 的执行判断条件, 推断得到)

requestDisallowInterceptTouchEvent(true)
  • 子 View 可以调用父 ViewGroup 的 requestDisallowInterceptTouchEvent(true)屏蔽父 ViewGroup 的拦截(会添加 FLAG_DISALLOW_INTERCEPT 标志)。
  • 但是,这个这种操作对 ActionDown 事件无效。因为每次 ActionDown 事件来到 ViewGruop 的 dispatch 时,会 reset 所有的标志位
mFirstTouchTarget
  1. 如果 子view 消费了 TOUCH_DOWN 事件,后续的事件,如果 ViewGroup 不拦截,则都交给这个 子View 尝试消费。因为mFirstTouchTarget只有在TOUCH_DOWN事件才会赋值。 如果这个 子view 对后续的某个事件不消费(onTouchEvent返回false) , 那 ViewGroup 也不会尝试消费这个事件, dispatchTouchEvent 直接返回 false
  2. 如果 子view 消费了 TOUCH_DOWN 事件,则 mFirstTouchTarget != null 成立,事件序列的后续事件只会给这个子view或者ViewGroup处理,而不可能给其他的子view
  3. 如果 子view 消费了 TOUCH_DOWN 事件,则 mFirstTouchTarget != null 成立,事件序列的后续事件会经过 ViewGroup 的 onInterceptTouchEvent(ev) 事件判断,如果 ViewGroup 要拦截。则子view会执行 mFirstTouchTarget=next 。单点触摸条件下,next为null,那实际上触发 mFirstTouchTarget = null ,ViewGroup 直接拦截,这个事件序列的后续事件都直接交给 ViewGroup 处理

ActionCancel 事件

上一点的第3条,原本由子view处理事件被ViewGroup拦截,则自动生成 ActionCancel 事件传递给子view

View 的 dispatchTouchEvent 方法

如果有 onTouchListener,优先执行 onTouch 方法,如果 onTouch 方法返回 true,则不执行 onTouchEvent 方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
public boolean dispatchTouchEvent(MotionEvent event) {
... // 省略代码

ListenerInfo li = mListenerInfo;
if (li != null && li.mOnTouchListener != null
&& (mViewFlags & ENABLED_MASK) == ENABLED
&& li.mOnTouchListener.onTouch(this, event)) {
result = true;
}

if (!result && onTouchEvent(event)) {
result = true;
}

... // 省略代码

return result;
}