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()
@Override
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
4
public 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
15
public 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; //未消费
}

Activity 事件传递机制

ViewGroup 的事件分发机制

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
/*
* ViewGroup:dispatchTouchEvent() 基于 Android 5.0 前
*/
public boolean dispatchTouchEvent(MotionEvent ev){
...
// disallowIntercept:是否禁用事件拦截的功能(默认 false),可通过调用 requestDisallowInterceptTouchEvent()修改
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);

//判断当前 View 是不是正在点击的 View,从而找到当前被点击的 View
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)){
//若该子 View 控件可点击,那么点击时,返回值为 true
mMotionTarget = child;
return true;// 拦截 ViewGroup 的点击事件
}
}
}
}
}
boolean isUpOrCancel = (action == MotionEvent.ACTION_UP) || (action == MotionEvent.ACTION_CANCEL);
if(isUpOrCancel){
mGroupFlages &= ~FLAG_DISALLIW_INTERCEPT;
}
final View target = mMotionTarget;

//若点击的是空白处(即无任何 View 接收事件)/拦截事件(手动复写 onInterceptTouchEvent(),从而让其返回 true)
if(target == null){
ev.setLocation(xf, yf);
if((mPrivateFlags & CANCEL_NEXT_UP_EVENT) != 0){
ev.setAction(MotionEvent.ACTION_CANCEL);
mPrivateFlags &= ~CANCEL_NEXT_UP_EVENT;
}

//调用 ViewGroup 父类的 dispatchTouchEvent(), 即 View.dispatchTouchEvent()
//因此会执行 ViewGroup 的 onTouch() -> onTouchEvent() -> performClick() -> onClick(),即自己处理事件,事件不会往下传递(View 的事件传递机制)
//此处需与上区别,上是子 View 的 dispatchTouchEvent()
return super.dispatchTouchEvent(ev);
}
...
}

public boolean onInterceptTouchEvent(MotionEvent ev){
return false; //是否拦截事件,阻止事件往下传递。默认 false
}

ViewGroup:dispatchTouchEvent():disallowIntercept || !onInterceptTouchEvent()->child.dispatchTouchEvent():target == null->super.dispatchTouchEvent()

ViewGroup 事件传递机制

View 事件分发机制

View 事件分发机制从dispatchTouchEvent()开始:

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

说明只有以下三个条件都为真,dispatchTouchEvent()才会返回 true;否则执行onTouchEvent():

  1. 给控件注册了 Touch 事件

    1
    2
    3
    public void setOnTouchListener(OnTouchListener l){
    mOnTouchListener = l;
    }
  2. mViewFlags & ENABLED_MASK == ENABLED该条件是判断当前点击的控件是否 enable,很多 View 默认为 enable,一般恒为 true;

  3. onTouch()回调返回 true
    1
    2
    3
    4
    5
    6
    7
    btn.setOnTouchListener(new View.OnTouchListener(){
    @Override
    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()

View 的事件传递机制


参考引用

Android事件分发机制 详解攻略,您值得拥有