Android事件分发机制是高级工程师必须烂熟于心的知识点,本小姐手撕源码后总结了以下文章,方便大家一起学习。
事件分发机制的传递过程:
当我们点击屏幕时,首先会按下屏幕,这个动作触发的事件其实就是MotionEvent.ACTION_DOWN,那么这个事件是如何进行传递的呢?
点击屏幕时,屏幕正处于Activity界面,Activity里有一个dispatchTouchEvent方法会进行事件的分发,请看源码:
public boolean dispatchTouchEvent(MotionEvent ev) {
if (ev.getAction() == MotionEvent.ACTION_DOWN) {
onUserInteraction();
}
if (getWindow().superDispatchTouchEvent(ev)) {
return true;
}
return onTouchEvent(ev);
}
当操作事件为ACTION_DOWN时,会和用户执行交互。Activity会将事件传递给Window,getWindow().superDispatchTouchEvent()是由Window来进行事件的分发,如果Window消费了该事件,getWindow().superDispatchTouchEvent()为true值,那么直接return退出该方法,如果window没有消费该事件,那么继续由Activity的onTouchEvent方法来处理。
小贴士:
return的用法:结束该方法,继续执行方法后面的语句。
接下来继续看Window的源码:
public abstract boolean superDispatchTouchEvent(MotionEvent event);
Window类是一个抽象类,里边的superDispatchTouchEvent方法是一个抽象方法,看Window类的介绍,这个抽象类的唯一存在实现是PhoneWindow,那么我们去看PhoneWindow的源码。
@Override
public boolean superDispatchTouchEvent(MotionEvent event) {
return mDecor.superDispatchTouchEvent(event);
}
从上面可以看出,PhoneWindow将事件继续传递给了DecorView,DecorView是Window窗口的顶级视图,包含窗口的装饰。DecorView是PhoneWindow的一个内部类。
接下来我们继续看DecorView的superDispatchTouchEvent方法。
DecorView实际上是一个ViewGroup,我们来看ViewGroup是如何进行事件分发的,请看源码:
下面的源码是我把不太主要的逻辑删减后的代码,ViewGroup的dispatchTouchEvent:
@Override
public boolean dispatchTouchEvent(MotionEvent ev) {
//注释1
boolean handled = false;
if (onFilterTouchEventForSecurity(ev)) {
// 注释2
if (actionMasked == MotionEvent.ACTION_DOWN) {
cancelAndClearTouchTargets(ev);
resetTouchState();
}
//注释3
final boolean intercepted;
if (actionMasked == MotionEvent.ACTION_DOWN
|| mFirstTouchTarget != null) {
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;
}
//注释4
if (!canceled && !intercepted) {
handled = child.dispatchTouchEvent(ev);
}
}
return handled;
}
注释1处:定义的handled值为事件是否被消费,默认为false,即事件默认没有被ViewGroup消费,会向子View传递。
注释2处:初始化Down事件,初始化并重置手势事件。初始化的原因:一个完整的事件序列是以DOWN开始,以UP结束的。如果是DOWN事件,说明这是一个新的事件序列,所以需要初始化之前的状态。
注释3处:检查拦截。如果按下的事件是DOWN事件,然后判断是否设置了标志位FLAG_DISALLOW_INTERCEPT,标志位和requestDisallowInterceptTouchEvent共同作用,表示要求不允许父View拦截事件。默认不设置标志位,则执行onInterceptTouchEvent方法,如果设置了标志位,则父View不拦截事件,会转到注释4,向子View传递继续执行事件。回到注释3,如果按下的不是DOWN事件,有ACTION_MOVE和ACTION_UP事件到来时,同一事件中的其他序列都会默认交给ViewGroup来处理。
划重点:
标志位FLAG_DISALLOW_INTERCEPT的作用:
禁止ViewGroup拦截除Down之外的事件。意思就是除down之外的事件只要设置了标志位都会向子View进行传递。
因为每次有ACTION_DOWN事件传过来时,都会在注释2处重置手势的状态。
接下来我们继续看ViewGroup的onInterceptTouchEvent方法:
public boolean onInterceptTouchEvent(MotionEvent ev) {
if (ev.isFromSource(InputDevice.SOURCE_MOUSE)
&& ev.getAction() == MotionEvent.ACTION_DOWN
&& ev.isButtonPressed(MotionEvent.BUTTON_PRIMARY)
&& isOnScrollbarThumb(ev.getX(), ev.getY())) {
return true;
}
return false;
}
默认返回false,向子View传递。根据源码对该方法的注释,onInterceptTouchEvent返回true时会执行onTouchEvent方法。但是如果设置了onTouchListener,会先执行onTouch方法,onTouch方法没有被消费才会执行onTouchEvent方法,如果onTouch事件被消费就不会再继续执行onTouchEvent方法。
划重点:
1.ViewGroup想要拦截事件,则可以重写onInterceptTouchEvent方法返回true。
因为:该方法默认为false,向子View传递。
2.onInterceptTouchEvent方法不是每次事件都会调用。
因为:触发的事件为ACTION_DOWN,如果设置了FLAG_DISALLOW_INTERCEPT标志位,就会向子View传递,就不会执行该方法。
小贴士:
dispatchTouchEvent:负责事件分发。
onInterceptTouchEvent:负责事件拦截。
最后我们再来看看View的dispatchTouchEvent源码:
下面是我简化逻辑后的代码:
public boolean dispatchTouchEvent(MotionEvent event) {
//注释1处
boolean result = false;
//注释2处
if (onFilterTouchEventForSecurity(event)) {
if ((mViewFlags & ENABLED_MASK) == ENABLED && handleScrollBarDragging(event)) {
result = true;
}
//noinspection SimplifiableIfStatement
ListenerInfo li = mListenerInfo;
if (li != null && li.mOnTouchListener != null
&& (mViewFlags & ENABLED_MASK) == ENABLED
&& li.mOnTouchListener.onTouch(this, event)) {
result = true;
}
if (!result && onTouchEvent(event)) {
result = true;
}
}
return result;
}
从源码可以看出:事件如果被View的dispatchTouchEvent消费则返回true,如果没有则返回false。
注释1处:定义事件最后有没有被View的dispatchTouchEvent消费,默认为false。
注释2处:判断是不是有onTouchListener,如果有执行onTouch方法,如果事件被onTouch方法消费不会继续往下执行,如果没有被消费,继续执行onTouchEvent方法。
接下来我们继续看View的onTouchEvent方法:
下面是我简化逻辑后的源码:
public boolean onTouchEvent(MotionEvent event) {
//注释1处
final boolean clickable = ((viewFlags & CLICKABLE) == CLICKABLE
|| (viewFlags & LONG_CLICKABLE) == LONG_CLICKABLE)
|| (viewFlags & CONTEXT_CLICKABLE) == CONTEXT_CLICKABLE;
//注释2处
if (clickable || (viewFlags & TOOLTIP) == TOOLTIP) {
switch (action) {
case MotionEvent.ACTION_UP:
performClick();
break;
case MotionEvent.ACTION_DOWN:
break;
case MotionEvent.ACTION_CANCEL:
break;
case MotionEvent.ACTION_MOVE:
break;
}
return true;
}
return false;
}
从上面源码可以看出,只要View的Click有点击事件,就会执行action事件,事件就会被处理返回true,否则事件没有被消费返回false。
当ACTION_UP事件触发时,会执行performClick方法,看performClick方法的源码:
public boolean performClick() {
final boolean result;
final ListenerInfo li = mListenerInfo;
if (li != null && li.mOnClickListener != null) {
playSoundEffect(SoundEffectConstants.CLICK);
li.mOnClickListener.onClick(this);
result = true;
} else {
result = false;
}
return result;
}
如果设置了onClickListener方法,那么就会执行onClick方法,事件就会被消费。
小贴士:
View没有onInterceptTouchEvent方法,当有点击事件传递给它时,就会执行onTouchEvent方法。
View的onTouchEvent方法默认都会消耗事件,返回true。但是如果View不可点击就不会消耗事件。
onClick事件会发生的前提:当前View可点击,并且收到了down和up事件。
Activity到DecorView布局的加载过程:
初始化Activity时调用setContentView加载布局,然后执行getWindow().setContentView传给Window,PhoneWindow里创建了DecorView,而DecorView又将屏幕划分为两个区域:一个是 TitleView,另一个是ContentView,我们平常做应用所写的布局正展示在ContentView中。
快问快答小测试:
1.Activity中为什么会有dispatchTouchEvent?
答:Android中每个层级对触摸事件的处理都是从dispatchTouchEvent开始的。
2.事件分发机制的传递规则。
事件分发机制从Activity传给Window传给ViewGroup传给View。
网友评论