带你从源码角度分析ViewGroup中事件分发流程

来源:互联网 发布:服装生产软件 编辑:程序博客网 时间:2024/05/23 13:11

序言

这篇博文不是对事件分发机制全面的介绍,只是从源码的角度分析ACTION_DOWN、ACTION_MOVE、ACTION_UP事件在ViewGroup中的分发逻辑,了解各个事件在ViewGroup的分发逻辑对理解、解决滑动冲突问题很有帮助。

ViewGroup中事件分发流程

这里我是用的是2.3.3版本的源码,原因在于这个版本的源码易读,当你理顺了整个分发流程,再去看其他更加高级版本的源码,你会发现思想是一样。如果一个事件传递给ViewGroup,那么dispatchTouchEvent方法会被调用,以下是对dispatchTouchEvent的分析。

public boolean dispatchTouchEvent(MotionEvent ev) {        if (!onFilterTouchEventForSecurity(ev)) {            return false;        }        final int action = ev.getAction();//获取事件的坐标        final float xf = ev.getX();        final float yf = ev.getY();        final float scrolledXFloat = xf + mScrollX;        final float scrolledYFloat = yf + mScrollY;        final Rect frame = mTempRect; //disallowIntercept 默认是false,//可以通过requestDisallowItercepctTouchEvent来设置参数//被设置成true后,ViewGroup无法拦截除ACTION_DOWN以外的事件(只能拦截Down)        boolean disallowIntercept = (mGroupFlags & FLAG_DISALLOW_INTERCEPT) != 0; //这里是ACTION_DOWN的处理逻辑        if (action == MotionEvent.ACTION_DOWN) {            if (mMotionTarget != null) {                // this is weird, we got a pen down, but we thought it was                // already down!                //We should probably send an ACTION_UP to the current target                mMotionTarget = null;            }            // If we're disallowing intercept or if we're allowing and we didn't            // intercept//默认情况下disallowIntercept为false,表示允许拦截,               //默认情况ViewGroup的onInterceptTouchEvent返回false            if (disallowIntercept || !onInterceptTouchEvent(ev)) {                // reset this event's action (just to protect ourselves)                ev.setAction(MotionEvent.ACTION_DOWN);                // We know we want to dispatch the event down, find a child                // who can handle it, start with the front-most child.                final int scrolledXInt = (int) scrolledXFloat;                final int scrolledYInt = (int) scrolledYFloat;                final View[] children = mChildren;                final int count = mChildrenCount;                for (int i = count - 1; i >= 0; i--) {//遍历子View                    final View child = children[i];//判断子元素是否可以接收到事件,两条件决定//条件一:子View是VISIBLE或者在播动画//条件二:点击坐标落在子View区域内(体现在内嵌的if)                    if ((child.mViewFlags & VISIBILITY_MASK) == VISIBLE                            || child.getAnimation() != null) {                        child.getHitRect(frame);                       //判断是否点击坐标落在子控件区域内                        if (frame.contains(scrolledXInt, scrolledYInt)) {                            final float xc = scrolledXFloat - child.mLeft;                            final float yc = scrolledYFloat - child.mTop;                            ev.setLocation(xc, yc);                            child.mPrivateFlags &= ~CANCEL_NEXT_UP_EVENT;                            //将事件派发给子View,返回true表示子View处理该事件,                            if (child.dispatchTouchEvent(ev))  {                                // Event handled, we have a target now.                                mMotionTarget = child;//将处理事件的目标View保存在变量                                return true;//返回true,表示消耗事件                            }                            // The event didn't get handled, try the next view.                            // Don't reset the event's location, it's not                            // necessary here.                        }                    }                }            }        }        boolean isUpOrCancel = (action == MotionEvent.ACTION_UP) ||                (action == MotionEvent.ACTION_CANCEL);        if (isUpOrCancel) {           //重置mGroupFlags,//使得在下一个事件ACTION_DOWN来临时disallowIntercept为false            mGroupFlags &= ~FLAG_DISALLOW_INTERCEPT;        }        // The event wasn't an ACTION_DOWN, dispatch it to our target if        // we have one.        final View target = mMotionTarget;        if (target == null) {//没有找到可以处理事件的子View            // We don't have a target, this means we're handling the            // event as a regular view.            ev.setLocation(xf, yf);            if ((mPrivateFlags & CANCEL_NEXT_UP_EVENT) != 0) {                ev.setAction(MotionEvent.ACTION_CANCEL);                mPrivateFlags &= ~CANCEL_NEXT_UP_EVENT;            }            //子控件不处理,所以此处判断一下自己是否处理    //此时ViewGroup调用的是父类View的dispatchTouchEvent            return super.dispatchTouchEvent(ev);        }        // if have a target, see if we're allowed to and want to intercept its        // events//允许并且想要拦截事件        if (!disallowIntercept && onInterceptTouchEvent(ev)) {            final float xc = scrolledXFloat - (float) target.mLeft;            final float yc = scrolledYFloat - (float) target.mTop;            mPrivateFlags &= ~CANCEL_NEXT_UP_EVENT;            ev.setAction(MotionEvent.ACTION_CANCEL);//设置ACTION_CANCEL            ev.setLocation(xc, yc);            if (!target.dispatchTouchEvent(ev)) {//告知目标子控件事件被拦截                // target didn't handle ACTION_CANCEL. not much we can do                // but they should have.            }            // clear the target            mMotionTarget = null;//重置为null,使得下个事件来临时target=null            // Don't dispatch this event to our own view, because we already            // saw it when intercepting; we just want to give the following            // event to the normal onTouchEvent().            return true;//返回true,表示事件被消耗        }        if (isUpOrCancel) {            mMotionTarget = null;        }        // finally offset the event to the target's coordinate system and        // dispatch the event.        final float xc = scrolledXFloat - (float) target.mLeft;        final float yc = scrolledYFloat - (float) target.mTop;        ev.setLocation(xc, yc);        if ((target.mPrivateFlags & CANCEL_NEXT_UP_EVENT) != 0) {            ev.setAction(MotionEvent.ACTION_CANCEL);            target.mPrivateFlags &= ~CANCEL_NEXT_UP_EVENT;            mMotionTarget = null;        }        //将事件分发给目标子控件        return target.dispatchTouchEvent(ev);    }

我们知道一些操作会产生事件,比方说在屏幕上滑动一下,这样会产生一系列事件,但这些事件是属于同一序列,它以ACTION_DOWN事件开始,中间有若干个ACTION_MOVE事件,以ACTION_UP事件结束。

ACTION_DOWN事件分发过程

ACTION_DOWN事件被分发到ViewGroup的时候是如何进行逻辑判断的呢,在第31行代码可以看到,首先会通过条件:disallowIntercept||!onInterceptTouchEvent判断是否拦截,disallowIntercept 默认是false,可以通过requestDisallowItercepctTouchEvent来设置参数,若这个条件不成立表示拦截则此时mMotionTarget = null,则来到第80行,那么target = null,表示没有找到可以处理事件的子控件,接下来执行第91行代码,此时调用super.dispatchTouchEvent即View的dispatchTouchEvent方法表示ViewGroup尝试自己处理事件;若条件成立表示不拦截,见第40行代码首先是遍历该ViewGroup的子控件,结合第45、49行代码可以知道在每遍历一个子控件的时候,首先判断子控件是不是visiable或者正在播动画并且点击事件的坐标落在子控件的区域内,假如两个条件同时满足则表明子控件可以接收事件,这时候会来到第55行代码,通过调用child.dispatchTouchEvent将事件分发给子控件,如果child.dispatchTouchEvent返回true,则表示事件被该子控件消耗了,此时执行第57行,该子控件被视作消耗事件的目标View并将其保存mMotionTarget变量中,返回true结束循环遍历;如果child.dispatchTouchEvent返回false,则表示该子控件没有消耗事件,如果该子控件不是ViewGroup遍历的最后一个子控件,则在继续循环遍历下一个子控件。如果此时该子控件是ViewGroup遍历的最后一个子控件,则表明所有的ViewGroup的子控件均不能处理事件,此时循环遍历结束,则mMotiononView = null,程序来到第80行,那么target = null,接下来调用super.dispatchTouchEvent即View的dispatchTouchEvent方法表明ViewGroup尝试自己处理事件.

ACTION_DOWN事件用流程图表示如下:



ACTION_MOVE事件分发过程

ACTION_MOVE事件被分发到ViewGroup的时候,从第81行可以看到首先会判断target是否等于null,若等于null,表示子控件均不能消耗事件,则调用super.dispatchTouchEvent即View的dispatchTouchEvent来处理事件。若不等于空,此时会来到第97行,此时通过条件(!disallowIntercept&&onInterceptTouchEvent(ev))判断是否拦截,若条件不成立表示不拦截则执行第131行代码,调用target.dispatchTouchEvent将事件分发给目标子控件处理,如果拦截则首先生成ACTION_CANCEL事件(见第101行)并分发给目标子控件target(见第103行),告知事件已被拦截,之后执行第108行将mMotionTarget重置为null,目的是让接下来的ACTION_UP事件直接能给ViewGroup自己处理,最后在第112行返回true表示事件被消耗。

ACTION_MOVE事件用流程图表示如下:



ACTION_UP事件分发过程

ACTION_UP分发到ViewGroup的时候,首先会通过执行mGrouFlags & = ~FALG_DISALLOW_INTERCEPT使得下一个ACTION_DOWN事件来临时disallowIntecept重置为默认的false,之后的处理逻辑和ACTION_MOVE基本一致,这里不再重复

ACTION_UP事件用流程图表示如下:



1 0
原创粉丝点击