触摸事件学习系列文章详见:
当前文章的源码基于Android 4.0 (Android 14)
一、Android如何分发事件
从上一篇文章《Android Touch事件学习 7 交给哪个视图处理事件?》可以简单通过LOG角度了解下调用次序,本片对其具体分发的源码进行解读。
查看点击绿色视图的LOG
1
2
3
4
5
6
7
8
9
|
RelativeLayout dispatchTouchEvent ACTION_DOWN
LinearLayout dispatchTouchEvent ACTION_DOWN
CustomView dispatchTouchEvent ACTION_DOWN
CustomView onTouchEvent ACTION_DOWN
RelativeLayout dispatchTouchEvent ACTION_UP
LinearLayout dispatchTouchEvent ACTION_UP
CustomView dispatchTouchEvent ACTION_UP
CustomView onTouchEvent ACTION_UP
|
二、ViewGroup的子类(各种布局)如何分发事件?
ViewGroup的子类都是布局,例如:RelativeLaout、LiearLayout、FrameLayout、GridLayout、AbsoluteLayout等都是其子类。
结合上面的图片可以看到dispatchTouchEvent是针对UI树形结构由上向下传递,执行每一个View的dispatchTouchEvent方法,其虽然是View视图的方法,但是ViewGroup对其进行了覆写,ViewGroup.dispatchTouchEvent复杂一点,按照上面的图片和LOG来看是先执行RelativeLayout.dispatchTouchEvent,之后执行LinearLayout.dispatchTouchEvent方法,因为RelativeLayout与LinearLayout都没有覆写此方法且都是ViewGroup的子类,所以这两个ViewGroup在传递视图的时候都是执行ViewGroup.dispatchTouchEvent。
下面就先来看看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
|
/**
* {@inheritDoc}
*/
@Override
public
boolean
dispatchTouchEvent(MotionEvent ev)
{
// 1. 用于测试目,直接忽略
if
(mInputEventConsistencyVerifier !=
null
)
{
mInputEventConsistencyVerifier.onTouchEvent(ev,
1
);
}
boolean
handled =
false
;
// 2. 未被其他窗口遮盖
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.
// 3. 清理触摸操作的所有痕迹,即派发取消操作
cancelAndClearTouchTargets(ev);
resetTouchState();
}
// 检测是否拦截Touch Event
// Check for interception.
final
boolean
intercepted;
if
(actionMasked == MotionEvent.ACTION_DOWN || mFirstTouchTarget !=
null
)
{
// 是否允许拦截,可以通过requestDisallowInterceptTouchEvent方法设置
final
boolean
disallowIntercept = (mGroupFlags & FLAG_DISALLOW_INTERCEPT) !=
0
;
if
(!disallowIntercept)
{
// 允许拦截
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
;
// 如果Touch Event没有取消并且没有被拦截,才会考虑是否向其子视图派发
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);
final
int
childrenCount = mChildrenCount;
if
(childrenCount !=
0
)
{
// Find a child that can receive the event.
// Scan children from front to back.
final
View[] children = mChildren;
final
float
x = ev.getX(actionIndex);
final
float
y = ev.getY(actionIndex);
// 遍历当前ViewGroup的所有子视图
for
(
int
i = childrenCount -
1
; i >=
0
; i--)
{
final
View child = children[i];
// 4 与5
if
(!canViewReceivePointerEvents(child) || !isTransformedTouchPointInView(x, y, child,
null
))
{
// 满足以上条件,没必要接收Touch Event
continue
;
}
// 如果是在子视图上触摸,经过以上过滤条件,只有当前手指正下方的子视图才会获取到此事件
// 子视图是否在TouchTarget中
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);
// 6. 执行触摸操作(会传递到最终视图的onTouchEvent方法)
if
(dispatchTransformedTouchEvent(ev,
false
, child, idBitsToAssign))
{
// Child wants to receive touch within its
// bounds.
mLastTouchDownTime = ev.getDownTime();
mLastTouchDownIndex = i;
mLastTouchDownX = ev.getX();
mLastTouchDownY = ev.getY();
// 会改变mFirstTouchTarget的值
newTouchTarget = addTouchTarget(child, idBitsToAssign);
alreadyDispatchedToNewTouchTarget =
true
;
break
;
}
}
}
// 没有发现可以接收事件的子视图
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;
}
}
}
// 子视图未消耗Touch Event 或者 被拦截未向子视图派发Touch Event
// Dispatch to touch targets.
if
(mFirstTouchTarget ==
null
)
{
// 由当前ViewGroup处理Touch Event
// 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;
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);
}
}
// 1. 用于测试目,直接忽略
if
(!handled && mInputEventConsistencyVerifier !=
null
)
{
mInputEventConsistencyVerifier.onUnhandledEvent(ev,
1
);
}
return
handled;
}
|
1. View.mInputEventConsistencyVerifier
1
2
3
4
5
6
|
/**
* Consistency verifier for debugging purposes.
* @hide
*/
protected
final
InputEventConsistencyVerifier mInputEventConsistencyVerifier =
InputEventConsistencyVerifier.isInstrumentationEnabled() ?
new
InputEventConsistencyVerifier(
this
,
0
) :
null
;
|
2. View.onFilterTouchEventForSecurity
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
|
/**
* Filter the touch event to apply security policies.
*
* @param event
* The motion event to be filtered.
* @return True if the event should be dispatched, false if the event should
* be dropped.
*
* @see #getFilterTouchesWhenObscured
*/
public
boolean
onFilterTouchEventForSecurity(MotionEvent event)
{
// noinspection RedundantIfStatement
// 当前被其他窗口遮挡时需要过滤触摸事件。
if
((mViewFlags & FILTER_TOUCHES_WHEN_OBSCURED) !=
0
&& (event.getFlags() & MotionEvent.FLAG_WINDOW_IS_OBSCURED) !=
0
)
{
// Window is obscured, drop this touch.
return
false
;
}
return
true
;
}
|
3. ViewGroup.cancelAndClearTouchTargets
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
|
/**
* Cancels and clears all touch targets.
*/
private
void
cancelAndClearTouchTargets(MotionEvent event)
{
// ## 如果指向其他视图,这里进行统一清理
if
(mFirstTouchTarget !=
null
)
{
boolean
syntheticEvent =
false
;
if
(event ==
null
)
{
// 如果参数为空
final
long
now = SystemClock.uptimeMillis();
// 使用静态方法一个MotionEvent对象,动作为ACTION_CANCEL
event = MotionEvent.obtain(now, now, MotionEvent.ACTION_CANCEL,
0
.0f,
0
.0f,
0
);
// 输入源是一个触摸屏定位设备。
event.setSource(InputDevice.SOURCE_TOUCHSCREEN);
syntheticEvent =
true
;
}
for
(TouchTarget target = mFirstTouchTarget; target !=
null
; target = target.next)
{
resetCancelNextUpFlag(target.child);
// 6 向下传递触摸事件(当前传递取消操作)
dispatchTransformedTouchEvent(event,
true
, target.child, target.pointerIdBits);
}
// 清理所有触摸目标
clearTouchTargets();
// 注销拷贝的对象
if
(syntheticEvent)
{
event.recycle();
}
}
}
|
4.canViewReceivePointerEvents
1
2
3
4
5
6
7
8
9
10
|
/**
* Returns true if a child view can receive pointer events.
*
* @hide
*/
private
static
boolean
canViewReceivePointerEvents(View child)
{
// 当前视图显示或者正在执行动画,才可以接受触摸事件
return
(child.mViewFlags & VISIBILITY_MASK) == VISIBLE || child.getAnimation() !=
null
;
}
|
5. isTransformedTouchPointInView
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
|
/**
* Returns true if a child view contains the specified point when
* transformed into its coordinate space. Child must not be null.
*
* @hide
*/
protected
boolean
isTransformedTouchPointInView(
float
x,
float
y, View child, PointF outLocalPoint)
{
// 视图有scrollTo或者scrollBy造成的滚动偏移也需要计算在内
float
localX = x + mScrollX - child.mLeft;
float
localY = y + mScrollY - child.mTop;
if
(!child.hasIdentityMatrix() && mAttachInfo !=
null
)
{
final
float
[] localXY = mAttachInfo.mTmpTransformLocation;
localXY[
0
] = localX;
localXY[
1
] = localY;
child.getInverseMatrix().mapPoints(localXY);
localX = localXY[
0
];
localY = localXY[
1
];
}
// 触摸点是否在当前子视图内
final
boolean
isInView = child.pointInView(localX, localY);
if
(isInView && outLocalPoint !=
null
)
{
outLocalPoint.set(localX, localY);
}
return
isInView;
}
|
6. 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
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
|
/**
* Transforms a motion event into the coordinate space of a particular child
* view, filters out irrelevant pointer ids, and overrides its action if
* necessary. If child is null, assumes the MotionEvent will be sent to this
* ViewGroup instead.
*/
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
)
{
// 调用View类的方法,其内部会调用View.onTouchEvent
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())
{
if
(child ==
null
)
{
// 调用View类的方法,其内部会调用View.onTouchEvent
handled =
super
.dispatchTouchEvent(event);
}
else
{
final
float
offsetX = mScrollX - child.mLeft;
final
float
offsetY = mScrollY - child.mTop;
event.offsetLocation(offsetX, offsetY);
// 如果当前视图通过scrollTo或scrollBy对子视图进行滚动
// 向下传递的是x,y偏移mScrollX, mScrollY后的值
handled = child.dispatchTouchEvent(event);
// 一次手势操作,例如滚动视图,MotionEvent参数都是同一个对象
// 如果有疑问的话,可以在onTouchEvent中打印其hashCode看下
// 所以这里要对之前做出针对其子视图的偏移就还原,便于之后使用
event.offsetLocation(-offsetX, -offsetY);
}
// 如果以上消耗了当前触摸事件,直接返回
return
handled;
}
// 获取当前参数的拷贝,对其做得修改不会影响当前参数对象
transformedEvent = MotionEvent.obtain(event);
}
else
{
transformedEvent = event.split(newPointerIdBits);
}
// 与以上操作雷同,不具体解释
// newPointerIdBits != oldPointerIdBits ?1
// 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())
{
transformedEvent.transform(child.getInverseMatrix());
}
handled = child.dispatchTouchEvent(transformedEvent);
}
// Done.
transformedEvent.recycle();
return
handled;
}
|
三、View并且非ViewGroup子类,如何分发事件?
图片是点击绿色按钮,并显示出相应的LOG,并且与文章顶部的图片是对应的
之前分析了ViewGroup是如何分发事件的,可以解释RelativeLayout与LinearLayout如何分发事件的,下面看下View.dispatchTouchEvent的源码,可以了解下View或者ViewGroup(其中可以调用super.dispatchTouchEvent(event))是如何处理事件的。
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
|
/**
* 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
(mInputEventConsistencyVerifier !=
null
)
{
mInputEventConsistencyVerifier.onTouchEvent(event,
0
);
}
// 上面代码的第2个标注。 未被其他窗口遮盖
if
(onFilterTouchEventForSecurity(event))
{
// noinspection SimplifiableIfStatement
if
(mOnTouchListener !=
null
&& (mViewFlags & ENABLED_MASK) == ENABLED && mOnTouchListener.onTouch(
this
, event))
{
// 如果有监听器,执行监听器的,不在执行当前视图的onTouchEvent方法
return
true
;
}
// 执行当前视图的onTouchEvent方法
if
(onTouchEvent(event))
{
return
true
;
}
}
// 用于测试目,直接忽略
if
(mInputEventConsistencyVerifier !=
null
)
{
mInputEventConsistencyVerifier.onUnhandledEvent(event,
0
);
}
return
false
;
}
|
四、事件分发简单总结
dispatchTouchEvent
事件派发显示隧道方式、再是冒泡方式
隧道方式传递,直道某一个元素消耗此事件,由上至下逐层分发视图。
冒泡方式传递,当某个视图消耗事件后其return boolean 是与分发相反的方法向上传递。
具体分发给哪一个视图是通过当前触摸点坐标在当前层哪个视图上判断
onInterceptTouchEvent
ViewGroup的方法,如果当前ViewGroup需要拦截传递给其子视图的事件,需要return true