源码基于安卓8.0分析结果
- 首先如何看安卓SDK源码,作者尝试过几种的方法,感觉这种比较方便
把在本地找到的Android.jar 放到工程中的libs的目录下,直接编译,就可以看到PhoneWindow 和DecorView的源码了 - 结论:Android事件分发流程 = Activity -> ViewGroup -> View
Activity事件分发机制
- Activity事件分发机制(Activity源码分析)
- 当一个点击事件发生时,事件最先传到
Activity
的dispatchTouchEvent()
进行事件分发
- 当一个点击事件发生时,事件最先传到
1 | public boolean dispatchTouchEvent(MotionEvent ev) { |
1、触发方法 onUserInteraction();这个事件触发的机制是DOWN事件
1
2
3
4
5
6
7
8
9/**
* onUserInteraction()
* 作用:实现屏保功能
* 注:
* a. 该方法为空方法
* b. 当此activity在栈顶时,触屏点击按home,back,menu键等都会触发此方法
*/
public void onUserInteraction() {
}2、触发方法 getWindow().superDispatchTouchEvent(ev)
- getWindow():::mWindow = new PhoneWindow(this);
- 定义:属于顶层View(DecorView)
- 说明:
- a. DecorView类是PhoneWindow类的一个内部类
- b. DecorView继承自FrameLayout,是所有界面的父类
- c. FrameLayout是ViewGroup的子类,故DecorView的间接父类 = ViewGroup
1 | mDecor = (DecorView) preservedWindow.getDecorView(); |
到此可以证明Activity的根布局是FrameLayout
调用父类的方法 = ViewGroup的dispatchTouchEvent()
- 即 将事件传递到ViewGroup去处理,详细请看ViewGroup的事件分发机制
1
2
3
4
5
6
7
8
private final class DecorView extends FrameLayout{
public boolean superDispatchTouchEvent(MotionEvent event) {
// 调用父类的方法 = ViewGroup的dispatchTouchEvent()
// 即 将事件传递到ViewGroup去处理,详细请看ViewGroup的事件分发机制
return super.dispatchTouchEvent(event);
}
}
- 即 将事件传递到ViewGroup去处理,详细请看ViewGroup的事件分发机制
3、Activity.onTouchEvent(ev)分析:Activity中的onTouchEvent,个人理解在返回键的时候哦,退出App(或者是一个Activity的结束)
1
2
3
4
5
6
7
8public boolean onTouchEvent(MotionEvent event) {
if (mWindow.shouldCloseOnTouch(this, event)) {
finish();
return true;
}
//只有在点击事件在Window边界外才会返回true,一般情况都返回false
return false;
}
- 主要是对于处理边界外点击事件的判断:是否是DOWN事件,event的坐标是否在边界内等
1
2
3
4
5
6
7
8/** @hide */
public boolean shouldCloseOnTouch(Context context, MotionEvent event) {
if (mCloseOnTouchOutside && event.getAction() == MotionEvent.ACTION_DOWN
&& isOutOfBounds(context, event) && peekDecorView() != null) {
return true;
}
return false;
}
ViewGroup件分发机制
上面Activity事件分发机制可知,ViewGroup事件分发机制从dispatchTouchEvent()开始
分析1:ViewGroup每次事件分发时,都需调用onInterceptTouchEvent()询问是否拦截事件
- a. 若在onInterceptTouchEvent()中返回false(即不拦截事件),就会让第二个值为true,从而进入到条件判断的内部
- 分析2:dispatchTransformedTouchEvent方法分析
- 将运动事件转换为特定子视图的坐标空间,
- 过滤不相关的指针ID,并在必要时重写其操作。
- 如果孩子是无效的,假设位移事件将被发送到这个ViewGroup相反。
- 调用ViewGroup父类的dispatchTouchEvent(),即View.dispatchTouchEvent()
- 因此会执行ViewGroup的onTouch() ->> onTouchEvent() ->> performClick() ->> onClick(),
- 即自己处理该事件,事件不会往下传递(具体请参考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
31private boolean dispatchTransformedTouchEvent(MotionEvent event, boolean cancel,
View child, int desiredPointerIdBits) {
final boolean handled;
//取消运动是一种特殊情况。我们不需要执行任何转换
//或过滤。重要的是行动,而不是内容。
final int oldAction = event.getAction();
if (cancel || oldAction == MotionEvent.ACTION_CANCEL) {
event.setAction(MotionEvent.ACTION_CANCEL);
// 调用ViewGroup父类的dispatchTouchEvent(),即View.dispatchTouchEvent()
// 因此会执行ViewGroup的onTouch() ->> onTouchEvent() ->> performClick() ->> onClick(),
// 即自己处理该事件,事件不会往下传递(具体请参考View事件的分发机制中的View.dispatchTouchEvent())
// 此处需与上面区别:子View的dispatchTouchEvent()
// 若点击的是空白处(即无任何View接收事件) / 拦截事件(手动复写onInterceptTouchEvent(),从而让其返回true)
if (child == null) {
handled = super.dispatchTouchEvent(event);
} else {
//调用View onTouchEvent() ->> performClick() ->> onClick(),
handled = child.dispatchTouchEvent(event);
}
event.setAction(oldAction);
/**
调用子View的dispatchTouchEvent后是有返回值的
若该控件可点击,那么点击时,dispatchTouchEvent的返回值必定是true,因此会导致条件判断成立
于是给ViewGroup的dispatchTouchEvent()直接返回了true,即直接跳出
即把ViewGroup的点击事件拦截掉
*/
return handled;
}
}
1 | @Override |
Activity事件分发的流程(View.dispatchTouchEvent(event)
关键代码的分析:
- mOnTouchListener != null (设置setOnTouchListener(this))
- (mViewFlags & ENABLED_MASK) == ENABLED (这个控件能否点击,)
- mOnTouchListener.onTouch(this, event) (返回了true事件被拦截)
只有以下3个条件都为真,dispatchTouchEvent()才返回true1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20// 说明:只有以下3个条件都为真,dispatchTouchEvent()才返回true;
// 否则执行onTouchEvent()
// 1. mOnTouchListener != null
// 2. (mViewFlags & ENABLED_MASK) == ENABLED
// 3. mOnTouchListener.onTouch(this, event)
/**
* 条件2:(mViewFlags & ENABLED_MASK) == ENABLED
* 说明:
* a. 该条件是判断当前点击的控件是否enable
* b. 由于很多View默认enable,故该条件恒定为true
*/
//当li恒等于null的时候,后面的判断会执行
//但是li在设置了setOnTouchListener就不为空,如果onTouch返回为true的话,
// onclick就不会走了
if (li != null && li.mOnTouchListener != null
&& (mViewFlags & ENABLED_MASK) == ENABLED
&& li.mOnTouchListener.onTouch(this, event)) {
result = true;
}
- mOnTouchListener.onTouch(this, event) (返回了true事件被拦截)
当上面的result返回为false的时候才会走1
2
3
4//没被拦截走到onTouchEvent(event)
if (!result && onTouchEvent(event)) {
result = true;
}
关于onTouchEvent方法:记住这点就行ACTION_UP—》手机抬起来了,才会执行performClick()
目前大多数的app都是只设置了setOnClickListener(),可以自己手动点击一个按钮,会发现只有抬起手,这个事件才会执行,如果让用户体验的比较好一点,对那些有强迫症的用户,应该也重写setOnTouchListener(this),但是记住不能拦击了,我的影像中,鲁大师App,就是重写了这个方法,所以在手指没有抬起来,下面tab的颜色已经发生了改变。
1 | // 若在onTouch()返回true,就会让上述三个条件全部成立, |
只要我们通过setOnClickListener()为控件View注册1个点击事件 ,那么就会给mOnClickListener变量赋值(即不为空), 则会往下回调onClick() & performClick()返回true,performClick 就是模拟了一个请求,点击事件的请求,之类的东西
1 | public boolean performClick() { |
- 通过对performClick() 源码解读,你可能可以发现有点怪怪的东西,就是一个方法中的全局变量。以前觉得有点怪怪的,但是目前看来,需要去判断下是否为null,不用去调用方法初始化值。
1 | ListenerInfo getListenerInfo() { |
- 涉及到的所有的代码
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
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270@RequiresApi(api = Build.VERSION_CODES.LOLLIPOP)
public boolean dispatchTouchEvent(MotionEvent event) {
// 如果事件应该通过可访问性焦点来处理。
if (event.isTargetAccessibilityFocus()) {
// 我们没有焦点,也没有虚拟后代,没有处理事件。
if (!isAccessibilityFocusedViewOrHost()) {
return false;
}
// 我们有焦点,得到事件,然后使用正常事件调度。
event.setTargetAccessibilityFocus(false);
}
boolean result = false;
if (mInputEventConsistencyVerifier != null) {
mInputEventConsistencyVerifier.onTouchEvent(event, 0);
}
final int actionMasked = event.getActionMasked();
if (actionMasked == MotionEvent.ACTION_DOWN) {
//防御新手势
stopNestedScroll();
}
if (onFilterTouchEventForSecurity(event)) {
if ((mViewFlags & ENABLED_MASK) == ENABLED && handleScrollBarDragging(event)) {
result = true;
}
//noinspection SimplifiableIfStatement
ListenerInfo li = mListenerInfo;
// 说明:只有以下3个条件都为真,dispatchTouchEvent()才返回true;
// 否则执行onTouchEvent()
// 1. mOnTouchListener != null
// 2. (mViewFlags & ENABLED_MASK) == ENABLED
// 3. mOnTouchListener.onTouch(this, event)
/**
* 条件2:(mViewFlags & ENABLED_MASK) == ENABLED
* 说明:
* a. 该条件是判断当前点击的控件是否enable
* b. 由于很多View默认enable,故该条件恒定为true
*/
//当li恒等于null的时候,后面的判断会执行
//但是li在设置了setOnTouchListener就不为空,如果onTouch返回为true的话,
// onclick就不会走了
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);
}
//在嵌套滚动之后清理,如果这是手势的结束;
//也取消了如果我们试着action_down但是我们不想休息
//手势。
if (actionMasked == MotionEvent.ACTION_UP ||
actionMasked == MotionEvent.ACTION_CANCEL ||
(actionMasked == MotionEvent.ACTION_DOWN && !result)) {
stopNestedScroll();
}
return result;
}
public boolean onTouchEvent(MotionEvent event) {
final float x = event.getX();
final float y = event.getY();
final int viewFlags = mViewFlags;
final int action = event.getAction();
// 若该控件可点击,则进入switch判断中
final boolean clickable = ((viewFlags & CLICKABLE) == CLICKABLE
|| (viewFlags & LONG_CLICKABLE) == LONG_CLICKABLE)
|| (viewFlags & CONTEXT_CLICKABLE) == CONTEXT_CLICKABLE;
if ((viewFlags & ENABLED_MASK) == DISABLED) {
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;
}
if (mTouchDelegate != null) {
if (mTouchDelegate.onTouchEvent(event)) {
return true;
}
}
// 若该控件可点击,则进入switch判断中
if (clickable || (viewFlags & TOOLTIP) == TOOLTIP) {
switch (action) {
// a. 若当前的事件 = 抬起View(主要分析)
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.
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)) {
// 执行performClick() ->>分析1
performClick();
}
}
}
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;
// b. 若当前的事件 = 按下View
case MotionEvent.ACTION_DOWN:
if (event.getSource() == InputDevice.SOURCE_TOUCHSCREEN) {
mPrivateFlags3 |= PFLAG3_FINGER_DOWN;
}
mHasPerformedLongPress = false;
if (!clickable) {
checkForLongClick(0, x, y);
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(0, x, y);
}
break;
// c. 若当前的事件 = 结束事件(非人为原因)
case MotionEvent.ACTION_CANCEL:
if (clickable) {
setPressed(false);
}
removeTapCallback();
removeLongPressCallback();
mInContextButtonPress = false;
mHasPerformedLongPress = false;
mIgnoreNextUpEvent = false;
mPrivateFlags3 &= ~PFLAG3_FINGER_DOWN;
break;
// d. 若当前的事件 = 滑动View
case MotionEvent.ACTION_MOVE:
if (clickable) {
drawableHotspotChanged(x, y);
}
// Be lenient about moving outside of buttons
if (!pointInView(x, y, mTouchSlop)) {
// Outside button
// Remove any future long press/tap checks
removeTapCallback();
removeLongPressCallback();
if ((mPrivateFlags & PFLAG_PRESSED) != 0) {
setPressed(false);
}
mPrivateFlags3 &= ~PFLAG3_FINGER_DOWN;
}
break;
}
// 若该控件可点击,就一定返回true
return true;
}
// 若该控件不可点击,就一定返回false
return false;
}
/**
* Call this view's OnClickListener, if it is defined. Performs all normal
* actions associated with clicking: reporting accessibility event, playing
* a sound, etc.
* *把这种观点的OnClickListener,如果它的定义。执行所有正常
*与点击相关的操作:报告可访问性事件,播放
*声音等。
* @return True there was an assigned OnClickListener that was called, false
* otherwise is returned.
*/
// 只要我们通过setOnClickListener()为控件View注册1个点击事件
// 那么就会给mOnClickListener变量赋值(即不为空)
// 则会往下回调onClick() & performClick()返回true
//performClick 就是模拟了一个请求,点击事件的请求,子类的东西
public boolean performClick() {
final boolean result;
final ListenerInfo li = mListenerInfo;
if (li != null && li.mOnClickListener != null) {
playSoundEffect(SoundEffectConstants.CLICK);
// TODO: 2018/4/18 这里看源码 有点意思
// ListenerInfo mOnClickListener=getListenerInfo();
// getListenerInfo().mOnClickListener.onClick(this);
li.mOnClickListener.onClick(this);
result = true;
} else {
result = false;
}
sendAccessibilityEvent(AccessibilityEvent.TYPE_VIEW_CLICKED);
notifyEnterOrExitForAutoFillIfNeeded(true);
return result;
}