您的位置:首页 > 其它

事件分发机制 源码 详解

2016-03-16 01:14 411 查看
Down事件的下发过程
ViewGroup相关事件有三个:onInterceptTouchEvent、dispatchTouchEvent、onTouchEvent
View相关事件有两个:dispatchTouchEvent、onTouchEvent
当一个Touch事件到达根节点时,它会依次【下发】,下发的过程是【递归】调用子View的dispatchTouchEvent方法实现的。简单来说,就是ViewGroup遍历它包含着的子View,调用每个View的dispatchTouchEvent方法,而当子View为ViewGroup时,又会通过调用ViwGroup的dispatchTouchEvent方法继续调用其内部的View的dispatchTouchEvent方法。
示意图:



dispatchTouchEvent方法只负责事件的分发,当返回为true时,下发会中断。如在上述例子中,若编号4的View的dispatchTouchEvent返回结果为true,那么后面的5-6-7将都接收不到本次的Touch事件。    //ViewGroup      public boolean dispatchTouchEvent(MotionEvent ev){        View[] views=getChildView();        for(int i=0;i<views.length;i++){//逐个遍历所有子View            if(...){ //判断Touch事件在屏幕上的位置是否在该子View的范围内            if(views[i].dispatchTouchEvent(ev))//若在,判断返回值是否为true              return true;//若为true,则ViewGroup也返回true              //同样此ViewGroup的父ViewGroup也返回true,所以只要有一个View返回true,整个下发事件全部结束!             }        }    }    //View    public boolean dispatchTouchEvent(MotionEvent ev){        //完全由自己的onTouchEvent方法决定此方法的返回值        return onTouchEvent(event);    }在此可以看出,ViewGroup的dispatchTouchEvent是真正在执行事件【分发】工作,而View的dispatchTouchEvent方法只是调用了自己的【onTouchEvent】方法,此方法决定了View是否要处理此touch事件。一般情况下,我们不该在普通View内重写dispatchTouchEvent方法,因为它并不执行分发逻辑,它仅仅是返回了onTouchEvent方法的返回值而已,我们只需处理onTouchEvent方法即可。

事件上传过程
上面我们是假设ViewGroup中的某个View的onTouchEvent返回值为true,此时事件分发结束,但如果所有View的onTouchEvent返回值都为false呢?如果所有View的onTouchEvent返回值都为false,表示没有View要处理此事件那么这个事件会从这个子view【往上】传递,而且都是传递到父ViewGroup的【onTouchEvent】
如果父ViewGroup的onTouchEvent 也返回 false 的话,则继续传递到Activity的onTouchEvent中如果还是false,则这个事件就是没人处理,就会自动消失
事件【向上】传递到中间的任何onTouchEvent方法中,如果返回true,则事件被消费掉,不会再传递
如上面的模型中,如果所有View及ViewGroup的onTouchEvent方法都返回false,则onTouchEvent方法调用顺序为:


事件处理过程[/b]
在目前的情况看来,似乎只要我们把所有的onTouchEvent都返回false,就能保证所有的子控件都响应本次Touch事件了。
实际上这种想法是片面的甚至错误的,因为这里传递的Touch事件,只限于【Acition_Down】事件,而Aciton_UP和Action_MOVE是不会传递的。Down方式通过dispatchTouchEvent分发的目的是为了找到真正需要处理【完整】Touch请求的View,当某个View或者ViewGroup的【onTouchEvent】事件返回true时,便表示它是真正要处理这次请求的View,之后的Aciton_UP和Action_MOVE将由它处理。看看改进后的ViewGroup的dispatchTouchEvent方法View mTarget=null;//记录捕获Touch事件处理的那个View,以便把整个Touch事件都传给他public boolean dispatchTouchEvent(MotionEvent ev) {    ...    if(ev.getAction()==KeyEvent.ACTION_DOWN){//当是DOWN事件时        if(!onInterceptTouchEvent()){//如果没有拦截        mTarget=null;//每次Down事件,都置为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,那么将会中止Up和Move事件向目标View传递。这种情况下,Down事件能正常分发,若子View都返回false,那mTarget还是为空,无影响。若某个子View返回了true,mTarget被赋值了,在Action_Move和Aciton_UP分发到该ViewGroup时,便会给mTarget分发一个Action_Delete的MotionEvent,同时清空mTarget的值,使得接下去的Action_Move将由ViewGroup的onTouchEvent处理。

总结[/b]
从头到尾总结一下:
1、触摸事件由Action_Down、Action_Move、Aciton_UP组成,其中一次完整的触摸事件中,Down只有一个;Up正常都有且只能有一个,但也可以是0个;Move可以有若干个,也可以为0个。
2、ViewGroup接收到Touch事件后,将遍历子View进行【Down事件】的分发,分发的目的是为了找到真正要处理本次完整触摸事件的View,这个View会在其onTouchuEvent中返回true。3、当某个子View返回true时,会中止事件的分发,同时在ViewGroup中记录该子View。接下去的Move和Up事件将由该子View直接进行处理。
4、当ViewGroup中所有子View都不捕获Down事件时,将触发ViewGroup自身的Touch事件。触发的方式是调用super.dispatchTouchEvent函数,即父类View的dispatchTouchEvent方法。在所有子View都不处理的情况下,触发Acitivity的onTouchEvent方法。
5、onInterceptTouchEvent有两个作用:①.拦截Down事件的分发。②.中止Up和Move事件向目标View传递,使得目标View所在的ViewGroup捕获Up和Move事件。
补充说明:对于onInterceptTouchEvent事件,它的应用场景在很多带scroll效果的ViewGroup中都有体现。设想一下在一个ViewPager中,每个Item都是个ImageView,我们需要对这些ImageView做Matrix操作,这不可避免要捕获掉Touch事件,但是我们又需要做到不影响ViewPager翻页效果,这又必须保证ViewPager能捕获到Move事件,于是,ViewPager的onInterceptTouchEvent会对Move事件做一个过滤,当适当条件的Move事件(持续若干时间或移动若干距离,这里我没读源码只是猜测)触发时,并会拦截掉,返回子View一个Action_Cancel事件。这个时候子View就没有Up事件了,很多需要在Up中处理的事物要转到Cancel中处理。

★★★注意点[/b]
1、当TouchEvent发生时,首先是【Activity】将TouchEvent事件通过【dispatchTouchEvent】方法传递给根部的【ViewGroup】
2、ViewGroup通过【dispatchTouchEvent】方法传递给自己的【onInterceptTouchEvent】方法    如果返回true ,表示拦截,事件由当前ViewGroup的【onTouchEvent】处理;    如果返回false,表示不拦截,则传递给子【View】处理
3、子view通过【dispatchTouchEvent】方法传递给自己的【onTouchEvent】方法    如果返回true,表示该View要处理该事件,那么这个事件就会止于该view;    如果返回false,表示不处理,那么这个事件会从这个子view【往上】传递,而且都是传递到父ViewGroup的【onTouchEvent】    如果父ViewGroup的onTouchEvent 也返回 false 的话,则继续传递到Activity的onTouchEvent中    如果还是false,则这个事件就是没人处理,就会自动消失
4、事件【向上】传递到中间的任何onTouchEvent方法中,如果返回true,则事件被消费掉,不会再传递
简单来说,就是事件先通过dispatchTouchEvent方法向下传递(返回true结束),再通过onTouchEvent方法向上传递(返回true结束)
onTouchEvent与onTouch的区别:
对于View(仅适用于View不适用于ViewGroup),我们可以通过重写onTouchEvent方法来处理Touch事件,也可以通过实现OnTouchListener的接口,然后在onTouch方法中达到同样的目的,这两种监听有什么区别呢?1、onTouchListener的onTouch方法优先级比onTouchEvent方法高,会先触发。

2、假如onTouch方法返回false会接着触发onTouchEvent方法,反之onTouchEvent方法不会被调用。

3、内置诸如click事件的实现等等都基于onTouchEvent,假如onTouch返回true,这些事件将不会被触发。


来自为知笔记(Wiz)
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: