Android 事件传递机制研究综述(二)

文章目录
  1. 1. 关于 View
  2. 2. 关于 ViewGroup

追求卓越,成功就会在不经意间追上你。

半个月前,在时隔很久以后,更新了一篇 Android 事件传递机制研究综述(一)博文,然而,分析大多留于表层,未进行深入挖掘。本篇在上篇的基础上继续深入,从相关源码角度分析事件的分发、拦截以及处理机制,不过,源码基于 Android 早期版本,如今源代码虽有所改动,但大体思想精髓一致。

关于 View

首先,且看 View 中 dispatchTouchEvent() 方法的源码:

1
2
3
4
5
6
7
public boolean dispatchTouchEvent(MotionEvent event) {
if (mOnTouchListener != null && (mViewFlags & ENABLED_MASK) == ENABLED &&
mOnTouchListener.onTouch(this, event)) {
return true;
}
return onTouchEvent(event);
}

方法内,进行一个判断,若 mOnTouchListener != null,(mViewFlags & ENABLED_MASK) == ENABLED 和 mOnTouchListener.onTouch(this, event) 同时成立,则返回 true,否则执行 onTouchEvent(event) 方法,再返回。View 里,寻找到 mOnTouchListener 如下:

1
2
3
public void setOnTouchListener(OnTouchListener l) {  
mOnTouchListener = l;
}

这里,mOnTouchListener 是在 setOnTouchListener() 里赋值的,即在控件注册了 touch 事件时赋值。

(mViewFlags & ENABLED_MASK) == ENABLED 是判断当前点击的控件是否是 enable 的,条件恒定为 true。

mOnTouchListener.onTouch(this, event) 即回调控件注册 touch 事件时的 onTouch() 方法,若该方法返回 true,三个条件全部成立,则整个方法直接返回 true。若该方法返回 false,则执行 onTouchEvent(event) 方法。

继续深入,且看 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
public boolean onTouchEvent(MotionEvent event) {  
final int viewFlags = mViewFlags;
if ((viewFlags & ENABLED_MASK) == DISABLED) {
// A disabled view that is clickable still consumes the touch
// events, it just doesn't respond to them.
return (((viewFlags & CLICKABLE) == CLICKABLE ||
(viewFlags & LONG_CLICKABLE) == LONG_CLICKABLE));
}
if (mTouchDelegate != null) {
if (mTouchDelegate.onTouchEvent(event)) {
return true;
}
}
if (((viewFlags & CLICKABLE) == CLICKABLE ||
(viewFlags & LONG_CLICKABLE) == LONG_CLICKABLE)) {
switch (event.getAction()) {
case MotionEvent.ACTION_UP:
boolean prepressed = (mPrivateFlags & PREPRESSED) != 0;
if ((mPrivateFlags & 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 (!mHasPerformedLongPress) {
// 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)) {
performClick();
}
}
}
if (mUnsetPressedState == null) {
mUnsetPressedState = new UnsetPressedState();
}
if (prepressed) {
mPrivateFlags |= PRESSED;
refreshDrawableState();
postDelayed(mUnsetPressedState,
ViewConfiguration.getPressedStateDuration());
} else if (!post(mUnsetPressedState)) {
// If the post failed, unpress right now
mUnsetPressedState.run();
}
removeTapCallback();
}
break;
case MotionEvent.ACTION_DOWN:
if (mPendingCheckForTap == null) {
mPendingCheckForTap = new CheckForTap();
}
mPrivateFlags |= PREPRESSED;
mHasPerformedLongPress = false;
postDelayed(mPendingCheckForTap, ViewConfiguration.getTapTimeout());
break;
case MotionEvent.ACTION_CANCEL:
mPrivateFlags &= ~PRESSED;
refreshDrawableState();
removeTapCallback();
break;
case MotionEvent.ACTION_MOVE:
final int x = (int) event.getX();
final int y = (int) event.getY();
// Be lenient about moving outside of buttons
int slop = mTouchSlop;
if ((x < 0 - slop) || (x >= getWidth() + slop) ||
(y < 0 - slop) || (y >= getHeight() + slop)) {
// Outside button
removeTapCallback();
if ((mPrivateFlags & PRESSED) != 0) {
// Remove any future long press/tap checks
removeLongPressCallback();
// Need to switch from pressed to not pressed
mPrivateFlags &= ~PRESSED;
refreshDrawableState();
}
}
break;
}
return true;
}
return false;
}

挑重点解读,从 14 行看到,若控件是可以点击的,则进入到 16 行的 switch 判断中,当前的事件是抬起手指的话,则进入到 MotionEvent.ACTION_UP 情况下。层层判断后,执行到 38 行的 performClick() 方法,该方法源码如下:

1
2
3
4
5
6
7
8
9
public boolean performClick() {  
sendAccessibilityEvent(AccessibilityEvent.TYPE_VIEW_CLICKED);
if (mOnClickListener != null) {
playSoundEffect(SoundEffectConstants.CLICK);
mOnClickListener.onClick(this);
return true;
}
return false;
}

只要 mOnClickListener 不是 null,就会调用 onClick() 方法。
整个流程梳理一遍,当调用 setOnClickListener() 方法给控件注册一个点击事件时,就会给 mOnClickListener 赋值。而后,当控件被点击时,就会在 performClick() 方法里回调被点击控件的 onClick() 方法。

关于 ViewGroup

且看 ViewGroup 中 onInterceptTouchEvent() 方法的源码:

1
2
3
public boolean onInterceptTouchEvent(MotionEvent ev) {  
return false;
}

就一小段,拦截触控事件,默认返回为 false。

再看 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
public boolean dispatchTouchEvent(MotionEvent ev) {  
final int action = ev.getAction();
final float xf = ev.getX();
final float yf = ev.getY();
final float scrolledXFloat = xf + mScrollX;
final float scrolledYFloat = yf + mScrollY;
final Rect frame = mTempRect;
boolean disallowIntercept = (mGroupFlags & FLAG_DISALLOW_INTERCEPT) != 0;
if (action == MotionEvent.ACTION_DOWN) {
if (mMotionTarget != null) {
mMotionTarget = null;
}
if (disallowIntercept || !onInterceptTouchEvent(ev)) {
ev.setAction(MotionEvent.ACTION_DOWN);
final int scrolledXInt = (int) scrolledXFloat;
final int scrolledYInt = (int) scrolledYFloat;
final View[] children = mChildren;
final int count = mChildrenCount;
for (int i = count - 1; i >= 0; i--) {
final View child = children[i];
if ((child.mViewFlags & VISIBILITY_MASK) == VISIBLE
|| child.getAnimation() != null) {
child.getHitRect(frame);
if (frame.contains(scrolledXInt, scrolledYInt)) {
final float xc = scrolledXFloat - child.mLeft;
final float yc = scrolledYFloat - child.mTop;
ev.setLocation(xc, yc);
child.mPrivateFlags &= ~CANCEL_NEXT_UP_EVENT;
if (child.dispatchTouchEvent(ev)) {
mMotionTarget = child;
return true;
}
}
}
}
}
}
boolean isUpOrCancel = (action == MotionEvent.ACTION_UP) ||
(action == MotionEvent.ACTION_CANCEL);
if (isUpOrCancel) {
mGroupFlags &= ~FLAG_DISALLOW_INTERCEPT;
}
final View target = mMotionTarget;
if (target == null) {
ev.setLocation(xf, yf);
if ((mPrivateFlags & CANCEL_NEXT_UP_EVENT) != 0) {
ev.setAction(MotionEvent.ACTION_CANCEL);
mPrivateFlags &= ~CANCEL_NEXT_UP_EVENT;
}
return super.dispatchTouchEvent(ev);
}
if (!disallowIntercept && onInterceptTouchEvent(ev)) {
final float xc = scrolledXFloat - (float) target.mLeft;
final float yc = scrolledYFloat - (float) target.mTop;
mPrivateFlags &= ~CANCEL_NEXT_UP_EVENT;
ev.setAction(MotionEvent.ACTION_CANCEL);
ev.setLocation(xc, yc);
if (!target.dispatchTouchEvent(ev)) {
}
mMotionTarget = null;
return true;
}
if (isUpOrCancel) {
mMotionTarget = null;
}
final float xc = scrolledXFloat - (float) target.mLeft;
final float yc = scrolledYFloat - (float) target.mTop;
ev.setLocation(xc, yc);
if ((target.mPrivateFlags & CANCEL_NEXT_UP_EVENT) != 0) {
ev.setAction(MotionEvent.ACTION_CANCEL);
target.mPrivateFlags &= ~CANCEL_NEXT_UP_EVENT;
mMotionTarget = null;
}
return target.dispatchTouchEvent(ev);
}

代码同样很长,挑重要的看。13 行中一个条件判断,若 disallowIntercept 和 !onInterceptTouchEvent(ev) 一者为 true,则进入条件判断。disallowIntercept 即禁用事件拦截功能,默认为 false,可以通过 requestDisallowInterceptTouchEvent() 方法修改该值。那么,第一个值为 false 时,会完全依赖第二个值决定是否进入判断内部。第二个值,即对 onInterceptTouchEvent() 方法的返回值取反。判断内部,19 行通过一个 for 循环,遍历当前 ViewGroup 下的所有子 View,然后,24 行判断当前遍历的 View 是否为正在点击的 View,是的话进入到条件判断的内部,再接着,在 29 行调用 View 的 dispatchTouchEvent() 方法,后续流程如 View。

至此,关于事件的分发、拦截以及处理机制研究综述全部完毕。

本人才疏学浅,如有疏漏错误之处,望读者中有识之士不吝赐教,谢谢。

1
Email: [email protected] / WeChat: Wolverine623

您也可以关注我个人的微信公众号码农六哥第一时间获得博客的更新通知,或后台留言与我交流