湖畔镇

View的事件分发

View的事件分发

本文的起点是Activity里的dispatchTouchEvent()方法,整体的流程如下图

流程梳理

Activity.java

1
2
3
4
5
6
7
8
9
10
public boolean dispatchTouchEvent(MotionEvent ev) {
// 通知用户与Activity发生了交互
if (ev.getAction() == MotionEvent.ACTION_DOWN) {
onUserInteraction();
}
if (getWindow().superDispatchTouchEvent(ev)) {
return true;
}
return onTouchEvent(ev);
}

获得Activity的mWindow,调用其superDispatchTouchEvent()方法

Window.java

1
public abstract boolean superDispatchTouchEvent(MotionEvent event);

是一个抽象方法,手机上获得的Window都是PhoneWindow这个子类

PhoneWindow.java

1
2
3
4
@Override
public boolean superDispatchTouchEvent(MotionEvent event) {
return mDecor.superDispatchTouchEvent(event);
}

调用了mDecorsuperDispatchTouchEvent()方法

mDecor是一个DecorView对象,它是Window的顶层View,是FrameLayout的子类,DecorView里包括一个标题栏和一个内容View,Activity的setContentView()方法就是给DecorView设置内容View

DecorView.java

1
2
3
4
5
@Override
public boolean dispatchTouchEvent(MotionEvent ev) {
final Window.Callback cb = mWindow.getCallback();
return cb != null && !mWindow.isDestroyed() && mFeatureId < 0 ? cb.dispatchTouchEvent(ev) : super.dispatchTouchEvent(ev);
}

可见可以通过设置回调来拦截触摸事件,如果没有的话就调用父类ViewGroup的dispatchTouchEvent()

ViewGroup.java

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
 @Override
public boolean dispatchTouchEvent(MotionEvent ev) {
...
// 检查是否拦截
final boolean intercepted;
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 {
// 没有触摸目标并且也不是初始的DONW事件 保持拦截
intercepted = true;
}

...

// 没有取消并且没有被拦截
if (!canceled && !intercepted) {
...
// 处理DOWN事件
if (actionMasked == MotionEvent.ACTION_DOWN || (split && actionMasked == MotionEvent.ACTION_POINTER_DOWN) || actionMasked == MotionEvent.ACTION_HOVER_MOVE) {
...

// 遍历子元素 发现能接受触摸事件的View
if (newTouchTarget == null && childrenCount != 0) {
for (int i = childrenCount - 1; i >= 0; i--) {
...
final View child = getAndVerifyPreorderedView(preorderedList, children, childIndex);
newTouchTarget = getTouchTarget(child);
if (newTouchTarget != null) {
// 子元素已经在处理事件 应是多点触控的情况 添加新的指针 并跳出遍历循环
newTouchTarget.pointerIdBits |= idBitsToAssign;
break;
}

// 不然的话交由子元素处理
if (dispatchTransformedTouchEvent(ev, false, child, idBitsToAssign)) {
// 返回true则是将要处理事件
// 记录接触点 产生新的触摸对象 标记为已经分发给触摸对象
mLastTouchDownTime = ev.getDownTime();
if (preorderedList != null) {
for (int j = 0; j < childrenCount; j++) {
if (children[childIndex] == mChildren[j]) {
mLastTouchDownIndex = j;
break;
}
}
} else {
mLastTouchDownIndex = childIndex;
}
mLastTouchDownX = ev.getX();
mLastTouchDownY = ev.getY();

// 添加新的触摸对象
newTouchTarget = addTouchTarget(child, idBitsToAssign);
alreadyDispatchedToNewTouchTarget = true;
break;
}


// 没有找到可以接受事件的子元素 用最近添加的
if (newTouchTarget == null && mFirstTouchTarget != null) {
newTouchTarget = mFirstTouchTarget;
while (newTouchTarget.next != null) {
newTouchTarget = newTouchTarget.next;
}
newTouchTarget.pointerIdBits |= idBitsToAssign;
}
}
}
}

if (mFirstTouchTarget == null) {
// 这个时候还没有触摸对象 则由本View处理
handled = dispatchTransformedTouchEvent(ev, canceled, null, TouchTarget.ALL_POINTER_IDS);
} else {
...
// 把事件发送给所有的触摸对象 已处理过的除外
if (dispatchTransformedTouchEvent(ev, cancelChild, target.child, target.pointerIdBits)) {
handled = true;
}
}

// 处理UP事件
}

`ViewGroup.java

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
private boolean dispatchTransformedTouchEvent(MotionEvent event, boolean cancel, View child, int desiredPointerIdBits) {
...
if (child == null) {
// 没有可接收事件的子元素 交由父类View处理
handled = super.dispatchTouchEvent(transformedEvent);
} else {
final float offsetX = mScrollX - child.mLeft;
final float offsetY = mScrollY - child.mTop;
transformedEvent.offsetLocation(offsetX, offsetY);
if (! child.hasIdentityMatrix()) {
transformedEvent.transform(child.getInverseMatrix());
}
// 由子元素处理
handled = child.dispatchTouchEvent(transformedEvent);
}
return handled;
}

View.java

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
public boolean dispatchTouchEvent(MotionEvent event) {
...
// 先观察设置给View的点击监听器
// 如果设置了 并且标志位是可点击的 则调用监听器的onTouch()方法
// onTouch返回true则最终结果为true 其它情况结果均为false
ListenerInfo li = mListenerInfo;
if (li != null && li.mOnTouchListener != null && (mViewFlags & ENABLED_MASK) == ENABLED && li.mOnTouchListener.onTouch(this, event)) {
result = true;
}

// 只有上面的结果为false才会onTouchEvent()处理
if (!result && onTouchEvent(event)) {
result = true;
}

return result;
}

可见返回值如果为true,则表示事件已被处理不需再处理

View.java

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
public boolean onTouchEvent(MotionEvent event) {
...
final int action = event.getAction();
if (clickable || (viewFlags & TOOLTIP) == TOOLTIP) {
switch (action) {
case MotionEvent.ACTION_UP:
...
performClick();
...
break;
case MotionEvent.ACTION_MOVE:
...
break;
case MotionEvent.ACTION_CANCEL:
...
break;
case MotionEvent.ACTION_DOWN:
break;
}
return true;
}
return false;
}

会在ACTION_UP事件里处理点击事件

同样处理后返回true,否则返回false

View.java

1
2
3
4
5
6
7
8
9
10
11
12
13
public boolean performClick() {
final boolean result;
final ListenerInfo li = mListenerInfo;
if (li != null && li.mOnClickListener != null) {
playSoundEffect(SoundEffectConstants.CLICK);
li.mOnClickListener.onClick(this);
result = true;
} else {
result = false;
}
...
return result;
}

这里调用了View.OnClickListener.onClick()

总结流程

  1. 事件首先到达Activity,由dispatchTouchEvent()分发给顶层布局容器

  2. ViewGroup通过dispatchTouchEvent()进行分发

    1. 处理DOWN事件,做一些初始化
    2. 处理拦截
    3. 如果没有拦截,则遍历子元素,根据DONW事件的信息查找触摸目标,如果有则调用这个触摸目标的dispatchTouchEvent() 方法,如果是ViewGroup,则重复2,如果是View,则调用其onTouchEvent()处理事件并返回
    4. 如果没有找到触摸目标,则调用自己父类的View.dispatchTouchEvent()方法,否则在所有的触摸目标上应用触摸事件,排除已处理过的
    5. 处理UP事件,清理状态
  3. 处理返回值,如果为true,则之后的同序列事件都由本View处理,如果为false,则之后的同序列事件交给父布局处理,重复此过程直到顶层布局容器

  4. 布局都没有处理,则由Activity的onTouchEvent()处理

细节整理

拦截原理

actionMasked == MotionEvent.ACTION_DOWNmFirstTouchTarget != null两个条件满足其一的时候会判断是否拦截,前者说明DOWN事件总是判断拦截的,后者说明之前的事件已经交给子元素处理了,所以一旦DOWN事件已经被拦截,mFirstTouchTarget就是空,那么接下来的MOVE和UP事件就不会再判断拦截了,即所有一系列事件都会被拦截

下面会判断拦截标志位,这个标志位会导致父元素无法拦截除DOWN之外的事件,因为在DOWN到来时会重置标志位,如果没有禁止拦截,那么onInterceptTouchEvent()确定是否拦截,这个标记可以由子元素调用requestDisallowInterceptTouchEvent() 来设置

返回值

返回值会层层回传,直到Activity那里

Activity.java

1
2
3
4
if (getWindow().superDispatchTouchEvent(ev)) {
return true;
}
return onTouchEvent(ev);

可见如果是true,就直接返回了,如果是false,就走Activity的onTouchEvent()方法,由Activity处理

对于DOWN事件有额外的处理,DOWN事件里会查找并分发给子元素处理,如果返回的是true,则会调用addTouchTarget(),这里面会给mFirstTouchTarget赋值,返回false则不会,后面就会做判断,决定后续事件是交给子元素还是自己处理

所以返回true则由子元素处理,返回false则由自己处理

分享