view和viewGroup,是android事件分发中的主要角色。view中,处理事件的方法为dispatchTouchEvent和onTouchEvent,viewGroup继承自view,相关方法有三个,父view的onTouchEvent,重写了dispatchTouchEvent,增加了onInterceptTouchEvent。这篇文章,主要分析一下这个流程。先盗张图:
如果之前已经对事件分发有些了解,我们首先要澄清一个知识点,避免进入误区。
viewGroup对应的父类是view,当viewgroup(上图②)调用它的super.dispatchTouchEvent方法执行的是当前②的父类 view的dispatchTouchEvent。并不是执行①的dispatchTouchEvent。这点很容易在理解时出现偏差导致进入误区。那事件是如何在view树中传递的呢?我们逐步分析系统的源码来解答。
1.viewGroup的dispatchTouchEvent
1.事件由activity发起,找到当前activity的decorView(FrameLayout根布局),调用decorView的dispatchTouchEvent
activity分发touch事件
public boolean dispatchTouchEvent(MotionEvent ev) {
if (ev.getAction() == MotionEvent.ACTION_DOWN) {
onUserInteraction();
}
if (getWindow().superDispatchTouchEvent(ev)) {
return true;
}
return onTouchEvent(ev);
}
PhoneWindow类中的调用
public boolean superDispatchTouchEvent(MotionEvent event) {
return mDecor.superDispatchTouchEvent(event);
}
2.于是乎如果我们理清了viewGroup间的事件传递,便弄懂了整个流程。源码不算长但是逻辑复杂,我们先理一个简单的伪代码的流程图
viewGroup的dispatch流程
public boolean dispatchTouchEvent(MotionEvent ev){
....//其他处理
View[] views=buildTouchDispatchChildList();//找到在该viewGroup中的子view
for(int i=0;i<views.length;i++){
//判断touch的位置在子view范围内
if(...){
if(views[i].dispatchTouchEvent(ev))
return true;
}
}
...//其他处理
}
ViewGroup会遍历它包含着的子View(layout的层级先后顺序),如果该事件在touch的范围内(通过view的坐标和触摸的位置判断),调用子view的dispatchTouchEvent方法,而当子View为ViewGroup时,自然会按照上述方式遍历,如此递归调用,直到某一个view的返回值为true,则下发中断,后续事件由该view接收处理
再看一个复杂点的:
private View mTarget;
@Override
public boolean dispatchTouchEvent(MotionEvent ev) {
boolean handled =false;
如果是按下事件
if(ev.getAction()==MotionEvent.ACTION_DOWN){
向上一次处理了touch事件的子view发送cancel事件通知上一次事件取消。
cancelAndClearTouchTargets(ev);
}
//如果有子view想拦截 或者自己不用拦截
if(requestDisallowInterceptTouchEvent==true||onInterceptTouchEvent(ev)==false){
View target;
if(ev.getAction()==MotionEvent.ACTION_DOWN){
View[] views=buildTouchDispatchChildList();//找到在该viewGroup中的子view
for(int i=0;i<views.length;i++){
if(touch的位置在子view范围内){
if(views[i].dispatchTouchEvent(ev))
mTarget=view[i];
handled=true;
break; //break很重要,也即当有子view拦截事件后,不再向其他子view发射事件
}
}
}
}
if(mFirstTouchTarget==null){
//也即view的dispatchTouchEvent
handled=super.dispatchTouchEvent(ev);
}
return handled;
}
1.viewGroup在ACTION_DOWN事件中,会先发送cancel事件。如果自己不需要拦截事件,或者有子view想拦截事件
2.如果自身的onInterceptTouchEvent(返回true),或者子view需要拦截(requestDisallowInterceptTouchEvent(true)),直接通过dispatchTransformedTouchEvent找出到底谁需要拦截(mFirstTouchTarget),交由它处理。
2.view的dispatchTouchEvent
1.我们先看一个简单的流程图,view的dispatchTouchEvent,主要是调用它的onTouchEvent
view的dispatch流程主要处理了view的事件
public boolean dispatchTouchEvent(MotionEvent ev){
....//其他处理
return onTouchEvent(event);
}
也即,根据view的onTouchEvent的返回结果,来判断是否需要拦截。
public boolean dispatchTouchEvent(MotionEvent event) {
boolean result =false;
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的onTouchEvent在ACTION_DOWN时返回结果为true,即该view拦截了事件,进而影响viewGroup的dispatchTouchEvent的返回结果。后续事件,都将由该view来处理。
2.view的onTouchEvent
public boolean onTouchEvent(MotionEvent event) {
final float x = event.getX();
final float y = event.getY();
final int viewFlags = mViewFlags;
final int action = event.getAction();
//如果view的touchDelegate不为空,则交由mTouchDelegate处理结果
if (mTouchDelegate != null) {
if (mTouchDelegate.onTouchEvent(event)) {
return true;
}
}
if(ev.getAction()==MotionEvent.ACTION_DOWN){
//如果有长按监听发送长按延时消息
}
if(ev.getAction()==MotionEvent.ACTION_UP){
//如果没执行过长按,有点击监听,执行点击事件
}
return false;
}
view的touch事件,主要处理了tap,点击,长按等事件的逻辑处理。
1.如果view被set了TouchDelegate,则交由它处理,否则,会在actiondown是埋下一个长按的延时消息,如果是在滑动容器,会埋下一个setPressed的延时消息,在Up事件中,如果处理了长按,则不触发点击事件。
2.setPressed();该方法是设置view的按下状态(我们在写selector时经常用到该属性state_pressed)如果是在滑动容器,会延时100ms发送。如果有兴趣可以详细阅读关于setPressed的处理
3.如果有onTouchListener,返回onTouchListener的返回结果
如果没有,调用onTouchEvent,并返回该方法的执行结果。
总结一些经常迷惑的点:
1.onInterceptTouchEvent,requestDisallowInterceptTouchEvent到底有啥用
源码在判断该方法返回结果的条件是这样的。
if (actionMasked == MotionEvent.ACTION_DOWN
|| mFirstTouchTarget != null) {
若down事件时便进行intercept,mTarget会一直为null,因为mTarget是在Down事件中赋值的。由于mTarge为null,该ViewGroup的onTouchEvent事件被执行。即当前viewGroup处理该事件。若down时还未拦截,move时触发return true,若某个子View返回了true,接下去的Action_Move(如果上一个操作不是UP)将由ViewGroup的onTouchEvent处理,并向之前的子view发送cancel事件。
2.view在有onTouchListener并返回结果为true时,不再走onTouchEvent,需要程序员自己处理。
3.假如在每一层遍历的时候的dispatch结果都为false,即没有view处理事件时,会在dispatchTouchEvent完成时调用自己的onTouchEvent,网上通常说的U型图是这个意思。具体位置在`
if(mFirstTouchTarget==null){
//也即view的dispatchTouchEvent
handled=super.dispatchTouchEvent(ev);
}
super,即是view的dispatch,会调用view的onTouchEvent。