事件顺序:
事件序列.pngAndroid中触摸事件主要相关的方法有三个:
- 分发:dispatchTouchEvent(MotionEvent event)
- 消费:onTouchEvent(MotionEvent event)
- 拦截:onInterceptTouchEvent(MotionEvent event)
一、事件从哪里开始,如果分发到View、ViewGroup?
当手指触摸屏幕的时候,事件是先从Activity的dispatchTouchEvent开始分发的,欲知分发过程,直接打开Activity源码,看dispatchTouchEvent方法:
public boolean dispatchTouchEvent(MotionEvent ev) {
if (ev.getAction() == MotionEvent.ACTION_DOWN) {
//这个方法是个空的,供子类实现,不管他
onUserInteraction();
}
if (getWindow().superDispatchTouchEvent(ev)) {
return true;
}
return onTouchEvent(ev);
}
Activity的dispatchTouchEvent调用了getWindow().superDispatchTouchEvent(ev),也就是PhoneWindow的superDispatchTouchEvent()。继续深入,打开PhoneWindow源码:
@Override
public boolean superDispatchTouchEvent(MotionEvent event) {
return mDecor.superDispatchTouchEvent(event);
}
PhoneWindow的superDispatchTouchEvent调用了mDecor的superDispatchTouchEvent,mDecor是DecorView的实例,DecorView集成自FrameLayout,所以事件就从Activity分发到了ViewGroup。顺序:Activity -> PhoneWindow -> DecorView -> ViewGroup
Activity的dispatchTouchEvent方法总结:如果mDecor(ViewGroup)的superDispatchTouchEvent返回true,Activity的dispatchTouchEvent就返回true;不然就调用Activity的onTouchEvent()。也就是说如果Activity中的View没有消费掉事件,事件最后会来到Activity的onTouchEvent中。
对PhoneWindow、DecorView的解释:View是如何被加载到屏幕窗口的。
二、ViewGroup的dispatchTouchEvent(MotionEvent event)
代码有点多,说不清......来段伪代码:
public boolean dispatchTouchEvent(MotionEvent ev) {
boolean concume = false;
//调用onInterceptTouchEvent判断是否拦截
if (onInterceptTouchEvent(ev)){
//拦截则调用自身的onTouchEvent
concume = onTouchEvent(ev);
}else{
//不拦截,将事件分发给子View
concume = child.dispatchTouchEvent(ev);
}
return concume
}
总之就是上面的注释,再抄一遍:调用onInterceptTouchEvent判断是否拦截,拦截则调用自身的onTouchEvent,不拦截,将事件分发给子View。
ViewGroup的dispatchTouchEvent源码中有几个点,这里列出来,方便之后阅读源码的理解,完整流程里有很多判断条件,可以去源码里详细了解:
- ACTION_DOWN事件会把mFirstTouchTarget清空,并且循环子View去拿到符合接受事件条件的View(判断是否动画中、是否超出边界等等),然后赋值给mFirstTouchTarget
- 如果mFirstTouchTarget不为空,之后的ACTION_MOVE、ACTION_UP都传递给mFirstTouchTarget
- 子View并不能通过parent.requestDisallowInterceptTouchEvent去阻止父View拦截ACTION_DOWN事件,其他事件可以。
三、View的dispatchTouchEvent(MotionEvent event)
public boolean dispatchTouchEvent(MotionEvent ev) {
...
//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;
}
...
}
在View的dispatchTouchEvent中可以看出,如果View设置了OnTouchListener,并且OnTouchListener中的onTouch方法返回true,那么就不会执行View的onTouchEvent方法了。所以OnTouchListener.onTouch的优先级大于View的onTouchEvent。如果onTouch消费掉了,则onTouchEvent不会执行。
三、View的onTouchEvent(MotionEvent event)
public boolean onTouchEvent(MotionEvent event) {
...
// A disabled view that is clickable still consumes the touch
// events, it just doesn't respond to them.
//首先上面有一段注释意思是不管View是否可用,他都可以消费事件,只是不给出响应。
...
}
源码去AS看,这里列出逻辑,方便之后阅读源码
- 在ACTION_DOWN里进行长按事件监测,如果发现View是在可滑动容器中,则延迟100毫秒监测,否则直接检测
- 首先判断长按事件,长按的判断是先在ACTION_DOWN发起一个500毫秒的延迟任务,如果发现ACTION_UP的时候不够500毫秒,会对这个延迟任务进行移除,则不会调用Listener的OnLongChick,然后判断点击。从使用角度也能理解,长按不会等到手指抬起就会触发,而点击需要手指抬起后才触发。
- 如果设置了OnLongClickListener并且OnLongClickListener的onLongClick返回true,那么onClick则不会执行。那么问题OnLongClickListener中的onLongClick和OnClickListener中的onClick是否只能执行一个?答案显然不是。只要onLongClick返回false,onClick也会执行。
- View的事件顺序:dispatchTouchEvent -> OnTouchListener.onTouch -> onTouchEvent -> onLongClick -> onClick。 只有中间有返回ture,后面就接受不到事件。
总结
事件分发流程图(责任链模式):
事件分发模型.png
事件分发结论:
- 一个事件序列从手指接触屏幕到手指离开屏幕,在这个过程中产生一系列事件,以DOWN事件开始,中间含有不定数的MOVE事件,以UP事件结束。
- 正常情况下,一个事件序列只能被一个View拦截并且消耗
- 某个View一旦决定拦截,那么这个事件序列都将由它的onTouchEvent处理,并且它的onInterceptTouchEvent不再会调用。
- 某个View一旦开始处理事件,如果他不消耗ACTION_ DOWN事件(onTouchEvent返回false),那么同一事件序列中的其他事件不会再交给它处理。并且重新交由它的父元素处理(父元素onTouchEvent被调用)。
- 事件的传递过程是由外向内的,即事件总是先传递给父元素,然后再由父元素分发给子View,通过requestDisallowInterceptTouchEvent方法可以在子View中干预父元素的事件分发过程,但ACTION_DOWN除外。
- ViewGroup默认不拦截任何事件,即onInterceptTouchEvent默认返回false。View没有onInterceptTouchEvent方法,一旦有点击事件传递给它,那么它的onTouchEvent方法就会被调用
- View的onTouchEvent默认会消费事件(返回true),除非它是不可点击的(chickable和longClickable同时为false)。View的longClickable默认都为false,chickable要分情况,比如Button的chickable默认为true,TextViewn的chickable默认为false。
- View的enable属性不影响onTouchEvent的默认返回值,哪怕一个View是disable状态的,只要他的clickable或者longClickable有一个为true,那么它的onTouchEvent就返回true。
- onClick会响应的前提是当前VIew是可点击的,并且收到了ACTION_DOWN和ACTION_UP事件,并且受长按事件影响,当长按事件返回true时,onClick不会响应。
- onLongClick在ACTION_DOWN里判断是否进行响应,要想执行长按事件该View必须是longClickable的并且设置了OnLongClickListener。
网友评论