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

序言

这篇博文不是对事件分发机制全面的介绍,只是从源码的角度分析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事件用流程图表示如下:

带你从源码角度分析ViewGroup中事件分发流程_第1张图片


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事件用流程图表示如下:

带你从源码角度分析ViewGroup中事件分发流程_第2张图片


ACTION_UP事件分发过程

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

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

带你从源码角度分析ViewGroup中事件分发流程_第3张图片


你可能感兴趣的:(Android疑难点,android,事件分发,ontouch,onTouchEvent)