安卓view的事件分发机制
何为view的事件分发
安卓的事件分发是指点击事件(MotionEvent)在view树中的传递过程。这句话里面有几个概念1.点击事件,2.view树。
点击事件
所谓点击事件,安卓系统捕捉手指在屏幕上点击的动作,并封装成MotionEvent这个对象。分发的就是这个MotionEvent对象。一个完整的点击事件,由一个按下事件(ACTION_DOWN),若干个滑动事件(ACTION_MOVE)和一个手指从屏幕上松开的事件ACTION_UP)组成。
MotionEvent中携带了一个事件对象的所有信息,包括事件类型(MotioneEvent.getAction),相对于父view的点击坐标(getX,getY),相对于屏幕的点击坐标(getRawX(),getRawY())。
view树
安卓中的view个groupView构成了一颗树形结构。最外层是docor view(也就是我们平时在onCreate中使用setContentView中的父容器),然后是我们在docor view中自定义的子view(也就是我们在xml中自定义的view)。groupView内可以包含很多和子view,同时groupView本身也是一个view,所以这样就形成了一个view树,如下图所示。
image.png
事件分发中的几个关键函数
dispatchTouchEvent,onInterceptTouchEvent,onTouchEvent。
groupView中这三个函数都有,view中只有dispatchTouchEvent和onTouchEvent
dispatchTouchEvent
如果事件传递到当前的view,则该view的dispatchTouchEvent一定会调用。
在viewGroup中,dispatchToEvent首先会调用onInterceptTouchEvent判断viewGroup本身是否拦截事件。如果viewGroup本身拦截事件,则会调用viewgroup的onTouchEvent处理事件;否则,会遍历该viewGroup的所有子view,调用子view的dispatchToEvent,将事件传递到子view中。
如果事件传递到一个普通的view(不是viewGroup),那么在该view的dispatchTouchEvent方法中会直接调用该view的onTouchEvent。如果onTouchEvent返回false,表示该view不消耗该事件,则以后该事件序列的其他事件不会再传递到该view中,如果onTouchEvent返回true,表示该view消耗事件,则该view的父viewGroup会保存该view的实例,下次将该事件序列的其他事件直接发送到该view。
如果viewGroup本身不拦截事件,而他的所有子view也不消耗事件,则viewgroup的onTouchEvent会被调用。同理如果viewgroup的父viewGroup的所有子view(也就是viewGroup的兄弟view)都不消耗事件,viewGroup的父viewGroup的onTouchEvent会被调用。这样,事件又会一级一级地上传。
onTouchEvent
在dispatchTouchEvent中调用,用来处理点击事件。如果返回false,则不会消耗该事件,返回false,表示消耗该事件。
onInterceptTouchEvent
在viewGroup中调用,用来判断是否拦截事件,如果拦截某个事件,该事件不会传递到子view,并且在该事件序列的其他事件中该方法不会调用。
事件分发的大致过程如下图所示
image.png
viewGroup的dispatchTouchEvent的伪代码如下
View mTarget=null;//保存捕获Touch事件处理的View
public boolean dispatchTouchEvent(MotionEvent ev) {
if(ev.getAction()==KeyEvent.ACTION_DOWN){
//每次Down事件,都置为Null
if(!onInterceptTouchEvent()){ //如果viewgroup不拦截事件
mTarget=null;
View[] views=getChildView();
for(int i=0;i<views.length;i++){ //遍历所有子view,调用子view的dispatchEvent
if(views[i].dispatchTouchEvent(ev)) //如果某个子view消耗事件,保存该子view的实例,返回true
mTarget=views[i];
return true;
}
}
}
if(mTarget==null){ //如果所有的子view都不消耗事件
return onTouchEvent(ev); //调用viewGroup的onTouchEvent
}
if(onInterceptTouchEvent(ev)){ //
return onTouchEvent(ev); //调用viewGroup的onTouchEvent
}
}
总结
1.同一个事件序列是从手指触屏幕开始,到手指离开屏幕那一刻。中间包括了一个ACTION_DOWN事件,若干个ACTION_MOVE事件,和一个ACTION_UP事件。
2.正常情况下,一个事件序列只能被一个view拦截且消耗。如果一个view消耗了ACTION_DOWN,则该事件序列的所有事件都会直接发送到该view中。
3.如果一个view不消耗ACTION_DOWM,则该事件序列的其他事件都不会传递到该view中
4.viewgroup默认不拦截任何事件
5.view的onTouchEvent默认消耗事件,除非他是不可点击(既不clickable也不longClickable)
6.view的enable不会影响onTouchEvent,一个view即使是disable的,只要他可以点击,就可以消耗事件
7.点击坐标不在view的范围内,点击事件不会传递到该view中。
8.事件从上到下的传递过程 activity->window->docor view ->viewgroup ->view
9.view的onTouchListener的优先级大于onTouchEvent。如果onTouchListener返回true,则onTouchEvent不会被调用。onTouchEvent中会调用onClickListener。