0%

点击事件的分发

核心就是 ViewGroup 的 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
public boolean dispatchTouchEvent(MotionEvent event) {
// 省略...
final boolean isIntercept;

// 如果当前是 DOWN 事件, 或者 mFirstTouchTarget 不是 null
// 其中,mFirstTouchTarget 是指本轮触摸事件序列的上一个事件给到谁捕获了
if (actionMasked == MotionEvent.ACTION_DOWN || mFirstTouchTarget != null) {
// 如果不允许拦截,标记为不拦截
if (disallowIntercept) {
isIntercept = false;
} else {
isIntercept = onInterceptTouchEvent(event);
}
}
// 否则,表示这个事件不是 DOWN 事件,并且 mFirstTouchTarget == null
// 意味着,这轮触摸事件序列,没有已有的目标可以处理,并且也不是第一次的 DOWN 事件,
// 所及交给当前 ViewGroup 自己处理.
else {
isIntercept = true;
}

// 省略...

// 如果没有被取消,且没有被拦截
if (!isCanceled && !isIntercept) {
// 如果事件是 DOWN 事件,或者是其他条件(不用关心)
if (actionMasked == MotionEvent.ACTION_DOWN
|| ...) {
// 省略一些判断逻辑,比如 childrenCount > 0 ...

// 注意这里是倒序遍历的
for (int i = childrenCount - 1; i >=0; i--) {
// 计算要获取哪个 View
final View child = ...;

// 判断 child 是否可以接收动画,如果在动画中,是不会接收点击事件的
// 或者,点击事件是否在 child 的范围内
// 任一条件不满足,都不会分发给这个 child.
if (!child.canViewReceivePointerEvent()
|| !isTransformedTouchPointInView(event)) {
continue;
}

// 分发给 child
// dispatchTransformedTouchEvent 会调用 child.dispatchTouchEvent
if (dispatchTransformedTouchEvent(event, .., child)) {
// 省略...

// 这里 addTouchTarget 内部会对 mFirstTouchTarget 赋值
newTouchTarget = addTouchTarget(child, ..);
alreadyDispatchedToNewTouchEventTarget = true;
break;
}
}
}
}

// 如果 mFirstTouchTarget 是 null, 说明没有子 view 捕获该事件,交给自己处理
if (mFirstTouchTarget == null) {
// 传入的 child 参数是 null,会在内部间接调用到 onTouchEvent 方法;
handled = dispatchTransformedTouchEvent(event, canceled, child = null, .);
} else {
TouchTarget target = mFirstTouchTarget;
while(target != null) {
target = target.next;
// 如果这个事件已经交给 target 处理过了,不再重复处理
if (alreadyDispatchedToNewTouchEventTarget && target == newTouchTarget) {
handled = true;
}
// 否则,交给子view处理
else {
// 省略 ...
if (dispatchTransformedTouchEvent(event, target, ...)) {
handled = true;
}
}
}
}

return handled;
}

后续的 MOVE、UP 等事件的分发交给谁,取决于它们的起始事件 Down 是由谁捕获的。

什么时候会出发 CANCEL 事件?
当父视图的 onInterceptTouchEvent 先返回 false,然后在子 View 的 dispatchTouchEvent 中返回 true(表示子 View 捕获事件),关键步骤就是在接下来的 MOVE 的过程中,父视图的 onInterceptTouchEvent 又返回 true,intercepted 被重新置为 true,此时上述逻辑就会被触发,子控件就会收到 ACTION_CANCEL 的 touch 事件。

如何强制父布局不拦截点击事件?
使用 requestDisallowInterceptTouchEvent(boolean disallow) 方法。

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
public boolean dispatchTouchEvent(MotionEvent event) {
// 省略...
final boolean isIntercept;

if (disallowIntercept) {
isIntercept = false;
} else {
isIntercept = onInterceptTouchEvent(event);
}

if (!isIntercept) {
for (int i = childrenCount - 1; i >= 0; i++) {
View child = ...;
boolean consumed = dispatchTransformedTouchEvent(child);
if (consumed) {
newTouchTarget = addTouchTarget(newTouchTarget);
alreadyDispatched = true;
break;
}
}
}


boolean handled;
if (newTouchTarget == null) {
handled = dispatchTransformedTouchEvent(null);
} else {
TouchTarget next = mFirstTouchTarget;
while(next != null) {
next = next.next;
handled = dispatchTransformedTouchEvent(next);
}
}
return handled;
}