Touch事件分发中只有两个主角:ViewGroup和View。Activity的Touch事件事实上是调用它内部的ViewGroup的Touch事件,能够直接当成ViewGroup处理。javascript
View在ViewGroup内,ViewGroup也能够在其余ViewGroup内,这时候把内部的ViewGroup当成View来分析。java
ViewGroup的相关事件有三个:onInterceptTouchEvent、dispatchTouchEvent、onTouchEvent。View的相关事件只有两个:dispatchTouchEvent、onTouchEvent。函数
先分析ViewGroup的处理流程:首先得有个结构模型概念:ViewGroup和View组成了一棵树形结构,最顶层为Activity的ViewGroup,下面有若干的ViewGroup节点,每一个节点之下又有若干的ViewGroup节点或者View节点,依次类推。如图:spa
当一个Touch事件(触摸事件为例)到达根节点,即Acitivty的ViewGroup时,它会依次下发,下发的过程是调用子View(ViewGroup)的dispatchTouchEvent方法实现的。简单来讲,就是ViewGroup遍历它包含着的子View,调用每一个View的dispatchTouchEvent方法,而当子View为ViewGroup时,又会经过调用ViwGroup的dispatchTouchEvent方法继续调用其内部的View的dispatchTouchEvent方法。上述例子中的消息下发顺序是这样的:①-②-⑤-⑥-⑦-③-④。dispatchTouchEvent方法只负责事件的分发,它拥有boolean类型的返回值,当返回为true时,顺序下发会中断。在上述例子中若是⑤的dispatchTouchEvent返回结果为true,那么⑥-⑦-③-④将都接收不到本次Touch事件。来个简单版的代码加深理解:.net
/** * ViewGroup * @param ev * @return */ public boolean dispatchTouchEvent(MotionEvent ev){ ....//其余处理,在此无论 View[] views=getChildView(); for(int i=0;i<views.length;i++){
//判断下Touch到屏幕上的点在该子View上面
if(...){ if(views[i].dispatchTouchEvent(ev)) return true;
} } ...//其余处理,在此无论 } /** * View * @param ev * @return */ public boolean dispatchTouchEvent(MotionEvent ev){ ....//其余处理,在此无论 return false; }orm
在此能够看出,ViewGroup的dispatchTouchEvent是真正在执行“分发”工做,而View的dispatchTouchEvent方法,并不执行分发工做,或者说它分发的对象就是本身,决定是否把touch事件交给本身处理,而处理的方法,即是onTouchEvent事件,事实上子View的dispatchTouchEvent方法真正执行的代码是这样的对象
/** * View * @param ev * @return */ public boolean dispatchTouchEvent(MotionEvent ev){ ....//其余处理,在此无论 return onTouchEvent(event); }blog
通常状况下,咱们不应在普通View内重写dispatchTouchEvent方法,由于它并不执行分发逻辑。当Touch事件到达View时,咱们该作的就是是否在onTouchEvent事件中处理它。继承
那么,ViewGroup的onTouchEvent事件是何时处理的呢?当ViewGroup全部的子View都返回false时,onTouchEvent事件便会执行。因为ViewGroup是继承于View的,它其实也是经过调用View的dispatchTouchEvent方法来执行onTouchEvent事件。递归
在目前的状况看来,彷佛只要咱们把全部的onTouchEvent都返回false,就能保证全部的子控件都响应本次Touch事件了。但必需要说明的是,这里的Touch事件,只限于Acition_Down事件,即触摸按下事件,而Aciton_UP和Action_MOVE却不会执行。事实上,一次完整的Touch事件,应该是由一个Down、一个Up和若干个Move组成的。Down方式经过dispatchTouchEvent分发,分发的目的是为了找到真正须要处理完整Touch请求的View。当某个View或者ViewGroup的onTouchEvent事件返回true时,便表示它是真正要处理此次请求的View,以后的Aciton_UP和Action_MOVE将由它处理。当全部子View的onTouchEvent都返回false时,此次的Touch请求就由根ViewGroup,即Activity本身处理了。
看看改进后的ViewGroup的dispatchTouchEvent方法
View mTarget=null;//保存捕获Touch事件处理的View public boolean dispatchTouchEvent(MotionEvent ev) { //....其余处理,在此无论 if(ev.getAction()==KeyEvent.ACTION_DOWN){ //每次Down事件,都置为Null
if(!onInterceptTouchEvent()){
mTarget=null; View[] views=getChildView(); for(int i=0;i<views.length;i++){ if(views[i].dispatchTouchEvent(ev)) mTarget=views[i]; return true; }
} } //当子View没有捕获down事件时,ViewGroup自身处理。这里处理的Touch事件包含Down、Up和Move if(mTarget==null){ return super.dispatchTouchEvent(ev); } //...其余处理,在此无论 if(onInterceptTouchEvent()){
//...其余处理,在此无论
}
//这一步在Action_Down中是不会执行到的,只有Move和UP才会执行到。 return mTarget.dispatchTouchEvent(ev); }
ViewGroup还有个onInterceptTouchEvent,看名字便知道这是个拦截事件。这个拦截事件须要分两种状况来讲明:
1.假如咱们在某个ViewGroup的onInterceptTouchEvent中,将Action为Down的Touch事件返回true,那便表示将该ViewGroup的全部下发操做拦截掉,这种状况下,mTarget会一直为null,由于mTarget是在Down事件中赋值的。因为mTarge为null,该ViewGroup的onTouchEvent事件被执行。这种状况下能够把这个ViewGroup直接当成View来对待。
2.假如咱们在某个ViewGroup的onInterceptTouchEvent中,将Acion为Down的Touch事件都返回false,其余的都返回True,这种状况下,Down事件能正常分发,若子View都返回false,那mTarget仍是为空,无影响。若某个子View返回了true,mTarget被赋值了,在Action_Move和Aciton_UP分发到该ViewGroup时,便会给mTarget分发一个Action_Delete的MotionEvent,同时清空mTarget的值,使得接下去的Action_Move(若是上一个操做不是UP)将由ViewGroup的onTouchEvent处理。
状况一用到的比较多,状况二我的还未找到使用场景。
从头至尾总结一下:
1.Touch事件分发中只有两个主角:ViewGroup和View。ViewGroup包含onInterceptTouchEvent、dispatchTouchEvent、onTouchEvent三个相关事件。View包含dispatchTouchEvent、onTouchEvent两个相关事件。其中ViewGroup又继承于View。
2.ViewGroup和View组成了一个树状结构,根节点为Activity内部包含的一个ViwGroup。
3.触摸事件由Action_Down、Action_Move、Aciton_UP组成,其中一次完整的触摸事件中,Down和Up都只有一个,Move有若干个,能够为0个。
4.当Acitivty接收到Touch事件时,将遍历子View进行Down事件的分发。ViewGroup的遍历能够当作是递归的。分发的目的是为了找到真正要处理本次完整触摸事件的View,这个View会在onTouchuEvent结果返回true。
5.当某个子View返回true时,会停止Down事件的分发,同时在ViewGroup中记录该子View。接下去的Move和Up事件将由该子View直接进行处理。因为子View是保存在ViewGroup中的,多层ViewGroup的节点结构时,上级ViewGroup保存的会是真实处理事件的View所在的ViewGroup对象:如ViewGroup0-ViewGroup1-TextView的结构中,TextView返回了true,它将被保存在ViewGroup1中,而ViewGroup1也会返回true,被保存在ViewGroup0中。当Move和UP事件来时,会先从ViewGroup0传递至ViewGroup1,再由ViewGroup1传递至TextView。
6.当ViewGroup中全部子View都不捕获Down事件时,将触发ViewGroup自身的onTouch事件。触发的方式是调用super.dispatchTouchEvent函数,即父类View的dispatchTouchEvent方法。在全部子View都不处理的状况下,触发Acitivity的onTouchEvent方法。
7.onInterceptTouchEvent有两个做用:1.拦截Down事件的分发。2.停止Up和Move事件向目标View传递,使得目标View所在的ViewGroup捕获Up和Move事件。
另外,上文所列出的代码并不是真正的源码,只是归纳了源码在事件分发处理中的核心处理流程,真正源码各位能够本身去看,包含了更丰富的内容。
补充:
“触摸事件由Action_Down、Action_Move、Aciton_UP组成,其中一次完整的触摸事件中,Down和Up都只有一个,Move有若干个,能够为0个。”,这里补充下其实UP事件是可能为0个的。
最近恰好在作一个手势放大缩小移动图片的Demo,对此有了更多的理解。对于onInterceptTouchEvent事件,它的应用场景在不少带scroll效果的ViewGroup中都有体现。设想一下再一个ViewPager中,每一个Item都是个ImageView,咱们须要对这些ImageView作Matrix操做,这不可避免要捕获掉Touch事件,可是咱们又须要作到不影响ViewPager翻页效果,这又必须保证ViewPager能捕获到Move事件,因而,ViewPager的onInterceptTouchEvent会对Move事件作一个过滤,当适当条件的Move事件(持续若干事件或移动若干距离,这里我没读源码只是猜想)触发时,并会拦截掉,返回子View一个Action_Cancel事件。这个时候子View就没有Up事件了,不少须要在Up中处理的事物要转到Cancel中处理。