Android事件分发机制

来源:互联网 发布:淘宝信誉度 编辑:程序博客网 时间:2024/06/16 17:05

为什么有这个机制

因为你点击的地方往往是多个view或viewGroup重叠的,如果没有这个机制,就会产生各种冲突,到底谁先开始执行。
开始

事件分发开始分发的源头是PhoneWindow,是抽象类Window的实现类,作为所有view的顶层管理容器,而PhoneWindow还有一个内部类DecorView,PhoneWindow的作用是通过DecorView来传递事件给下面的view,而DecorView的作用把下面view是否处理了事件的结果返回给PhoneWindow。这个了解就可以了,而分发的总流程是通过递归的方式从上往下,即Activity—>PhoneWindow—>DecorView—>ViewGroup—>…..—>View
三个重要方法:

总的分发过程是通过三个重要方法来实现

public boolean dispatchTouchEvent(MotionEvent ev)

作用:一旦某个view(viewGroup)调用这个函数,就表示事件传递到这个view中了,返回的结果表示当前view是否处理了这个事件。每个view或viewGroup中都有(view和viewGroup中不同)。

 public boolean onInterceptTouchEvent(MotionEvent ev)

作用:在ViewGroup的dispatchTouchEvent的第二次拦截代码中调用,默认false,表示不拦截,除非自己重写返回true,拦截事件给自己处理。

 public boolean onTouchEvent(MotionEvent ev)

在View的dispatchTouchEvent中被调用,作用:事件处理函数,默认也是false,表示没处理,只要view可点击并且调用,无论什么动作都是返回true,表示view处理了。

方法的具体实现:

假设事件传递到viewGroup了,直接从viewGroup开始分发过程
首先肯定是调用viewGroup的dispatchTouchEvent(MotionEvent ev),而且开始是先判断是否拦截,下面是dispatchTouchEvent判断拦截代码

public boolean dispatchTouchEvent(MotionEvent ev) {....//intercepted是真正判断是否进行拦截,true表示拦截final boolean intercepted;if (actionMasked == MotionEvent.ACTION_DOWN        || mFirstTouchTarget != null) {    final boolean disallowIntercept = (mGroupFlags & FLAG_DISALLOW_INTERCEPT) != 0;    if (!disallowIntercept) {        intercepted = onInterceptTouchEvent(ev);        ev.setAction(action);    } else {        intercepted = false;    }} else {        intercepted = true;}

首先要明白何为事件,一个事件是从down开始,若干move,以up或cancel结束,每个动作都是一个event,我们说的事件分发其实是对每一个event进行分发,当前event完全处理完,才开始下一个event分发。
上面的第一个if判断如果不拦截是分成两种,1、当前event是down,一个事件的起点。2、mFirstTouchTarget是当前viewGroup的子view的处理事件标志位,如果子view处理完了,就会赋值,否则就为null,这种情况要想不为null一定是递归子view返回的结果,即从下往上判断是否拦截,1的情况是从上往下判断。
如果满足了任意以上任意一个,进入这个if,这个disallowIntercept,默认是false,这样viewGroup是否拦截由onInterceptTouchEvent(ev)决定,如果是true,ViewGroup就压根不会进入到这个if去调用onInterceptTouchEvent(ev),意思即使你设置了onInterceptTouchEvent(ev)=true也没用,不会进行拦截,由子view处理事件。这个true通过在子view重写dispatchTouchEvent时写getParent().requestDisallowInterceptTouchEvent(true);虽然设置了true,但要注意down(开始)必须由子view处理,否则你都进不了view的dispatchTouchEvent,即down时ViewGroup的 onInterceptTouchEvent(ev)要返回true,下面有例子,先到此结束。
这里分析disallowIntercept默认false,接下来还需要第二次判断,即intercepted = onInterceptTouchEvent(ev);上面说了,重写返回true还是会拦截,二次都不拦截,即intercepted =false才可以往下分发。
上面else就是intercepted =true拦截,情景是当前event不是down且子view没有处理之前的down。如果子view如果处理了down,则mFirstTouchTarget!=null,下一个event是move或up时,也不会进行拦截,进入第一个if后,mFirstTouchTarget在down时就不拦截了,现在肯定也不会拦截。那如果子view没有处理down,mFirstTouchTarget=null,下一个event是move或up时,第一个if都不会进入,直接拦截,即以后的event都由viewGroup执行。
由此得出:只要第一个down被某个view(或viewGroup)处理了,这个事件接下来的所有event都还是由这个view处理。

判断完拦截后,dispatchTouchEvent就会继续往下执行,取了一些关键代码

//如果不拦截,上面intercepted=false,满足ifif (!canceled && !intercepted) {    if (newTouchTarget == null && childrenCount != 0)        //然后就遍历所有的子view       for (int i = childrenCount - 1; i >= 0; i--) {        //这个函数里面调用view的dispatchTouchEvent来处理事件,返回handled=true表明事件被子view处理了,就进入if                 if (dispatchTransformedTouchEvent(ev, false, child, idBitsToAssign)) {        //这个函数就会对mFirstTouchTarget进行赋值,且newTouchTarget!=null,break后,不会执行下面if代码,执行第22行开始的else部分,最终handled=true,      即这个viewGroup的dispatchTouchEvent返回了true,这个viewGroup的递归结束,即再上一个viewGroup的dispatchTouchEvent的第7行的if满足,然后接下的        就跟上面一样,又执行第22行,最终这个也返回true,同理这个true一直往上, 每个dispatchTouchEvent都返回了true,这个event最终结束        newTouchTarget = addTouchTarget(child, idBitsToAssign);        alreadyDispatchedToNewTouchTarget = true;        break;              }           }}.......//mFirstTouchTarget 没有赋值说明上面遍历完子view,子view都没有处理,就是给自己处理,但都是调用view的dispatchTouchEvent的onTouchEventif (mFirstTouchTarget == null) {    // No touch targets so treat this as an ordinary view.    handled = dispatchTransformedTouchEvent(ev, canceled, null,            TouchTarget.ALL_POINTER_IDS);else //这个else说明mFirstTouchTarget被赋值过,即子view处理了,回到上一级的dispatchTouchEvent,会执行到这里,判断上一级的dispatchTouchEvent的返回结果。{                // Dispatch to touch targets, excluding the new touch target if we already                // dispatched to it.  Cancel touch targets if necessary.                TouchTarget predecessor = null;                TouchTarget target = mFirstTouchTarget;                while (target != null) {                    final TouchTarget next = target.next;  //这个event被子view处理了,所以父类的dispatchTouchEvent也返回trueelse情形不知道。。                                      if (alreadyDispatchedToNewTouchTarget && target == newTouchTarget) {                        handled = true;                    } else {                        final boolean cancelChild = resetCancelNextUpFlag(target.child)                                || intercepted;                        if (dispatchTransformedTouchEvent(ev, cancelChild,                                target.child, target.pointerIdBits)) {                            handled = true;                        }}

上面16行之前是不拦截的处理,16行之后的第一个if的情况是拦截后给自己处理或子view不处理给自己处理
ViewGroup里的dispatchTransformedTouchEvent

//这个函数里对这个view再调用dispatchTouchEvent所以类似二叉树的先序遍历private boolean dispatchTransformedTouchEvent(MotionEvent event, boolean cancel,        View child, int desiredPointerIdBits) {    final boolean handled;    final int oldAction = event.getAction();    if (cancel || oldAction == MotionEvent.ACTION_CANCEL) {        event.setAction(MotionEvent.ACTION_CANCEL);        if (child == null) {    //这是上面第19行代码调用,传入的child是null            handled = super.dispatchTouchEvent(event);//1        } else {    //这是上面for里if传入child执行这个            handled = child.dispatchTouchEvent(event);//2        }        event.setAction(oldAction);        return handled;    }

注意:1,2调用的都是view的dispatchTouchEvent,viewGroup的super就是View,View中的dispatchTouchEvent才有onTouchEvent,即ViewGroup和View的处理函数onTouchEvent是在View中的dispatchTouchEvent。
然后不管拦截没都进入到view的dispatchTouchEvent,也取部分代码

public boolean dispatchTouchEvent(MotionEvent event) {   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;    }}

这个很简单,先执行onTouch,再执行onTouchEvent。只要我们设置了此view可点击,并调用setOnTouchListener,里面就会重写onTouch,返回true,则这个view的dispatchTouchEvent也是返回true,表示处理了,如果onTouch返回false,就会调用onTouchEvent。
View的onTouchEvent核心代码

public boolean onTouchEvent(MotionEvent event) {        if (((viewFlags & CLICKABLE) == CLICKABLE ||        (viewFlags & LONG_CLICKABLE) == LONG_CLICKABLE) ||        (viewFlags & CONTEXT_CLICKABLE) == CONTEXT_CLICKABLE) {                switch (action) {                case MotionEvent.ACTION_UP:                ......                case MotionEvent.ACTION_DOWN:                .....                case MotionEvent.ACTION_CANCEL:                    ......               case MotionEvent.ACTION_DOWN:                ......                break;                }            return true;        }    retrun false;}

里面各种event操作都先不管,很明显,只要这个view可点击,就会进入if,无论什么event都会返回true,即onTouchEvent返回true,即
dispatchTouchEvent返回true。
这样就完成了一个完整分发过程,那如果执行了子view的dispatchTouchEvent没有处理,那就返回false,交给直接的父类viewGroup处理,如果处理了,那他的dispatchTouchEvent会返回true,往上的父容器的dispatchTouchEvent也都返回true,如果不处理,就给上级处理,直到某个dispatchTouchEvent处理返回true,否则到最后没处理,所有的dispatchTouchEvent都返回了false,感觉都返回false不太可能,只要自带GroupView可点击,返回true,开始下个动作move或up,所有的event都会被刚才那个返回true的ViewGroup处理,因为不是down,且下级view的mFirstTouchTarget也没赋值,就直接拦截给自己了,下面的view都不会响应,即不会进入下级view的dispatchTouchEvent。

说到底,还是由down被谁处理了就以后event都被他处理。

关于例子,我是参考http://blog.csdn.net/a62321780/article/details/51986515 他写的例子。看他的第一个结果,move有很多次,但总共只打印了三次是为什么?其实就是上面这段话,因为他的自定义view,viewGroup都是自定义,默认不点击的,即onTouchEvent是返回false的,又因ViewGroup拦截了事件,view的dispatchTouchEvent就没执行,所以执行时,先执行MyViewGroup的dispatchTouchEvent,然后拦截给自己,执行自己的onTouchEvent返会了false,即自己的dispatchTouchEvent也返回了false,且mFirstTouchTarget没有进行赋值,再往上结束递归,最终这个down动作的dispatchTouchEvent返回true的view是自带的ViewGroup,比如A,那下面的所有动作event都是被这个A给拦截了,不再调用自定义ViewGroup的三个函数了,所有只打印3个。所以要想所有的move都能被MyViewGroup处理,只要onTouchEvent返回true,可以设置这个MyViewGroup在xml的属性可点击或重OnTouchEvent,super后返回true。
还是上面例子,假如我现在在MyViewGroup中onTouchEvent返回false不拦截,且设置ViewGroup,view都可点击
在viewGroup中重写onInterceptTouchEvent

 @Override    public boolean onInterceptTouchEvent(MotionEvent event) {        boolean isIntercept = false;        switch (event.getAction()) {           case MotionEvent.ACTION_DOWN:           {               isIntercept = false;               break;           }           case MotionEvent.ACTION_MOVE:           {               isIntercept=true;                break;           }       }        Log.d("Debug", "MyViewGroup:onInterceptTouchEvent " + isIntercept);        return isIntercept;    }

在view中重写如下dispatchTouchEvent

public boolean dispatchTouchEvent(MotionEvent ev) {        getParent().requestDisallowInterceptTouchEvent(true);//1        boolean dispatch = super.dispatchTouchEvent(ev);        Log.d("Debug", "MyView:dispatchTouchEvent " + dispatch);        return dispatch;    }

打印结果
MyViewGroup:onInterceptTouchEvent false
MyView:OnTouchEvent true
MyView:dispatchTouchEvent true
MyViewGroup:dispatchTouchEvent true
MyView:OnTouchEvent true
MyView:dispatchTouchEvent true
…..(还有很多)
分析:ViewGroup在down时不拦截,view处理了down,两个dispatchTouchEvent都返回true,但在move时明明写了onInterceptTouchEvent 返回true拦截,结果还是没拦截,是因为getParent().requestDisallowInterceptTouchEvent(true);看源码如果disallowIntercept=true就不会去执行onInterceptTouchEvent,直接不拦截,以后的event还是都由子view执行。
参考:http://blog.csdn.net/guolin_blog/article/details/9097463
http://blog.csdn.net/a62321780/article/details/51986515
http://www.jianshu.com/p/34cb396104a7