Android 之事件分发机制
目录
基础认识:
事件分发的对象:Touch 事件
- 定义:当用户触摸屏幕时(View/ViewGroup派生的控件),将产生点击事件(Touch 事件),封装成 MotionEvent 对象
- 事件类型:ACTION_DOWN、ACTION_UP、ACTION_MOVE、ACTION_CANCEL
- 事件列
事件传递的顺序:Activity -> ViewGroup -> View
涉及方法:
方法 | 作用 | 调用时刻 |
---|---|---|
dispatchTouchEvent() |
分发(传递)点击事件 | 当事件传递给当前 View 时,该方法就会被调用 |
onTouchEvent() |
处理点击事件 | 在dispatchTouchEvent() 内部调用 |
onInterceptTouchEvent() |
判断是否拦截了某个事件;只存在于 ViewGroup,普通的 View 无该方法 | 在 ViewGroup 的dispatchTouchEvent() 内部调用 |
Activity 的事件分发机制
当一个点击事件发生时,事件最先传到的是 Activity 的 dispatchTouchEvent()
进行事件分发:1
2
3
4
5
6
7
8
9
10
11
12
13
14
15// Activity:dispatchTouchEvent()
public boolean dispatchTouchEvent(MotionEvent ev){
if(ev.getAction() == MotionEvent.ACTION_DOWN){
//该方法为空方法,作用是实现屏保功能,当当前 Activity 处于栈顶时,触屏点击按 Home\Back\Menu 等键都会触发此方法
onUserInteraction();
}
//`getWindow()`获取了 Window 类的对象,Window 是抽象类,其唯一实现类为 PhoneWindow
if(getWindow().spuerDispatchTouchEvent(ev)){
return true;
}
//Activity 中没有一个子 View 接收处理事件就会到达这里
return onTouchEvent(ev);
}
Activity:dispatchTouchEvent(ev)
->getWindow().superDispatchTouchEvent(ev)
:1
2
3
4
5
6// PhoneWindow:superDispatchTouchEvent()
public boolean superDispatchTouchEvent(MotionEvent ev){
//mDecor 是顶层 View (DecorView) 的实例对象
return mDecor.superDispatchTouchEvent(ev);
}
DecorView 是顶层 View,DecorView 类是 PhoneWindow 类的一个内部类,DecorView 继承自 FrameLayout, 是所有界面的父类。Activity:dispatchTouchEvent(ev)
->getWindow().superDispatchTouchEvent(ev)
->mDecor.superDispatchTouchEvent(ev)
1
2
3
4public boolean superDispatchTouchEvent(MotionEvent ev){
// 调用父类方法:ViewGroup 的 dispatchTouchEvent(),将事件传递给 ViewGroup 进行处理
return super.dispatchTouchEvent(event);
}
Activity:dispatchTouchEvent(ev)
->getWindow().superDispatchTouchEvent(ev)
:true->mDecor.superDispatchTouchEvent(ev)
->ViewGroup:dispatchTouchEvent(ev)
再来看看Activity:onTouchEvent(ev)
,当一个事件未被 Activity 下的任何一个 View 接收处理时,比如“处理发生在 Window 边界外的触摸事件”时,就会调用此方法。Activity:dispatchTouchEvent(ev)
->getWindow().superDispatchTouchEvent(ev)
:false->Activity:onTouchEvent(ev)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15public boolean onTouchEvent(MotionEvent ev){
if(mWindow.shouldCloseOnTouch(this, ev)){
finish();
return true;
}
return false;
}
public boolean shouldCloseOnTouch(Context context, MotionEvent ev){
//主要是对于处理边界外点击事件的判断:是否是 DOWN 事件,event 的坐标是否在边界内等
if(mCloseOnTouchOutside && ev.getAction() == MotionEvent.ACTION_DOWN && isOutOfBounds(context, ev) && peekDecorView() != null){
return true; //事件在边界外,即消费事件
}
return false; //未消费
}
ViewGroup 的事件分发机制
1 | /* |
ViewGroup:dispatchTouchEvent()
:disallowIntercept || !onInterceptTouchEvent()
->child.dispatchTouchEvent()
:target == null
->super.dispatchTouchEvent()
View 事件分发机制
View 事件分发机制从dispatchTouchEvent()
开始:1
2
3
4
5
6public boolean dispatchTouchEvent(MotionEvent ev){
if(mOnTouchListener != null && (mViewFlags & ENABLED_MASK) == ENABLED && mOnTouchListener.onTouch(this.event)){
return true;
}
return onTouchEvent(event);
}
说明只有以下三个条件都为真,dispatchTouchEvent()
才会返回 true;否则执行onTouchEvent()
:
给控件注册了 Touch 事件
1
2
3public void setOnTouchListener(OnTouchListener l){
mOnTouchListener = l;
}mViewFlags & ENABLED_MASK == ENABLED
该条件是判断当前点击的控件是否 enable,很多 View 默认为 enable,一般恒为 true;onTouch()
回调返回 true1
2
3
4
5
6
7btn.setOnTouchListener(new View.OnTouchListener(){
public boolean onTouch(View v, MotionEvent ev){
...
return false; //不消费完事件,执行 View.onTouchEvent(); ture 消费完事件,事件分发结束
}
}
View.dispatchTouchEvent()
:false->View.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/*
* Android 5.0 前版本
*/
public boolean onTouchEvent(MotionEvent ev){
final int viewFlags = mViewFlags;
if((viewFlags & ENABLED_MASK) == DISABLED){
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(ev.getAction()){
case MotionEvent.ACTION_UP:
boolean prepressed = (mPrivateFlags & PREPRESSED) != 0;
...
performClick();
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();
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 Check
removeLongPressCallback();
//need to switch from pressed to not pressed
mPrivateFlags &= ~PRESSED;
refreshDrawableState();
}
}
break;
}
//若该控件可以点击,就一定返回ture
return true;
}
//若该控件不可点击,就一定返回 false;
return false;
}
public boolean performClick(){
if(mOnClickListener != null){
playSoundEffect(SoundEffectConstants.CLICK);
mOnClickListener.onClick(this);
return true;
}
}
注:onTouch()
的执行先于onClick()