抱歉,您的浏览器无法访问本站
本页面需要浏览器支持(启用)JavaScript
了解详情 >

基础知识

  • MotionEvent.getPointerIdBits()
  • View.hasIdentityMatrix():Returns true if the transform matrix is the identity matrix. Recomputes the matrix if necessary.
  • View.getInverseMatrix():Utility method to retrieve the inverse of the current mMatrix property. We cache the matrix to avoid recalculating it when transform properties have not changed.
  • View.mScrollX
  • View.stopNestedScroll()
  • View.CLICKABLE: 默认没有该 flag。设置方法有:布局 clickable 属性、setClickable() (setOnClickListener 会调用 setClickable())
  • View.isFocusable() isFocusableInTouchMode() isFocused() requestFocus()

MotionEvent

MotionEvent 是用于报告移动(鼠标、笔、手指、轨迹球)事件的对象。Motion Event 可能包含绝对或相对运动和其他数据,具体取决于设备的类型。

Motion Event 根据 action code 和一组 axis values 描述运动。action code 指定发生的状态变化,例如指针向下或向上。 axis values 描述位置和其他运动属性。

例如,当用户第一次触摸屏幕时,系统会使用 action code ACTION_DOWN 和一组 axis values(包括触摸的 X 和 Y 坐标以及有关接触区域的压力、大小和方向的信息)向适当的 View 传递 touch event。

一些设备可以同时报告多个移动轨迹。 多点触摸屏为每个手指发出一个移动轨迹。 生成运动轨迹的单个手指或其他对象称为 **pointer**。 Motion event 包含有关当前活动的所有 pointer 的信息,即使自上次事件传递以来其中一些 pointer 没有移动。

pointer 的数量只会随着单个 pointer 的上下移动而改变一个,除非手势被取消。

每个 pointer 都有一个唯一的 id,在它第一次下降时分配(由 ACTION_DOWNACTION_POINTER_DOWN 指示)。 pointer id 保持有效,直到 pointer 最终上升(由 ACTION_UPACTION_POINTER_UP 指示)或手势被取消(由 ACTION_CANCEL 指示)。

MotionEvent 类提供了许多方法来查询 pointer 的位置和其他属性,例如 getX(int)getY(int)getAxisValuegetPointerId(int)getToolType(int) 等。 大多数这些方法接受 pointer 索引作为参数而不是 pointer id。 事件中每个 pointer 的 pointer 索引范围从 0 到比 getPointerCount() 返回的值小一。

单个 pointer 在运动事件中出现的顺序未定义。 因此,pointer 的 pointer 索引可以从一个事件更改为下一个事件,但只要 pointer 保持活动状态,pointer 的 pointer id 就保证保持不变。 使用 getPointerId(int) 方法获取 pointer 的 pointer ID,以在手势中的所有后续运动事件中跟踪它。 然后对于连续的运动事件,使用 findPointerIndex(int) 方法获取该运动事件中给定pointer id 的 pointer索引。

主要方法

  • Activity.dispatchTouchEvent():调用以处理触摸屏事件。 可以重写它以在将所有触摸屏事件发送到 Window 之前拦截它们。 请务必为应该正常处理的触摸屏事件调用此实现。如果此事件被消费,则返回 true。
  • Activity.onTouchEvent():当触摸屏事件未被其下的任何 View 处理时调用。 这对于处理发生在 window 边界之外的触摸事件最有用,因为那里没有 view 可以接收它。如果消费了该事件,则返回 true,否则返回 false。 默认实现始终返回 false。
  • View.dispatchTouchEvent():将触摸屏运动事件向下传递给目标View,如果它是目标View,则传递给该View。如果事件被view处理了则为 true,否则为 false。
  • OnTouchListener.onTouch():在触摸事件被分发给 view 时调用。 这使 listener 有机会在目标view之前做出响应。
  • View.onTouchEvent():实现此方法来处理触摸屏运动事件。如果使用此方法检测 click 动作,建议通过实现并调用 performClick() 来执行动作。 这将确保一致的系统行为
  • ViewGroup.dispatchTouchEvent():ViewGroup 将触摸事件分发给合适的子view来处理
  • ViewGroup.onInterceptTouchEvent():实现这个方法来拦截所有的触摸屏运动事件。 这使可以在事件发送给children时查看事件,并随时掌握当前手势。使用此函数需要小心,因为它与 View.onTouchEvent(MotionEvent) 的交互相当复杂,并且使用它需要以正确的方式实现该方法和这个方法。
    事件将按以下顺序接收:
    1. 将在这里收到 down 事件
    2. down 事件将由该ViewGroup的子view处理,或者交给自己的 onTouchEvent() 方法来处理; 这意味着应该实现 onTouchEvent() 以返回 true,这样将继续看到手势的其余部分(而不是寻找父view来处理它)。 此外,通过从 onTouchEvent() 返回 true,将不会在 onInterceptTouchEvent() 中收到任何后续事件,并且所有触摸处理必须像往常一样在 onTouchEvent() 中进行。
    3. 只要从此函数返回 false,每个后续事件(直到并包括最后一个 up)将首先传递到此处,然后传递到目标的 onTouchEvent()。
    4. 如果从这里返回 true,将不会收到任何以下事件:目标view将收到相同的事件,但带有动作 MotionEvent.ACTION_CANCEL,并且所有其他事件将被传递到 onTouchEvent() 方法并且不再出现在这里。

事件传递顺序

对于一个 MotionEvent,首先会分发给 Activity,接着再将其分发给 DecorView,通过 DecorView 再将事件向下逐级分发。如果向下分发到最后一个view后事件还没被消费,则再交给Activity的onTouchEvent处理

分发过程分析

事件分发首先从 Activity.dispatchTouchEvent() 开始

1
2
3
4
5
6
7
8
9
public boolean dispatchTouchEvent(MotionEvent ev) {
if (ev.getAction() == MotionEvent.ACTION_DOWN) {
onUserInteraction();
}
if (getWindow().superDispatchTouchEvent(ev)) {
return true;
}
return onTouchEvent(ev);
}

Activity 先调用 Window 的 superDispatchTouchEvent() 来分发事件

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

Window 把事件交给 DecorView 去分发

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

DecorView 调用父类也就是 ViewGroup 的 dispatchTouchEvent() 来分发事件。

ViewGroup 的事件分发

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
public boolean dispatchTouchEvent(MotionEvent ev) {
boolean handled = false;
if (onFilterTouchEventForSecurity(ev)) { // 在 View 的 Window 被遮挡时过滤触摸
final int action = ev.getAction();
final int actionMasked = action & MotionEvent.ACTION_MASK;

// 处理初始 down 事件.
// ------------------------处理 DOWN 事件开始-----------------------
if (actionMasked == MotionEvent.ACTION_DOWN) {
// 开始新的触摸手势时丢弃所有先前的状态。 由于应用程序切换、ANR 或其他一些状态更改,框架可能已丢弃先前手势的 up or cancel 事件。
cancelAndClearTouchTargets(ev); // 如果 mFirstTouchTarget 不为 null,就遍历 mFirstTouchTarget,对每个 TouchTarget 的 View 分发 CANCEL 事件,然后把 mFirstTouchTarget 置空;
resetTouchState(); // 去除 mGroupFlags 中的 FLAG_DISALLOW_INTERCEPT;
}
// ------------------------处理 DOWN 事件结束-----------------------

// Check for interception.
final boolean intercepted;
if (actionMasked == MotionEvent.ACTION_DOWN || mFirstTouchTarget != null) { // 当 action 为 down 或 mFirstTouchTarget 不为 null 时,才会考虑是否拦截;否则,必然拦截
final boolean disallowIntercept = (mGroupFlags & FLAG_DISALLOW_INTERCEPT) != 0;
// 子 View 可以通过调用父 view 的 requestDisallowInterceptTouchEvent() 来请求禁止拦截事件
if (!disallowIntercept) {
intercepted = onInterceptTouchEvent(ev);
ev.setAction(action); // restore action in case it was changed
} else {
intercepted = false;
}
} else {
// 没有触摸目标,并且此操作不是初始 down,因此此 ViewGroup 会继续拦截触摸。
intercepted = true;
}

// 如果被拦截,则开始正常的事件分发。 此外,如果已经有一个正在处理手势的 view,请执行正常的事件分发。
if (intercepted || mFirstTouchTarget != null) {
ev.setTargetAccessibilityFocus(false);
}

// Check for cancelation. 如果 mPrivateFlags 中 有 PFLAG_CANCEL_NEXT_UP_EVENT 或者 action 为 ACTION_CANCEL,判定为已取消
final boolean canceled = resetCancelNextUpFlag(this) || actionMasked == MotionEvent.ACTION_CANCEL;

// 如果需要,更新 pointer down 的触摸目标列表。
final boolean isMouseEvent = ev.getSource() == InputDevice.SOURCE_MOUSE;
final boolean split = (mGroupFlags & FLAG_SPLIT_MOTION_EVENTS) != 0 && !isMouseEvent; // 默认包含 FLAG_SPLIT_MOTION_EVENTS;可通过 setMotionEventSplittingEnabled() 修改
TouchTarget newTouchTarget = null;
boolean alreadyDispatchedToNewTouchTarget = false;

if (!canceled && !intercepted) { // 如果未取消且未拦截
View childWithAccessibilityFocus = ev.isTargetAccessibilityFocus() ? findChildWithAccessibilityFocus() : null;

// ------------------------处理 DOWN 事件开始 或 POINTER_DOWN,HOVER_MOVE 事件-----------------------
if (actionMasked == MotionEvent.ACTION_DOWN || (split && actionMasked == MotionEvent.ACTION_POINTER_DOWN)
|| actionMasked == MotionEvent.ACTION_HOVER_MOVE) {
final int actionIndex = ev.getActionIndex(); // down 事件的 actionIndex 总为 0
final int idBitsToAssign = split ? 1 << ev.getPointerId(actionIndex) : TouchTarget.ALL_POINTER_IDS;

// 清除此 pointer ID 的早期触摸目标,以防它们变得不同步。
removePointersFromTouchTargets(idBitsToAssign);

final int childrenCount = mChildrenCount;
if (childrenCount != 0) {
final float x = isMouseEvent ? ev.getXCursorPosition() : ev.getX(actionIndex);
final float y = isMouseEvent ? ev.getYCursorPosition() : ev.getY(actionIndex);

// 找到一个可以接收事件的 child
// 构建预排序 list,子类可自定义;默认仅在 childrenCount > 1 且 hasChildWithZ() 时构建;默认使用 mChildren 中的顺序,且 Z 越小的越靠前
final ArrayList<View> preorderedList = buildTouchDispatchChildList();
// isChildrenDrawingOrderEnabled() 指示 ViewGroup 是否按照 getChildDrawingOrder(int, int) 定义的顺序绘制其子级。
final boolean customOrder = preorderedList == null && isChildrenDrawingOrderEnabled();
final View[] children = mChildren;

for (int i = childrenCount - 1; i >= 0; i--) {
final int childIndex = getAndVerifyPreorderedIndex(childrenCount, i, customOrder);
final View child = getAndVerifyPreorderedView(preorderedList, children, childIndex);

// 如果子 view 不能接收 PointerEvents 或者 转换后的 TouchPoint 不在子 view 范围内,则跳过该子 view
if (!child.canReceivePointerEvents() || !isTransformedTouchPointInView(x, y, child, null)) {
ev.setTargetAccessibilityFocus(false);
continue;
}

// 获取指定子 view 的 TouchTarget。 如果找不到则返回 null。
newTouchTarget = getTouchTarget(child);
if (newTouchTarget != null) {
// Child 已经正在其范围内接收 touch。 除了它正在处理的 pointer 之外,还给它一个新的 pointer。
newTouchTarget.pointerIdBits |= idBitsToAssign;
break;
}

resetCancelNextUpFlag(child);

// dispatchTransformedTouchEvent 将 motion event 转换为特定子 view 的坐标空间,过滤掉不相关的 pointer ID,并在必要时覆盖其 action。
// 如果 child 为 null,则假定 MotionEvent 将改为发送到此 ViewGroup。
if (dispatchTransformedTouchEvent(ev, false, child, idBitsToAssign)) { // 如果子 view 消费了 DOWN 事件,则继续
// 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():创建一个新的 TouchTarget,其 next 赋值为 mFirstTouchTarget,然后把这个新的 TouchTarget 赋值给 mFirstTouchTarget
newTouchTarget = addTouchTarget(child, idBitsToAssign);
alreadyDispatchedToNewTouchTarget = true;
break;
}

// The accessibility focus didn't handle the event, so clear
// the flag and do a normal dispatch to all children.
ev.setTargetAccessibilityFocus(false);
}
if (preorderedList != null) preorderedList.clear();
}

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 事件结束 或 POINTER_DOWN,HOVER_MOVE 事件-----------------------
}

// Dispatch to touch targets.
if (mFirstTouchTarget == null) { // 没有子 view 消费 DOWN 事件时,mFirstTouchTarget == null
// No touch targets so treat this as an ordinary view.
handled = dispatchTransformedTouchEvent(ev, canceled, null, TouchTarget.ALL_POINTER_IDS);
} else { // DOWN 事件被消费
// Dispatch to touch targets, excluding the new touch target if we already
// dispatched to it. Cancel touch targets if necessary.
TouchTarget predecessor = null;
TouchTarget target = mFirstTouchTarget;
while (target != null) {
final TouchTarget next = target.next;
if (alreadyDispatchedToNewTouchTarget && target == newTouchTarget) { // DOWN 事件
handled = true;
} else { // 其他事件
final boolean cancelChild = resetCancelNextUpFlag(target.child) || intercepted;
if (dispatchTransformedTouchEvent(ev, cancelChild, target.child, target.pointerIdBits)) { // 分发事件给子 view
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.
if (canceled || actionMasked == MotionEvent.ACTION_UP || actionMasked == MotionEvent.ACTION_HOVER_MOVE) {
resetTouchState(); // mFirstTouchTarget 置空;清除 PFLAG_CANCEL_NEXT_UP_EVENT;清除 FLAG_DISALLOW_INTERCEPT
} 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.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
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
private boolean dispatchTransformedTouchEvent(MotionEvent event, boolean cancel,
View child, int desiredPointerIdBits) {
final boolean handled;

// Canceling motions is a special case. We don't need to perform any transformations
// or filtering. The important part is the action, not the contents.
final int oldAction = event.getAction();
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;
}

// Calculate the number of pointers to deliver.
final int oldPointerIdBits = event.getPointerIdBits();
final int newPointerIdBits = oldPointerIdBits & desiredPointerIdBits;

// If for some reason we ended up in an inconsistent state where it looks like we
// might produce a motion event with no pointers in it, then drop the event.
if (newPointerIdBits == 0) {
return false;
}

// If the number of pointers is the same and we don't need to perform any fancy
// irreversible transformations, then we can reuse the motion event for this
// dispatch as long as we are careful to revert any changes we make.
// Otherwise we need to make a copy.
final MotionEvent transformedEvent;
if (newPointerIdBits == oldPointerIdBits) {
if (child == null || child.hasIdentityMatrix()) { // hasIdentityMatrix() 为 true 时,代表子 view 没有经过 translation, scale 等转换
if (child == null) {
handled = super.dispatchTouchEvent(event);
} else {
final float offsetX = mScrollX - child.mLeft;
final float offsetY = mScrollY - child.mTop;
event.offsetLocation(offsetX, offsetY);

handled = child.dispatchTouchEvent(event);

event.offsetLocation(-offsetX, -offsetY);
}
return handled;
}
transformedEvent = MotionEvent.obtain(event);
} else {
transformedEvent = event.split(newPointerIdBits);
}

// Perform any necessary transformations and dispatch.
if (child == null) {
handled = super.dispatchTouchEvent(transformedEvent);
} else {
final float offsetX = mScrollX - child.mLeft;
final float offsetY = mScrollY - child.mTop;
transformedEvent.offsetLocation(offsetX, offsetY);
if (!child.hasIdentityMatrix()) { // 使用子 view 的 Matrix 对事件坐标进行转换,使其对应子 view 的坐标轴
transformedEvent.transform(child.getInverseMatrix());
}

handled = child.dispatchTouchEvent(transformedEvent);
}

// Done.
transformedEvent.recycle();
return handled;
}

View.hasIdentityMatrix()

1
2
3
public final boolean hasIdentityMatrix() {
return mRenderNode.hasIdentityMatrix();
}

View.getInverseMatrix()

1
2
3
4
5
6
7
8
9
public final Matrix getInverseMatrix() {
ensureTransformationInfo();
if (mTransformationInfo.mInverseMatrix == null) {
mTransformationInfo.mInverseMatrix = new Matrix();
}
final Matrix matrix = mTransformationInfo.mInverseMatrix;
mRenderNode.getInverseMatrix(matrix);
return matrix;
}

View 的事件分发

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
public boolean dispatchTouchEvent(MotionEvent event) {
boolean result = false;

final int actionMasked = event.getActionMasked();
if (actionMasked == MotionEvent.ACTION_DOWN) {
// DOWN 事件时,停止嵌套滚动
stopNestedScroll();
}

if (onFilterTouchEventForSecurity(event)) {
if ((mViewFlags & ENABLED_MASK) == ENABLED && handleScrollBarDragging(event)) { // 处理鼠标拖动 ScrollBar
result = true;
}

ListenerInfo li = mListenerInfo;
if (li != null && li.mOnTouchListener != null
&& (mViewFlags & ENABLED_MASK) == ENABLED
&& li.mOnTouchListener.onTouch(this, event)) { // 1. 先调用 OnTouchListener.onTouch() 处理事件
result = true;
}

if (!result && onTouchEvent(event)) { 2. 再调用 onTouchEvent() 处理事件
result = true;
}
}

// 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.
if (actionMasked == MotionEvent.ACTION_UP ||
actionMasked == MotionEvent.ACTION_CANCEL ||
(actionMasked == MotionEvent.ACTION_DOWN && !result)) {
stopNestedScroll(); // UP 或 CANCEL 或 (DOWN 未被消费) 时,停止嵌套滚动
}

return result;
}

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
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
&& (mPrivateFlags4 & PFLAG4_ALLOW_CLICK_WHEN_DISABLED) == 0) { // View 被禁用且允许被禁用时点击时,
if (action == MotionEvent.ACTION_UP && (mPrivateFlags & PFLAG_PRESSED) != 0) {
setPressed(false);
}
mPrivateFlags3 &= ~PFLAG3_FINGER_DOWN;
// clickable 的 disabled view 仍然会消费 touch event, 但是它不会做任何处理
return clickable;
}

if (mTouchDelegate != null) {
if (mTouchDelegate.onTouchEvent(event)) {
return true;
}
}

if (clickable || (viewFlags & TOOLTIP) == TOOLTIP) {
switch (action) {
case MotionEvent.ACTION_UP: // -------------------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.
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.
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
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();
}
if (!post(mPerformClick)) {
performClickInternal();
}
}
}

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: // -------------------DOWN 事件------------------
if (event.getSource() == InputDevice.SOURCE_TOUCHSCREEN) {
mPrivateFlags3 |= PFLAG3_FINGER_DOWN;
}
mHasPerformedLongPress = false;

if (!clickable) {
checkForLongClick(
ViewConfiguration.getLongPressTimeout(),
x,
y,
TOUCH_GESTURE_CLASSIFIED__CLASSIFICATION__LONG_PRESS);
break;
}

if (performButtonActionOnTouchDown(event)) {
break;
}

// Walk up the hierarchy to determine if we're inside a scrolling container.
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) {
mPendingCheckForTap = new CheckForTap();
}
mPendingCheckForTap.x = event.getX();
mPendingCheckForTap.y = event.getY();
postDelayed(mPendingCheckForTap, ViewConfiguration.getTapTimeout());
} else {
// Not inside a scrolling container, so show the feedback right away
setPressed(true, x, y);
checkForLongClick(
ViewConfiguration.getLongPressTimeout(),
x,
y,
TOUCH_GESTURE_CLASSIFIED__CLASSIFICATION__LONG_PRESS);
}
break;

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

case MotionEvent.ACTION_MOVE: // -------------------MOVE 事件------------------
if (clickable) {
drawableHotspotChanged(x, y);
}

final int motionClassification = event.getClassification();
final boolean ambiguousGesture =
motionClassification == MotionEvent.CLASSIFICATION_AMBIGUOUS_GESTURE;
int touchSlop = mTouchSlop;
if (ambiguousGesture && hasPendingLongPressCallback()) {
if (!pointInView(x, y, touchSlop)) {
// The default action here is to cancel long press. But instead, we
// just extend the timeout here, in case the classification
// stays ambiguous.
removeLongPressCallback();
long delay = (long) (ViewConfiguration.getLongPressTimeout()
* mAmbiguousGestureMultiplier);
// Subtract the time already spent
delay -= event.getEventTime() - event.getDownTime();
checkForLongClick(
delay,
x,
y,
TOUCH_GESTURE_CLASSIFIED__CLASSIFICATION__LONG_PRESS);
}
touchSlop *= mAmbiguousGestureMultiplier;
}

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

final boolean deepPress =
motionClassification == MotionEvent.CLASSIFICATION_DEEP_PRESS;
if (deepPress && hasPendingLongPressCallback()) {
// process the long click action immediately
removeLongPressCallback();
checkForLongClick(
0 /* send immediately */,
x,
y,
TOUCH_GESTURE_CLASSIFIED__CLASSIFICATION__DEEP_PRESS);
}

break;
}

return true;
}

return false;
}

主要方法

  • Activity.dispatchTouchEvent()
  • Activity.onTouchEvent()
  • Window.superDispatchTouchEvent()
  • ViewGroup.dispatchTouchEvent()
  • ViewGroup.onInterceptTouchEvent()
  • View.dispatchTouchEvent()
  • View.onTouchListener.onTouch()
  • View.onTouchEvent()
  • TouchDelegate.onTouchEvent()
  • View.performClickInternal()
  • View.performClick()
  • View.OnClickListener.onClick()
  • View.performLongClickInternal()
  • View.onLongClickListener.onLongClick()

评论