触摸事件分析

概述

初始化

每一个事件流,都是以ACTION_DOWN作为开始,以ACTION_UP或ACTION_CANCEL作为结束
在处理开始前,需要做一个安全处理,即是否有不可见的window覆盖其上,防止有恶意软件劫持用户输入事件

如果是DOWN事件,那么表示要开始一个新的事件流,这时就需要清理原来的状态,重新开始

拦截

接来下检查父View是否会拦截事件,如果父View希望拦截,也就是在onInterceptTouchEvent()中返回true ,那么后续将不再寻找有意愿接收事件的子View,而是将事件直接发给父View的onTouchEvent来处理

比如说,长按列表中某个项,子View会处理长按事件,长按然后开始滑动,父View会接管滑动事件,列表就开始滑动,同时子View就恢复状态。这个逻辑就是通过事件拦截实现的。

请求不拦截

当然,子View也可以要求父View不要拦截这个事件

比如说,长按列表中某个项,然后上下拖动,改变子View的顺序,这个逻辑就需要子View告知父View不要拦截处理,交给我就可以了

子View是通过requestDisallowInterceptTouchEvent()方法来请求父View不要拦截事件

然后查看这个事件是否是取消事件,取消事件的话也不会去找VIew分发了,因为这是一个结束事件,所以要通知之前分发过的view,事件已经被其他View接管

查找想要接管的View

只有事件即没有被拦截,也不是取消事件,才会找新的view接管
遍历自己的view树,然后找愿意接管事件的view,(从最接近用户的那个view开始),找到之后,标记这个view为target,接下来的事件就不在找了,直接转发给这个view,这是正常的事件流

被拦截或者是取消事件

那么对于拦截或者取消的情况呢:
如果在这之前还没有找过view接管,那么直接传给当前view处理
如果曾找过view接管,
对于拦截事件,分发给子view事件被取消了,方便子view做后续处理
对于取消事件,要分发给子view,告诉事件取消

简化模型

以上事件的分发过程,可以使用下边的简化模型来理解

1
2
3
4
5
6
7
8
9
10
11
public boolean disaptchTouchEvent(MotionEvent ev){
boolean handled = false;

if (onInterceptTouchEvent(ev)) {
handled = onTouchEvent(ev);
}else{
handled = child.dispatchTouchEvent(ev);
}

return handled;
}

流程分析

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
@Override
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;

// Handle an initial down.
if (actionMasked == MotionEvent.ACTION_DOWN) { // 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;
if (actionMasked == MotionEvent.ACTION_DOWN
|| mFirstTouchTarget != null) {// 事件流开始或者已经找到要分发的view
final boolean disallowIntercept = (mGroupFlags & FLAG_DISALLOW_INTERCEPT) != 0;
if (!disallowIntercept) { // 是否允许父view拦截
intercepted = onInterceptTouchEvent(ev); // 父View是否拦截
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;
}

// If intercepted, start normal event dispatch. Also if there is already
// a view that is handling the gesture, do normal event dispatch.
if (intercepted || mFirstTouchTarget != null) {
ev.setTargetAccessibilityFocus(false);
}

// 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;// newTarget标识是否和原来的target有变化,来判断是否已经分发过了
boolean alreadyDispatchedToNewTouchTarget = false;
if (!canceled && !intercepted) { // 不是取消事件,也不是拦截事件(如果是二者,那么不找新的target,直接进行分发,cancel分发给子view,拦截分发给自己)

// If the event is targeting accessibility focus we give it to the
// view that has accessibility focus and if it does not handle it
// we clear the flag and dispatch the event to all children as usual.
// We are looking up the accessibility focused host to avoid keeping
// state since these events are very rare.
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;

// Clean up earlier touch targets for this pointer id in case they
// have become out of sync.
removePointersFromTouchTargets(idBitsToAssign);

final int childrenCount = mChildrenCount;
if (newTouchTarget == null && childrenCount != 0) {//viewgroup是不是空的
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.
final ArrayList<View> preorderedList = buildTouchDispatchChildList();// 获取直接子View,而不是子孙View
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);

// If there is a view that has accessibility focus we want it
// to get the event first and if not handled we will perform a
// normal dispatch. We may do a double iteration but this is
// safer given the timeframe.
if (childWithAccessibilityFocus != null) {// 如果有辅助View,那么我们就要确保先找到这个辅助View,然后从头开始正常分发
if (childWithAccessibilityFocus != child) {
continue;
}
childWithAccessibilityFocus = null;
i = childrenCount - 1;
}

if (!canViewReceivePointerEvents(child) //如果view不可以接收触摸事件 且 事件坐标范围不在view内,那么跳过继续寻找
|| !isTransformedTouchPointInView(x, y, child, null)) {
ev.setTargetAccessibilityFocus(false);
continue;
}

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;// 找到了有意愿处理触摸事件的view,不再继续往下找
}

resetCancelNextUpFlag(child);
if (dispatchTransformedTouchEvent(ev, false, child, idBitsToAssign)) {// 找到可以分发的,跳出循环
// Child wants to receive touch within its bounds. // 找到了,(注意,只有ACTION_DOWN才能进行到这一步,MOVE等事件在上一步就已经找好了
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();
newTouchTarget = addTouchTarget(child, idBitsToAssign);// 新加入的touchTarget加入列表头
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;
}
}
}

// Dispatch to touch targets.
if (mFirstTouchTarget == null) {// 如果没有找到,分发给自己
// No touch targets so treat this as an ordinary view.
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.
TouchTarget predecessor = null;
TouchTarget target = mFirstTouchTarget;// 表明事件之前已经分发过了。所以有target记录
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)) {
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();
} 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;
}

作者

0xforee

发布于

2018-01-09

更新于

2018-04-22

许可协议


欢迎关注我的公众号 0xforee,第一时间获取更多有价值的思考

评论