View 事件分发

事件的种类

手势类型 事件名称 说明
按下 MotionEvent.ACTION_DOWN 一切事件的起点、可以有多个
移动 MotionEvent.ACTION_MOVE 手指移动时持续触发
抬起 MotionEvent.ACTION_UP 手指抬起,事件结束
取消 MotionEvent.ACTION_CANCEL 事件取消,比如ViewGroup抢夺事件

事件的传递

在手指接触屏幕那一刻,由屏幕到 Native 层,再由 Native 传到 Activity 的 dispatchTouchEvent(); 我们来看一下Activity 的代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
/**
* Called to process touch screen events. You can override this to
* intercept all touch screen events before they are dispatched to the
* window. Be sure to call this implementation for touch screen events
* that should be handled normally.
*
* @param ev The touch screen event.
*
* @return boolean Return true if this event was consumed.
*/
public boolean dispatchTouchEvent(MotionEvent ev) {
if (ev.getAction() == MotionEvent.ACTION_DOWN) {
// activity无论分发按键事件、触摸事件或者轨迹球事件都会调用Activity#onUserInteraction()
// 重写该方法可知道用户用某种方式和你正在运行的activity交互
onUserInteraction();
}
// 如果 getWindow().superDispatchTouchEvent(ev) 返回为 true 则说明事件被 Activity
// 子布局消费掉,事件被处理则返回 true
if (getWindow().superDispatchTouchEvent(ev)) {
return true;
}
// 如果 Activity 子布局没有消费该事件,则事件由 Activity 处理
return onTouchEvent(ev);
}

为方便查看,我在代码中加了注释,因为我们关心的是 View 的事件分发,所以我们关心的应该是getWindow().superDispatchTouchEvent(ev),分发的过程。跟进去我们发现 superDispatchTouchEvent(ev) 在 Window 中是一个抽象方法,在 Android 中 PhoneWindow 是 Window 的唯一实现子类。看一下 PhoneWindow#superDispatchTouchEvent(MotionEvent event) :

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

发现,PhoneWindow 去调用了 DecorView 的 superDispatchTouchEvent(MotionEvent event),再来看一下 DecorView#superDispatchTouchEvent(MotionEvent event) :

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

发现 DecorView 调用了父亲的 dispatchTouchEvent(event); ,DecorView 继承自 FrameLayout,看一下FrameLayout 发现他并没有实现该方法,所以,DecorView应该调用的是 Fragment 的父亲 ViewGroup 的dispatchTouchEvent(MotionEvent event) ,所以,事件就从屏幕最终传输到 View 和 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
214
215
216
217
@Override
public boolean dispatchTouchEvent(MotionEvent ev) {
...
boolean handled = false;
// 触摸事件安全检查,一般都没问题
if (onFilterTouchEventForSecurity(ev)) {
final int action = ev.getAction();
final int actionMasked = action & MotionEvent.ACTION_MASK;
// Handle an initial down.
if (actionMasked == MotionEvent.ACTION_DOWN) {
// Throw away all previous state when starting a new touch gesture.
// The framework may have dropped the up or cancel event for the previous gesture
// due to an app switch, ANR, or some other state change.
cancelAndClearTouchTargets(ev);
resetTouchState();
}
// Check for interception.
// 是否拦截事件标志(重要)
final boolean intercepted;
// 如果是按下事件(新的触摸动作),或者已经存在处理事件的子View
if (actionMasked == MotionEvent.ACTION_DOWN
|| mFirstTouchTarget != null) {
// 检查是否不允许拦截,由子 view requestDisallowInterceptTouchEvent(true)设置
// 相当于子 view 的尚方宝剑
final boolean disallowIntercept = (mGroupFlags & FLAG_DISALLOW_INTERCEPT) != 0;
if (!disallowIntercept) {
// 允许拦截,该 viewgroup 去尝试拦截
intercepted = onInterceptTouchEvent(ev);
ev.setAction(action); // restore action in case it was changed
} else {
intercepted = false;
}
} else {
// There are no touch targets and this action is not an initial down
// so this view group continues to intercept touches.
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;
// 如果不是取消事件,并且该事件没有被当前 view 拦截,进入分发流程
if (!canceled && !intercepted) {
...
// 如果是按下事件
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;
// Clean up earlier touch targets for this pointer id in case they
// have become out of sync.
removePointersFromTouchTargets(idBitsToAssign);
// 该 view 的直接子 view/viewgroup数量(不包括孙 view)
final int childrenCount = mChildrenCount;
// 还没有 view 接受按下事件,表示此事件为第一个按下事件
if (newTouchTarget == null && childrenCount != 0) {
final float x = ev.getX(actionIndex);
final float y = ev.getY(actionIndex);
// Find a child that can receive the event.
// Scan children from front to back.
// 创建待遍历的view列表,按照虚拟 z 轴排序,排序将影响 view 接到事件的顺序
final ArrayList<View> preorderedList = buildTouchDispatchChildList();
final boolean customOrder = preorderedList == null
&& isChildrenDrawingOrderEnabled();
final View[] children = mChildren;
// 倒序遍历每个字 view/viewgroup
for (int i = childrenCount - 1; i >= 0; i--) {
// 分别获取子 view 及其索引
final int childIndex = getAndVerifyPreorderedIndex(
childrenCount, i, customOrder);
final View child = getAndVerifyPreorderedView(
preorderedList, children, childIndex);
...
// 判断 view 可见性和是否存在动画,如果不可见或者因动画移出该区域
// 则跳过,继续遍历
if (!canViewReceivePointerEvents(child)
|| !isTransformedTouchPointInView(x, y, child, null)) {
ev.setTargetAccessibilityFocus(false);
continue;
}
// 链表中已经存在该view,说明该子view已经接收过按下(初始)
// 的触摸事件,说明这是一个多点触摸的情况,
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;
}
resetCancelNextUpFlag(child);
// 在此方法中对事件进行下发或者处理,如果为 true 说明事件被下层子 view
// 消费
if (dispatchTransformedTouchEvent(ev, false, child, idBitsToAssign)) {
// Child wants to receive touch within its bounds.
// 记录该事件消费时间和子 view 的下标
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();
// 将接受按下事件的 view 加入到 TouchTarget 链表, // 同时记录此 view到 mFirstTouchTarget = target
newTouchTarget = addTouchTarget(child, idBitsToAssign);
alreadyDispatchedToNewTouchTarget = true;
// 按下事件被消费,结束分发
break;
}
...
}
// 将事件分发 view List 清空
if (preorderedList != null) preorderedList.clear();
}// 这里if (newTouchTarget == null && childrenCount != 0) 判断结束点
// 没有找到可以消费事件的子 view,事件回归到上次最近的 Down 事件
if (newTouchTarget == null && mFirstTouchTarget != null) {
// Did not find a child to receive the event.
// Assign the pointer to the least recently added target.
newTouchTarget = mFirstTouchTarget;
while (newTouchTarget.next != null) {
newTouchTarget = newTouchTarget.next;
}
newTouchTarget.pointerIdBits |= idBitsToAssign;
}
}// down 事件
} // if (!canceled && !intercepted) { 分发事件结束
// Dispatch to touch targets.
// 经过down事件分发结束,所有子 view 都没消费该事件
if (mFirstTouchTarget == null) {
// No touch targets so treat this as an ordinary view.
// 事件直接由 viewgroup 自己消费
// child = null;dispatchTransformedTouchEvent
// 会直接去调用 view 的 disPatchTouchEvent
handled = dispatchTransformedTouchEvent(ev, canceled, null,
TouchTarget.ALL_POINTER_IDS);
} else {
// Dispatch to touch targets, excluding the new touch target if we already
// dispatched to it. Cancel touch targets if necessary.
// mFirstTouchTarget != null说明已经有 view 拿到了 down 事件,后续事件也交给它
TouchTarget predecessor = null;
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)) {
// 该TouchTarget已经在前面的情况中被分发处理了,避免重复处理
handled = true;
}
// 如果发送了取消事件,则移除分发记录(链表移动操作)
if (cancelChild) {
if (predecessor == null) {
mFirstTouchTarget = next;
} else {
predecessor.next = next;
}
target.recycle();
target = next;
continue;
}
}
predecessor = target;
target = next;
}
}
// Update list of touch targets for pointer up or cancel, if needed.
// 如果为up事件或者hover_move事件(一系列触摸事件结束),清除记录的信息
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 # onInterceptTouchEvent

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
/**
* Implement this method to intercept all touch screen motion events. This
* allows you to watch events as they are dispatched to your children, and
* take ownership of the current gesture at any point.
*
* <p>Using this function takes some care, as it has a fairly complicated
* interaction with {@link View#onTouchEvent(MotionEvent)
* View.onTouchEvent(MotionEvent)}, and using it requires implementing
* that method as well as this one in the correct way. Events will be
* received in the following order:
*
* <ol>
* <li> You will receive the down event here.
* <li> The down event will be handled either by a child of this view
* group, or given to your own onTouchEvent() method to handle; this means
* you should implement onTouchEvent() to return true, so you will
* continue to see the rest of the gesture (instead of looking for
* a parent view to handle it). Also, by returning true from
* onTouchEvent(), you will not receive any following
* events in onInterceptTouchEvent() and all touch processing must
* happen in onTouchEvent() like normal.
* <li> For as long as you return false from this function, each following
* event (up to and including the final up) will be delivered first here
* and then to the target's onTouchEvent().
* <li> If you return true from here, you will not receive any
* following events: the target view will receive the same event but
* with the action {@link MotionEvent#ACTION_CANCEL}, and all further
* events will be delivered to your onTouchEvent() method and no longer
* appear here.
* </ol>
*
* @param ev The motion event being dispatched down the hierarchy.
* @return Return true to steal motion events from the children and have
* them dispatched to this ViewGroup through onTouchEvent().
* The current target will receive an ACTION_CANCEL event, and no further
* messages will be delivered here.
*/
public boolean onInterceptTouchEvent(MotionEvent ev) {
// 在这里对事件进行判断是否需要拦截事件,如果事件需要被拦截,就会阻止事件继续下发
if (ev.isFromSource(InputDevice.SOURCE_MOUSE)
&& ev.getAction() == MotionEvent.ACTION_DOWN
&& ev.isButtonPressed(MotionEvent.BUTTON_PRIMARY)
&& isOnScrollbarThumb(ev.getX(), ev.getY())) {
return true;
}
return false;
}

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
/**
* Pass the touch screen motion event down to the target view, or this
* view if it is the target.
*
* @param event The motion event to be dispatched.
* @return True if the event was handled by the view, false otherwise.
*/
public boolean dispatchTouchEvent(MotionEvent event) {
// If the event should be handled by accessibility focus first.
// 辅助功能事件情况
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();
if (actionMasked == MotionEvent.ACTION_DOWN) {
// Defensive cleanup for new gesture
// 停止滚动(如果存在)
stopNestedScroll();
}
if (onFilterTouchEventForSecurity(event)) {
if ((mViewFlags & ENABLED_MASK) == ENABLED && handleScrollBarDragging(event)) {
result = true;
}
//noinspection SimplifiableIfStatement
// 如果注册了 onTouchListener 监听,并且监听 回调方法中 onTouch() 返回 true 时,该
// 事件被消费,此时不会再执行 onTouchEvent(event),注意:onClickListener() 会在
// onTouchEvent(event) 中调用,所以如果 onTouch() 返回 true 时 click 事件就不会接收到
ListenerInfo li = mListenerInfo;
if (li != null && li.mOnTouchListener != null
&& (mViewFlags & ENABLED_MASK) == ENABLED
&& li.mOnTouchListener.onTouch(this, event)) {
result = true;
}
// 如果事件被消费就不会再执行 onTouchEvent(event)
if (!result && onTouchEvent(event)) {
result = true;
}
}
// 一致性检验,检查事件是否被改变
if (!result && mInputEventConsistencyVerifier != null) {
mInputEventConsistencyVerifier.onUnhandledEvent(event, 0);
}
// Clean up after nested scrolls if this is the end of a gesture;
// also cancel it if we tried an ACTION_DOWN but we didn't want the rest
// of the gesture.
// 如果是up事件(系列触摸动作的终点),或者是cancel事件,或者是初始事件并且我们没对它进行处理 // (回忆前面的内容,如果没有处理down事件,那么也不会收到后面的事件),就停止滚动状态
if (actionMasked == MotionEvent.ACTION_UP ||
actionMasked == MotionEvent.ACTION_CANCEL ||
(actionMasked == MotionEvent.ACTION_DOWN && !result)) {
stopNestedScroll();
}
return result;
}

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
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
214
/**
* Implement this method to handle touch screen motion events.
* <p>
* If this method is used to detect click actions, it is recommended that
* the actions be performed by implementing and calling
* {@link #performClick()}. This will ensure consistent system behavior,
* including:
* <ul>
* <li>obeying click sound preferences
* <li>dispatching OnClickListener calls
* <li>handling {@link AccessibilityNodeInfo#ACTION_CLICK ACTION_CLICK} when
* accessibility features are enabled
* </ul>
*
* @param event The motion event.
* @return True if the event was handled, false otherwise.
*/
public boolean onTouchEvent(MotionEvent event) {
final float x = event.getX();
final float y = event.getY();
final int viewFlags = mViewFlags;
final int action = event.getAction();
// 如果该view被禁用,但是被设置为clickable或longClickable或contextClickable,仍然消耗该事件
final boolean clickable = ((viewFlags & CLICKABLE) == CLICKABLE
|| (viewFlags & LONG_CLICKABLE) == LONG_CLICKABLE)
|| (viewFlags & CONTEXT_CLICKABLE) == CONTEXT_CLICKABLE;
if ((viewFlags & ENABLED_MASK) == DISABLED) {
// 如果view被禁用且按下状态为true,取消接下状态
if (action == MotionEvent.ACTION_UP && (mPrivateFlags & PFLAG_PRESSED) != 0) {
setPressed(false);
}
mPrivateFlags3 &= ~PFLAG3_FINGER_DOWN;
// A disabled view that is clickable still consumes the touch
// events, it just doesn't respond to them.
return clickable;
}
// 如果为该view设置了触摸事件代理,则转发到代理处理触摸事件
if (mTouchDelegate != null) {
if (mTouchDelegate.onTouchEvent(event)) {
return true;
}
}
// 处理了view被禁用和设置了触摸事件代理的情况。
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) {
// take focus if we don't have it already and we should in
// touch mode.
// 如果有prepressed或pressed标志
boolean focusTaken = false;
if (isFocusable() && isFocusableInTouchMode() && !isFocused()) {
// 可以获得焦点但没有获得
// 请求获取焦点
focusTaken = requestFocus();
}
if (prepressed) {
// The button is being released before we actually
// showed it as pressed. Make it show the pressed
// state now (before scheduling the click) to ensure
// the user sees it.
// prepressed状态表示滚动容器中的点击检测还没有被消息队列执行,
// 这个时候如果抬起手指说明是一个点击事件,调用setPressed显示反馈
setPressed(true, x, y);
}
if (!mHasPerformedLongPress && !mIgnoreNextUpEvent) {
// This is a tap, so remove the longpress check
// 没有到达执行长按触发消息的时间就抬起了手指,
// 说明这是一个单击事件,移除长按触发消息
removeLongPressCallback();
// Only perform take click actions if we were in the pressed state
// 当当前view没有获取焦点时才能触发点击事件,
// 说明一个可以获取焦点的view是无法触发点击事件的
if (!focusTaken) {
// Use a Runnable and post this rather than calling
// performClick directly. This lets other visual state
// of the view update before click actions start.
if (mPerformClick == null) {
mPerformClick = new PerformClick();
}
// 使用post来将performClick动作放入队列中执行来
// 保证其他view视觉上的变化可以在点击事件被触发之前被看到
if (!post(mPerformClick)) {
performClickInternal();
}
}
}
// UnsetPressedState为Runnable消息,用于取消view的prepressed或pressed状态
if (mUnsetPressedState == null) {
mUnsetPressedState = new UnsetPressedState();
}
if (prepressed) {
// 取消prepressed状态
postDelayed(mUnsetPressedState,
ViewConfiguration.getPressedStateDuration());
} else if (!post(mUnsetPressedState)) {
// If the post failed, unpress right now
// 取消pressed状态
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;
}
// 检查Button点击事件的特殊情况
// 只是处理了事件来源是鼠标的特殊情况。
if (performButtonActionOnTouchDown(event)) {
break;
}
// Walk up the hierarchy to determine if we're inside a scrolling container.
// 向上遍历view以检查是否处在一个可滚动的容器中
boolean isInScrollingContainer = isInScrollingContainer();
// For views inside a scrolling container, delay the pressed feedback for
// a short period in case this is a scroll.
// 如果是在滚动容器中,稍延迟触摸反馈来应对这是一个滚动操作的情况
if (isInScrollingContainer) {
mPrivateFlags |= PFLAG_PREPRESSED;
if (mPendingCheckForTap == null) {
// 新建一个对象用于检测单击事件
// 它是一个Runnable,用于延迟执行单击检测的任务:
mPendingCheckForTap = new CheckForTap();
}
mPendingCheckForTap.x = event.getX();
mPendingCheckForTap.y = event.getY();
// 利用消息队列来延迟发送检测单击事件的方法,
// 延迟时间为getTapTimeout设置的超时
postDelayed(mPendingCheckForTap, ViewConfiguration.getTapTimeout());
} else {
// Not inside a scrolling container, so show the feedback right away
// 没有在滚动容器中,马上显示触摸反馈,并且开始检查长按事件
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) {
// 通知可能存在的子View或drawable触摸点发生了移动。
drawableHotspotChanged(x, y);
}
// Be lenient about moving outside of buttons
// 确定触摸点在范围内
if (!pointInView(x, y, mTouchSlop)) {
// Outside button
// Remove any future long press/tap checks
// 如果移出了这个范围,首先第4行调用removeTapCall():
removeTapCallback();
removeLongPressCallback();
// 如果pressed标志位为1,那么就取消消息队列中长按触发消息,
// 同时去除pressed标志位。
if ((mPrivateFlags & PFLAG_PRESSED) != 0) {
setPressed(false);
}
mPrivateFlags3 &= ~PFLAG3_FINGER_DOWN;
}
break;
}
return true;
}
return false;
}// 总结一下,只要触摸点移动出了当前view,那么所有的点击、长按事件都不会触发,
// 但是只要移动还在view+slot范围内,那么点击长按事件还是会被触发的。

冲突案例: