Android事件分发机制

Android事件分发机制

想要了解Android的事件分发机制,首先需要知道事件是从哪里开始的。从之前的一篇文章View是如何被添加到屏幕上的的最后一幅图,可以知道,我们打开一个界面后,界面的层级从顶层开始是

Activity->PhoneWindow->DecorView->…

所以当我们手指点击到屏幕上之后,事件的分发也是在Activity中开始。执行Activity中的dispatchTouchEvent方法那就从这里开始看

1
2
3
4
5
6
7
8
9
10
11
12
13
14
public boolean dispatchTouchEvent(MotionEvent ev) {
//如果是DOWN事件
if (ev.getAction() == MotionEvent.ACTION_DOWN) {
//此方法里面是空的,可以重写此方法
onUserInteraction();
}
//如果getWindow().superDispatchTouchEvent(ev)返回true
//那么dispatchTouchEvent方法就返回true
//反之就执行activity的onTouchEvent方法
if (getWindow().superDispatchTouchEvent(ev)) {
return true;
}
return onTouchEvent(ev);
}

首先判断是不是DOWN事件,如果是执行onUserInteraction(),这个方法里面啥也没有,我们可以重写这个方法来实现我们的关于DOWN事件的逻辑。

然后调用getWindow().superDispatchTouchEvent(ev),如果它返回true,那么dispatchTouchEvent就返回true。如果返回false,就执行activity的onTouchEvent方法。

Activity的onTouchEvent方法很简单,如果一个事件没有被Activity下的任何一个veiw接受
就结束返回true,只有点击到Window外面才会返回true,一般情况下都返回false。

1
2
3
4
5
6
7
8
public boolean onTouchEvent(MotionEvent event) {
if (mWindow.shouldCloseOnTouch(this, event)) {
finish();
return true;
}

return false;
}

下面来看看getWindow().superDispatchTouchEvent(ev)这个方法,getWindow()方法返回一个Window对象,它是一个抽象类,只有一个子类那就是PhoneWindow,所以无PhoneWindow中查看superDispatchTouchEvent(ev)方法

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

mDecor是一个DecorView对象,public class DecorView extends FrameLayout implements RootViewSurfaceTaker, WindowCallbacks,DecorView继承自FrameLayout,所以它也是一个ViewGroup它是我们一个页面的最顶级的View。点进DecorView中可以看到

1
2
3
public boolean superDispatchTouchEvent(MotionEvent event) {
return super.dispatchTouchEvent(event);
}

它直接调用了其父类的dispatchTouchEvent方法,继续跟进就进入到了ViewGroup中的dispatchTouchEvent方法了。

现在我们知道,当我们点击屏幕的时候,事件通过Activity传递到PhoneWindow在传递到ViewGroup中开始真正的分发。下面开始查看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
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
public boolean dispatchTouchEvent(MotionEvent ev) {
if (mInputEventConsistencyVerifier != null) {
mInputEventConsistencyVerifier.onTouchEvent(ev, 1);
}

// If the event targets the accessibility focused view and this is it, start
// normal event dispatch. Maybe a descendant is what will handle the click.
if (ev.isTargetAccessibilityFocus() && isAccessibilityFocusedViewOrHost()) {
ev.setTargetAccessibilityFocus(false);
}

boolean handled = false;
//安全验证
if (onFilterTouchEventForSecurity(ev)) {
final int action = ev.getAction();
final int actionMasked = action & MotionEvent.ACTION_MASK;

//开始处理一个DOWN事件
if (actionMasked == MotionEvent.ACTION_DOWN) {
//当开始处理一个DOWN事件的时候,清除掉之前的所有的事件和标志位
cancelAndClearTouchTargets(ev);
resetTouchState();
}

//定义一个boolean类型的变量记录是否拦截事件
final boolean intercepted;
//如果是DOWN事件并且当前触摸的对象mFirstTouchTarget不为空
if (actionMasked == MotionEvent.ACTION_DOWN
|| mFirstTouchTarget != null) {
//disallowIntercept可以拦截除了Down事件以外的事件,因为前面的DOWN中清空了所有标志位
//FLAG_DISALLOW_INTERCEPT可以通过requestDisallowInterceptTouchEvent方法来更改。一般是子view调用这个方法。
final boolean disallowIntercept = (mGroupFlags & FLAG_DISALLOW_INTERCEPT) != 0;
if (!disallowIntercept) {
//onInterceptTouchEvent默认返回false,不拦截事件
intercepted = onInterceptTouchEvent(ev);
ev.setAction(action); // restore action in case it was changed
} else {
intercepted = false;
}
} else {
//没有触摸目标或者不是DOWN事件,说明拦截
intercepted = true;
}

// 如果拦截了,就进行正常的事件分发
// a view that is handling the gesture, do normal event dispatch.
if (intercepted || mFirstTouchTarget != null) {
ev.setTargetAccessibilityFocus(false);
}

// 检查是否取消
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) {

View childWithAccessibilityFocus = ev.isTargetAccessibilityFocus()
? findChildWithAccessibilityFocus() : null;

if (actionMasked == MotionEvent.ACTION_DOWN
|| (split && actionMasked == MotionEvent.ACTION_POINTER_DOWN)
|| actionMasked == MotionEvent.ACTION_HOVER_MOVE) {
final int actionIndex = ev.getActionIndex(); // always 0 for down
final int idBitsToAssign = split ? 1 << ev.getPointerId(actionIndex)
: TouchTarget.ALL_POINTER_IDS;

//清理指针id防止不同步
removePointersFromTouchTargets(idBitsToAssign);

final int childrenCount = mChildrenCount;
//如果新的触摸对象为空并且子view个数大于0
if (newTouchTarget == null && childrenCount != 0) {
final float x = ev.getX(actionIndex);
final float y = ev.getY(actionIndex);
//找到一个可以接收事件的子节点
// Scan children from front to back.
final ArrayList<View> preorderedList = buildTouchDispatchChildList();
final boolean customOrder = preorderedList == null
&& isChildrenDrawingOrderEnabled();
final View[] children = mChildren;
//倒叙遍历子view
for (int i = childrenCount - 1; i >= 0; i--) {
final int childIndex = getAndVerifyPreorderedIndex(
childrenCount, i, customOrder);
final View child = getAndVerifyPreorderedView(
preorderedList, children, childIndex);

//如果view是可获取焦点的
if (childWithAccessibilityFocus != null) {
//当前view不可获取焦点
if (childWithAccessibilityFocus != child) {
continue;
}
childWithAccessibilityFocus = null;
i = childrenCount - 1;
}
//如果view是不可见状态或者在执行动画,或者触摸范围不是在view的范围之内,就跳出循环继续循环下面的
if (!canViewReceivePointerEvents(child)
|| !isTransformedTouchPointInView(x, y, child, null)) {
ev.setTargetAccessibilityFocus(false);
continue;
}

//找到当前触摸的view
newTouchTarget = getTouchTarget(child);
if (newTouchTarget != null) {
//子view在触摸范围内,在给它一个标志位
newTouchTarget.pointerIdBits |= idBitsToAssign;
break;
}

resetCancelNextUpFlag(child);
//dispatchTransformedTouchEvent分发事件,如果child不为null,就调用child的dispatchTouchEvent方法,
//child为null就调用父类的dispatchTouchEvent方法。
//返回值就是子veiw是否处理事件
if (dispatchTransformedTouchEvent(ev, false, child, idBitsToAssign)) {
// Child wants to receive touch within its bounds.
mLastTouchDownTime = ev.getDownTime();
if (preorderedList != null) {
// childIndex points into presorted list, find original index
for (int j = 0; j < childrenCount; j++) {
if (children[childIndex] == mChildren[j]) {
mLastTouchDownIndex = j;
break;
}
}
} else {
mLastTouchDownIndex = childIndex;
}
mLastTouchDownX = ev.getX();
mLastTouchDownY = ev.getY();
//addTouchTarget方法给mFirstTouchTarget赋值,addTouchTarget指向当前的子view。
//所以如果所有子view都不消耗事件,mFirstTouchTarget就为null
newTouchTarget = addTouchTarget(child, idBitsToAssign);
alreadyDispatchedToNewTouchTarget = true;
break;
}
ev.setTargetAccessibilityFocus(false);
}
if (preorderedList != null) preorderedList.clear();
}

if (newTouchTarget == null && mFirstTouchTarget != null) {
newTouchTarget = mFirstTouchTarget;
while (newTouchTarget.next != null) {
newTouchTarget = newTouchTarget.next;
}
newTouchTarget.pointerIdBits |= idBitsToAssign;
}
}
}

// mFirstTouchTarget == null表示没有子veiw消耗事件
if (mFirstTouchTarget == null) {
// 就把它当成一个普通的veiw来执行dispatchTransformedTouchEvent方法
//第三个参数传null,进去后会调用父类的dispatchTouchEvent方法,
//最终调用onTouchEvent方法来处理事件
handled = dispatchTransformedTouchEvent(ev, canceled, null,
TouchTarget.ALL_POINTER_IDS);
} else {
TouchTarget predecessor = null;
TouchTarget target = mFirstTouchTarget;
while (target != null) {
final TouchTarget next = target.next;
//如果DOWN事件处理完毕
if (alreadyDispatchedToNewTouchTarget && target == newTouchTarget) {
handled = true;
} else {
final boolean cancelChild = resetCancelNextUpFlag(target.child)
|| intercepted;
除了DOWN事件的其余事件分发给子view处理
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;
}
}

if (canceled
|| actionMasked == MotionEvent.ACTION_UP
|| actionMasked == MotionEvent.ACTION_HOVER_MOVE) {
resetTouchState();
} else if (split && actionMasked == MotionEvent.ACTION_POINTER_UP) {
final int actionIndex = ev.getActionIndex();
final int idBitsToRemove = 1 << ev.getPointerId(actionIndex);
removePointersFromTouchTargets(idBitsToRemove);
}
}

if (!handled && mInputEventConsistencyVerifier != null) {
mInputEventConsistencyVerifier.onUnhandledEvent(ev, 1);
}
return handled;
}

上面就是ViewGroup的事件分发的流程,重要部分都做了注释,总结一下就是,dispatchTouchEvent方法中,onInterceptTouchEvent方法是否拦截事件,默认不拦截,想要拦截我们需要重写此方法。如果拦截就通过dispatchTransformedTouchEvent方法调用自身的onTouchvent()方法,否则就还是通过dispatchTransformedTouchEvent方法调用子view的dispatchTouchEvent方法。如果子view还是ViewGroup,那么重复上面的调用流程,如果子view是View,那么执行View的dispatchTouchEvent方法。下面去看一下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
public boolean dispatchTouchEvent(MotionEvent event) {
//焦点的判断
if (event.isTargetAccessibilityFocus()) {
// We don't have focus or no virtual descendant has it, do not handle the event.
if (!isAccessibilityFocusedViewOrHost()) {
return false;
}
// We have focus and got the event, then use normal event dispatch.
event.setTargetAccessibilityFocus(false);
}

boolean result = false;

if (mInputEventConsistencyVerifier != null) {
mInputEventConsistencyVerifier.onTouchEvent(event, 0);
}

final int actionMasked = event.getActionMasked();
//5.0以后的嵌套滑动
if (actionMasked == MotionEvent.ACTION_DOWN) {
// Defensive cleanup for new gesture
stopNestedScroll();
}
//安全判断
if (onFilterTouchEventForSecurity(event)) {
if ((mViewFlags & ENABLED_MASK) == ENABLED && handleScrollBarDragging(event)) {
result = true;
}
//我们是否设置了mOnTouchListener,如果设置了执行它的onTouch方法
ListenerInfo li = mListenerInfo;
if (li != null && li.mOnTouchListener != null
&& (mViewFlags & ENABLED_MASK) == ENABLED
&& li.mOnTouchListener.onTouch(this, event)) {
result = true;
}

//执行onTouchEvent方法。到这里我们知道没如果我们设置了一个view的
//mOnTouchListener方法,那么先执行它的onTouch方法,返回false才执行onTouchEvent
if (!result && onTouchEvent(event)) {
result = true;
}
}

if (!result && mInputEventConsistencyVerifier != null) {
mInputEventConsistencyVerifier.onUnhandledEvent(event, 0);
}

if (actionMasked == MotionEvent.ACTION_UP ||
actionMasked == MotionEvent.ACTION_CANCEL ||
(actionMasked == MotionEvent.ACTION_DOWN && !result)) {
stopNestedScroll();
}

return result;
}

View的dispatchTouchEvent方法就简单多了,首先判断我们有没有设置OnTouchListener方法,如果设置了就执行它的Touch方法,只有Touch方法返回false的时候,才会继续去执行View的onTouchEvent方法。所以我们知道OnTouchListener的优先级高于onTouchEvent。

OK事件分发看完了,下面去View的onTouchEvent看一下事件的处理

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
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
public boolean onTouchEvent(MotionEvent event) {
final float x = event.getX();
final float y = event.getY();
final int viewFlags = mViewFlags;
final int action = event.getAction();
//是否是可点击的
final boolean clickable = ((viewFlags & CLICKABLE) == CLICKABLE
|| (viewFlags & LONG_CLICKABLE) == LONG_CLICKABLE)
|| (viewFlags & CONTEXT_CLICKABLE) == CONTEXT_CLICKABLE;

if ((viewFlags & ENABLED_MASK) == DISABLED) {
if (action == MotionEvent.ACTION_UP && (mPrivateFlags & PFLAG_PRESSED) != 0) {
setPressed(false);
}
mPrivateFlags3 &= ~PFLAG3_FINGER_DOWN;
//如果一个vew设置了点击事件或者长按事件,即使它是DISABLED的,
//也会消费这个事件,只是不响应。也就是这里直接返回true,但是下面的代码都不执行了。
return clickable;
}
//如果view设置了代理执行下面的方法
if (mTouchDelegate != null) {
if (mTouchDelegate.onTouchEvent(event)) {
return true;
}
}

//如果是可点击的,就开始处理ACTION_UP,ACTION_DOWN,ACTION_CANCEL,ACTION_MOVE事件。
if (clickable || (viewFlags & TOOLTIP) == TOOLTIP) {
switch (action) {
case MotionEvent.ACTION_UP:
mPrivateFlags3 &= ~PFLAG3_FINGER_DOWN;
if ((viewFlags & TOOLTIP) == TOOLTIP) {
handleTooltipUp();
}
if (!clickable) {
removeTapCallback();
removeLongPressCallback();
mInContextButtonPress = false;
mHasPerformedLongPress = false;
mIgnoreNextUpEvent = false;
break;
}
boolean prepressed = (mPrivateFlags & PFLAG_PREPRESSED) != 0;
if ((mPrivateFlags & PFLAG_PRESSED) != 0 || prepressed) {
boolean focusTaken = false;
if (isFocusable() && isFocusableInTouchMode() && !isFocused()) {
focusTaken = requestFocus();
}

if (prepressed) {
// 按钮在我们之前就被释放了
// 按下显示。让它显示被压的
// 现在状态(在调度单击之前)以确保用户可以看到它。
setPressed(true, x, y);
}
//如果没有处理长按事件或者长按事件返回了false
//mHasPerformedLongPress在DOWN的时候置为false,在DOWN中
//监测是否有长按事件,如果有长按事件mHasPerformedLongPress会置为true
if (!mHasPerformedLongPress && !mIgnoreNextUpEvent) {
// 如果没有长按事件,移除掉长按的回调
removeLongPressCallback();

// Only perform take click actions if we were in the pressed state
if (!focusTaken) {
//使用Runnable来执行一个点击事件,而不是直接执行
if (mPerformClick == null) {
mPerformClick = new PerformClick();
}
if (!post(mPerformClick)) {
performClick();
}
}
}

if (mUnsetPressedState == null) {
mUnsetPressedState = new UnsetPressedState();
}

if (prepressed) {
postDelayed(mUnsetPressedState,
ViewConfiguration.getPressedStateDuration());
} else if (!post(mUnsetPressedState)) {
// If the post failed, unpress right now
mUnsetPressedState.run();
}

removeTapCallback();
}
mIgnoreNextUpEvent = false;
break;

case MotionEvent.ACTION_DOWN:
if (event.getSource() == InputDevice.SOURCE_TOUCHSCREEN) {
mPrivateFlags3 |= PFLAG3_FINGER_DOWN;
}
//是否处理长按事件
mHasPerformedLongPress = false;

if (!clickable) {
//监测长按事件
checkForLongClick(0, x, y);
break;
}

if (performButtonActionOnTouchDown(event)) {
break;
}

// 是否在一个滚动容器中
boolean isInScrollingContainer = isInScrollingContainer();

// 如果是在容器中,发送一个100毫秒的延时post
//在其run方法中监测长按事件
if (isInScrollingContainer) {
mPrivateFlags |= PFLAG_PREPRESSED;
if (mPendingCheckForTap == null) {
mPendingCheckForTap = new CheckForTap();
}
mPendingCheckForTap.x = event.getX();
mPendingCheckForTap.y = event.getY();
postDelayed(mPendingCheckForTap, ViewConfiguration.getTapTimeout());
} else {
// 如果不在容器中,直接检查长按事件
//长按事件的监测,发送一个500毫秒的延时post,
//在run方法中如果检测到是长按就给上面的mHasPerformedLongPress标志位赋值为true
setPressed(true, x, y);
checkForLongClick(0, x, y);
}
break;

case MotionEvent.ACTION_CANCEL:
if (clickable) {
setPressed(false);
}
removeTapCallback();
removeLongPressCallback();
mInContextButtonPress = false;
mHasPerformedLongPress = false;
mIgnoreNextUpEvent = false;
mPrivateFlags3 &= ~PFLAG3_FINGER_DOWN;
break;

case MotionEvent.ACTION_MOVE:
if (clickable) {
drawableHotspotChanged(x, y);
}

// Be lenient about moving outside of buttons
if (!pointInView(x, y, mTouchSlop)) {
// Outside button
// Remove any future long press/tap checks
removeTapCallback();
removeLongPressCallback();
if ((mPrivateFlags & PFLAG_PRESSED) != 0) {
setPressed(false);
}
mPrivateFlags3 &= ~PFLAG3_FINGER_DOWN;
}
break;
}

return true;
}

return false;
}

onTouchEvent方法中,只要CLICKABLE和LONG_CLICKABLE有一个为true就消费这个事件,在DOWN方法中处理长按事件,在UP方法中处理点击事件。LONG_CLICKABLE默认为false,CLICKABLE的值跟具体的view有关,比如Button默认是true,TextView默认是false。通过setClickable和setLongClickable可以改变这两个值。setOnClickListener和setOnLongClickListener会把这两个值设置为true。

OK,View的事件分发的源码查看完毕,看一下流程图

总结一下:

  • 一个事件序列是从手指按下屏幕(ACTION_DOWN)开始,到手指离开屏幕(ACTION_UP)结束,中间有一系列的(ACTION_MOVE)事件,非人为的结束事件会走到ACTION_CANCEL中
  • 正常情况下一个事件序列只能由一个View拦截消耗,因为某一个View一旦决定拦截事件,那么这个事件就只能由它来处理。并且它的onInterceptTouchEvent方法不会在被调用。
  • 一旦一个View开始处理事件,如果它不消耗ACTION_DOWN事件,也就是onTouchEvent返回了false,那么同一个事件序列中的其他事件都不会再交给他处理,并且把事件重新交给它的父容器来处理。父容器的onTouchEvent方法会被调用。
  • 事件的分发过程是由外到内的,事件总是先传递到父容器,在由父容器分发给子View,子View可以通过requestDisallowInterceptTouchEvent方法来干预父容器的执行,ACTION_DOWN事件除外,因为它会在ACTION_DOWN事件中清空标志位。
  • ViewGroup中的onInterceptTouchEvent方法默认返回false,所有ViewGroup默认不拦截任何事件。而View中没有onInterceptTouchEvent方法,一旦有事件传递给它,它的onTouchEvent方法就会被调用。
  • View的onTouchEvent方法默认返回true,也就是默认处理事件,除非它是不可点击的(clickable和longClickable都为false)。LONG_CLICKABLE默认为false,CLICKABLE的值跟具体的view有关,比如Button默认是true,TextView默认是false。通过setClickable和setLongClickable可以改变这两个值。setOnClickListener和setOnLongClickListener会把这两个值设置为true。
  • View的enable属性不会影响onTouchEvent的默认返回值,即使它是enable状态,clickable和longClickable只要有一个为true,那么它的onTouchEvent就返回true。
  • onClick会响应的前提是,View是可点击的,并且收到了ACTION_DOWN和ACTION_UP事件,当长按事件返回true的时候,onClick是不会响应的。
  • onLongClick是在ACTION_DOWN中判断的,想要执行长按事件,longClickable需要为true。
# 进阶

コメント

Your browser is out-of-date!

Update your browser to view this website correctly. Update my browser now

×