源码角度把Android事件处理与分发理透

针对单点触摸

一.首先得知道有什么事件

MotionEvent

在这里插入图片描述

注意:

ACTION_MOVE会多次触发(体现为源码中同一块代码会调用多次)。
ACTION_CANCLE先记住,是事件被上层拦截时触发,至于具体的,后面就知道了。

二.DOWN事件处理与分发

知道了有什么事件,下面就得知道谁能怎么处理事件

具体来说,就是继承自View的只能处理事件,继承自ViewGroup的才能分发事件。ViewGroup先要走分发流程,再走处理流程。而View只能走处理流程。看下图

在这里插入图片描述

1.事件传递的顺序:当一个事件发生的时候,它传递的顺序,可以用这么一张图来表示

在这里插入图片描述
触摸屏幕后,首先接触到事件的是Activity,下面让我们从源码角度理解此流程
进入Activity类的dispatchTouchEvent方法

    public boolean dispatchTouchEvent(MotionEvent ev) {
    
    
        if (ev.getAction() == MotionEvent.ACTION_DOWN) {
    
    
            onUserInteraction();
        }
        if (getWindow().superDispatchTouchEvent(ev)) {
    
    
            return true;
        }
        return onTouchEvent(ev);
    }

第一个if不需要我们管,看第二个if,它调用了PhoneWindowsuperDispatchTouchEvent方法,让我们继续追踪

@Override
    public boolean superDispatchTouchEvent(MotionEvent event) {
    
    
        return mDecor.superDispatchTouchEvent(event);
    }

发现它调用了DecorViewsuperDispatchTouchEvent方法,我们继续追踪

public boolean superDispatchTouchEvent(MotionEvent event) {
    
    
        return super.dispatchTouchEvent(event);
    }

DecorViewsuperFrameLayout
在这里插入图片描述
但是由于FrameLayout没有实现dispatchTouchEvent方法,所以此方法最最终在ViewGroup里面执行。我们追踪它,就进入了ViewGroup的此方法
在这里插入图片描述
然后就开始事件分发了。事件分发在ViewGroup.java中。事件处理在View.java中,有两个方法,即dispatchTouchEvent和onTouchEvent。

那么就有人会问了,ViewdispatchTouchEventViewGroupdispatchTouchEvent有什么区别呢?其实是这样的,我们知道,ViewGroup是继承View的,所以它重写了ViewdispatchTouchEvent方法,使得它具有事件分发功能。而具体如何分发,后面来分析。

2.一个小案例,理解View处理事件

插播一个小知识:什么是冲突?事件只有一个,但是有多个人想要处理,如果处理的人不是我们想要的那个人,我们就说发生了冲突

来看这样一个非常典型的例子:
在这里插入图片描述
比如这个例子,我们想让onTouch处理,结果onClick处理了,那么我们就说发生了冲突。经过测试我发现,当我在onTouch方法中返回true的时候,onClick方法不执行,当返回false的时候,onClick方法执行。此时问题来了,什么时候onTouch处理,什么时候onClick处理呢?我们能否自己控制呢?
刚刚说了,事件处理在ViewdispatchTouchEvent中,所以我们直接进入源码找答案

public boolean dispatchTouchEvent(MotionEvent event) {
    
    
        	....
            if (li != null && li.mOnTouchListener != null
                    && (mViewFlags & ENABLED_MASK) == ENABLED
                    && li.mOnTouchListener.onTouch(this, event)) {
    
    
                result = true;
            }

            if (!result && onTouchEvent(event)) {
    
    
                result = true;
            }
        
			....
        return result;
}

可以通过源码发现,在第一个if的最后一个条件之前,所有的条件都为true。而我们的onTouch方法就是重写了最后一个条件的那个语句,即li.mOnTouchListener.onTouch(this, event),当我们onTouch返回true的时候,result赋值为true,当返回false的时候,result也赋值为false。然后我们看下一个if。当是第一种情况的时候,resulttrue之后为false,所以onTouchEvent方法不会执行。而当是第二种情况的时候,resultfalse之后为true,所以onTouchEvent就会执行。这个和前面的测试结果好像很像,所以我们不妨做个大胆的推测,即onClick方法就在onTouchEvent方法中调用。口说无凭,我们追踪进入

public boolean onTouchEvent(MotionEvent event) {
    
    
        ...
        if (clickable || (viewFlags & TOOLTIP) == TOOLTIP) {
    
    
            switch (action) {
    
    
                case MotionEvent.ACTION_UP:
                 				 ...
                                if (!post(mPerformClick)) {
    
    
                                    performClickInternal();
                                }
                       			 ...
      

我们知道onClick方法是在ACTION_UP的时候执行的,所以我们进入相应的case语句,找到performClickInternal方法(略去大量无关代码),追踪

    private boolean performClickInternal() {
    
    
        // Must notify autofill manager before performing the click actions to avoid scenarios where
        // the app has a click listener that changes the state of views the autofill service might
        // be interested on.
        notifyAutofillManagerOnClick();

        return performClick();
    }

追踪performClick

	public boolean performClick() {
    
    
        ...
        if (li != null && li.mOnClickListener != null) {
    
    
            playSoundEffect(SoundEffectConstants.CLICK);
            li.mOnClickListener.onClick(this);
            result = true;
        } else {
    
    
            result = false;
        }
		...
    }

终于在if中找到了onClick的调用。同时resulttrue,意为告诉父容器,此事件我已处理了。

刚刚我们通过一个小案例,一个小事件冲突,体会到了View事件处理的机制以及流程,也就是说View的事件处理就是onTouchonClick。除此之外,我们也体会到了一个小的事件冲突的处理是怎样的。也就是说,在这里插入图片描述这张图的红框部分我们已经解决了,接下来我们看ViewGroup的分发事件,是如何处理的

3.ViewGroup的事件分发源码解析

我们不妨类比这么一张图

在这里插入图片描述
DecorView其实就是个ViewGroup。下面这些总监也是各种ViewGroup。我们的事件分发其实就是在总经理这里开始分发,一层一层地来。
第一个层级:总经理,即ViewGroup
第二个层级:总监,也是ViewGroup
第三个层级:比如清洁工,有可能是View,也可能是ViewGroup
在这里插入图片描述比如这张图,如果清洁是ImageButton那就是View,如果它是清洁工的头,比如LinearLayout,那么他就是ViewGroup。但是我们这里为了方便理解清楚,默认规定为普通清洁工,即View(其实都一样)。

我们首先看总经理,也就是ViewGroup的事件分发。源码进入ViewGroup.java,找到dispatchTouchEvent

下面是拦截的情况(也就是不分发,或者是分发了,但是都不处理。如果子View都不处理,也只能自己处理)
@Override
    public boolean dispatchTouchEvent(MotionEvent ev) {
    
    
     	...
        if (onFilterTouchEventForSecurity(ev)) {
    
    
            final int action = ev.getAction();
            final int actionMasked = action & MotionEvent.ACTION_MASK;

            // Handle an initial down.
            if (actionMasked == MotionEvent.ACTION_DOWN) {
    
    
 				//-----------------------------------
 				//-----------------------------------
 				//如果是DOWN事件,则进行一些清0操作
 				//-----------------------------------
 				//-----------------------------------
                cancelAndClearTouchTargets(ev);
                resetTouchState();
            }

            //-----------------------------------
            //-----------------------------------
			//下面办的事就是判断事件是否需要拦截
			//-----------------------------------
			//-----------------------------------
            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); // restore action in case it was changed
                } else {
    
    
                    intercepted = false;
                }
            } else {
    
    
                // There are no touch targets and this action is not an initial down
                // so this view group continues to intercept touches.
                intercepted = true;
            }
			...

            //-----------------------------------
            //-----------------------------------
			//如果拦截了,就会走到这个if里面
			//-----------------------------------
			//-----------------------------------
            if (mFirstTouchTarget == null) {
    
    
                // No touch targets so treat this as an ordinary view.
                handled = dispatchTransformedTouchEvent(ev, canceled, null,
                        TouchTarget.ALL_POINTER_IDS);
            } else {
    
    
              。。。
    }
虽然注释写的很清楚,但是这里还是要解释一下:

首先如果判断是否是DOWN事件,如果是则进行一些清0操作(我们本来就是分析的DOWN事件,所以此条件一定成立),然后判断事件是否需要拦截。
我们这里是需要拦截的情况,事件拦截了,也就是说onInterceptTouchEvent方法的返回值为true,那么相当于拦截的这个ViewGroup是最后一个,就得面临一个选择,即事件到底处不处理,然后走入if (mFirstTouchTarget == null)中,执行dispatchTransformedTouchEvent方法,这个方法,就是是否处理。追踪进入。我们结合前面的可以知道,他这里child的参数为null,说明它这里不是让孩子处理的,而是给自己处理的。

private boolean dispatchTransformedTouchEvent(MotionEvent event, boolean cancel,
            View child, int desiredPointerIdBits) {
    
    
        ...
        if (newPointerIdBits == oldPointerIdBits) {
    
    
            if (child == null || child.hasIdentityMatrix()) {
    
    
                if (child == null) {
    
    
                    handled = super.dispatchTouchEvent(event);
      				...

省略无关代码,发现会执行super.dispatchTouchEvent,这里就到View里面去了。所以ViewGroup的处理事件,就是交给View的。就又看它是onTouch处理还是onClick处理,就是刚刚那个案例。

下面是不拦截的情况(也就是分发)
@Override
    public boolean dispatchTouchEvent(MotionEvent ev) {
    
    
        	//-----------------------------------
            //-----------------------------------
			//判断是否拦截,假设这里是不拦截
			//-----------------------------------
			//-----------------------------------
            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); // restore action in case it was changed
                } else {
    
    
                    intercepted = false;
                }
            } else {
    
    
                // There are no touch targets and this action is not an initial down
                // so this view group continues to intercept touches.
                intercepted = true;
            }
			...
            if (!canceled && !intercepted) {
    
    
                //-----------------------------------
	            //-----------------------------------
				//如果拦截了,就会走到这个if里面,而不走上面说的那个if里面
				//-----------------------------------
				//-----------------------------------
                View childWithAccessibilityFocus = ev.isTargetAccessibilityFocus()
                        ? findChildWithAccessibilityFocus() : null;
				//-----------------------------------
	            //-----------------------------------
				//进来之后首先会判断一下,如果为down事件才进行分发
				//-----------------------------------
				//-----------------------------------
                if (actionMasked == MotionEvent.ACTION_DOWN
                        || (split && actionMasked == MotionEvent.ACTION_POINTER_DOWN)
                        || actionMasked == MotionEvent.ACTION_HOVER_MOVE) {
    
    
                  	...
                  	 //-----------------------------------
		            //-----------------------------------
					//newTouchTarget一定为null,而第二个条件,就是说
					//比如总经理,它的childrenCount就是那5个总监的5,不包括下面在子View
					//满足条件,进入此if
					//-----------------------------------
					//-----------------------------------
                    if (newTouchTarget == null && childrenCount != 0) {
    
    
                       	...
                       	//-----------------------------------
			            //-----------------------------------
						//进入buildTouchDispatchChildList,进行一个排序
						//-----------------------------------
						//-----------------------------------
                        final ArrayList<View> preorderedList = buildTouchDispatchChildList();
                        final boolean customOrder = preorderedList == null
                                && isChildrenDrawingOrderEnabled();
                        final View[] children = mChildren;
                        //-----------------------------------
			            //-----------------------------------
						//倒序取,取出各种总监,假如取出来的是市场总监
						//-----------------------------------
						//-----------------------------------
                        for (int i = childrenCount - 1; i >= 0; i--) {
    
    
                            final int childIndex = getAndVerifyPreorderedIndex(
                                    childrenCount, i, customOrder);
                            final View child = getAndVerifyPreorderedView(
                                    preorderedList, children, childIndex);
                            //-----------------------------------
				            //-----------------------------------
							//会进行一个判断,虽然市场总监是总监,但是到底有没有能力呢?
							//所以会进行两个判断
							//1.第一个是判断View能否接收事件,有两个条件,
							//第一个是View是不是显示的即VISIBLE
							//第二个是,如果不是VISIBLE,那就是有可能是动画,即animation,不是VISIBLE,有动画也行
							//2.第二个条件是,point要在View的范围内
							//如果市场总监没有这个能力,就分到下一个总监即continue
							//-----------------------------------
							//-----------------------------------
                            if (!child.canReceivePointerEvents()
                                    || !isTransformedTouchPointInView(x, y, child, null)) {
    
    
                                continue;
                            }
                            ...
                            //-----------------------------------
				            //-----------------------------------
							//又看到了dispatchTransformedTouchEvent分发处理事件方法,但是参数不同
							//它的第三个参数不是null,而是child,就是现在的市场总监,对市场总监进行事件分发
							//-----------------------------------
							//-----------------------------------
                            if (dispatchTransformedTouchEvent(ev, false, child, idBitsToAssign)) {
    
    
                             	//-----------------------------------
					            //-----------------------------------
								//有可能dispatchTransformedTouchEvent返回false,也就是说
								//市场总监不处理,那么就不会进入if,就开始下一个for循环。
								//对应上面例图就是说总经理问完市场总监,市场总监不行,那就去问工程总监
								//-----------------------------------
								//-----------------------------------
                                // Child wants to receive touch within its bounds.
                                mLastTouchDownTime = ev.getDownTime();
                                if (preorderedList != null) {
    
    
                                    // childIndex points into presorted list, find original index
                                    for (int j = 0; j < childrenCount; j++) {
    
    
                                        if (children[childIndex] == mChildren[j]) {
    
    
                                            mLastTouchDownIndex = j;
                                            break;
                                        }
                                    }
                                } else {
    
    
                                    mLastTouchDownIndex = childIndex;
                                }
                                //-----------------------------
                                //-----------------------------
                                //如果事件要处理的话,那么就会进行一些赋值。比如说
                                //newTouchTarget = mFirstTouchTarget != null
                                //target.next = null
                                //alreadyDispatchedToNewTouchTarget = true;
                                //-----------------------------
                                //-----------------------------
                                mLastTouchDownX = ev.getX();
                                mLastTouchDownY = ev.getY();
                                newTouchTarget = addTouchTarget(child, idBitsToAssign);
                                alreadyDispatchedToNewTouchTarget = true;
                                break;//直接结束for循环,后续就不该工程总监或者其他总监的事情了
                            }
			...
            // Dispatch to touch targets.
            if (mFirstTouchTarget == null) {
    
    
                // No touch targets so treat this as an ordinary view.
                handled = dispatchTransformedTouchEvent(ev, canceled, null,
                        TouchTarget.ALL_POINTER_IDS);
            } else {
    
    
       			//-----------------------------------
	            //-----------------------------------
				//mFirstTouchTarget不为null,所以走else。这个while循环只循环一次。
				//因为循环一次后target就为null了
				//-----------------------------------
				//-----------------------------------
                TouchTarget predecessor = null;
                TouchTarget target = mFirstTouchTarget;
                while (target != null) {
    
    
                    final TouchTarget next = target.next;
                    //-----------------------------------
					//-----------------------------------
					//这里if语句条件满足,所以进入这里
					//-----------------------------------
					//-----------------------------------
                    if (alreadyDispatchedToNewTouchTarget && target == newTouchTarget) {
    
    
                        handled = true;
                        //因为市场总监已经处理了,所以总经理就不处理了,所以handled赋值为true
                    } else {
    
    
                       ...

这里要纠正我们之前一个不太确切的观点:如果连DOWN事件都拿不到,那么MOVE和UP事件也拿不到。其实这是针对叶子节点的,也就是比如上面例图中的高空清洁,但是如果它是总监的话,就不同了。它没有处理DOWN事件,也可能处理MOVE事件。

首先判断是否需要拦截,这里是不拦截的情况。然后进入 if (!canceled && !intercepted)
然后判断,如果是DOWN才能进行事件分发。然后到了第二个if,if (newTouchTarget == null && childrenCount != 0)newTouchTarget一定为null,而第二个条件,就是说比如总经理,它的childrenCount就是那5个总监的5,不包括下面的子View。满足条件,进入此if,然后得到一个排好序的preorderedList怎么排的序呢?


追踪buildTouchDispatchChildList

public ArrayList<View> buildTouchDispatchChildList() {
    
    
        return buildOrderedChildList();
    }

追踪buildOrderedChildList

ArrayList<View> buildOrderedChildList() {
    
    
        final int childrenCount = mChildrenCount;
        if (childrenCount <= 1 || !hasChildWithZ()) return null;

        if (mPreSortedChildren == null) {
    
    
            mPreSortedChildren = new ArrayList<>(childrenCount);
        } else {
    
    
            // callers should clear, so clear shouldn't be necessary, but for safety...
            mPreSortedChildren.clear();
            mPreSortedChildren.ensureCapacity(childrenCount);
        }

        final boolean customOrder = isChildrenDrawingOrderEnabled();
        for (int i = 0; i < childrenCount; i++) {
    
    
            // add next child (in child order) to end of list
            final int childIndex = getAndVerifyPreorderedIndex(childrenCount, i, customOrder);
            final View nextChild = mChildren[childIndex];
            final float currentZ = nextChild.getZ();

            // insert ahead of any Views with greater Z
            int insertIndex = i;
            while (insertIndex > 0 && mPreSortedChildren.get(insertIndex - 1).getZ() > currentZ) {
    
    
                insertIndex--;
            }
            mPreSortedChildren.add(insertIndex, nextChild);
        }
        return mPreSortedChildren;
    }

之后返回已经排好序的数组mPreSortedChildren
排序是根据Z值来排的。这个Z值一般用不到。所以如果不写Z值,那么就默认根据你写的顺序来排序。比如FrameLayout包括ViewPagerTextView,那么ViewPager就排在TextView的前面,以此类推。越是往前的,它就优先接收事件。
这里有意思的是它是根据一个倒序来取东西,也就是说如果想让谁先处理事件,就把他放在数组的后面。


然后我们再回到上面,preorderedList 接收了排好序的数组。然后倒序取,然后会进行一个
if (!child.canReceivePointerEvents()|| !isTransformedTouchPointInView(x, y, child, null)) ,判断子view即市场总监有没有能力处理事件
追踪canReceivePointerEvents

 protected boolean canReceivePointerEvents() {
    
    
        return (mViewFlags & VISIBILITY_MASK) == VISIBLE || getAnimation() != null;
    }

1.第一个是判断View能否接收事件,有两个条件,
第一个是View是不是显式的即VISIBLE
第二个是,如果不是VISIBLE,那就是有可能是动画,即animation,不是VISIBLE,有动画也行
2.第二个条件是,point要在View的范围内
如果市场总监没有这个能力,就分到下一个总监即continue

情况①:由自己处理或者分发给自己的子控件处理

又看到了dispatchTransformedTouchEvent分发处理事件方法,但是参数不同了。它的第三个参数不是null,而是child,就是现在的市场总监,(因为我们一开始是面对的总经理,所以它的第一个孩子是市场总监)
我们进入dispatchTransformedTouchEvent看看

private boolean dispatchTransformedTouchEvent(MotionEvent event, boolean cancel,
            View child, int desiredPointerIdBits) {
    
    
       	...
        if (child == null) {
    
    
            handled = super.dispatchTouchEvent(transformedEvent);
        } else {
    
    
        	//-----------------------------------
           	//-----------------------------------
			//因为child不为null,所以就进入到else里面
			//-----------------------------------
			//-----------------------------------
            final float offsetX = mScrollX - child.mLeft;
            final float offsetY = mScrollY - child.mTop;
            transformedEvent.offsetLocation(offsetX, offsetY);
            if (! child.hasIdentityMatrix()) {
    
    
                transformedEvent.transform(child.getInverseMatrix());
            }
			//-----------------------------------
           	//-----------------------------------
			//这里的dispatchTouchEvent,一定会走ViewGroup的dispatchTouchEvent,而不是View的。
			//相当于是一个递归,又是开始之前的情况,首先判断是否拦截,如果拦截怎么怎么样,如果不拦截怎么怎么样
			//-----------------------------------
			//-----------------------------------
            handled = child.dispatchTouchEvent(transformedEvent);
        }

        // Done.
        transformedEvent.recycle();
        return handled;
    }

对市场总监进行事件分发。如果事件要处理的话,那么就会进行一些赋值。比如说

newTouchTarget = mFirstTouchTarget != null
target.next = null
alreadyDispatchedToNewTouchTarget = true

然后一个break,直接结束for循环,后续就不该工程总监或者其他总监的事情了
然后走下面的elsemFirstTouchTarget不为null,所以走else。这个while循环只循环一次。因为循环一次后target就为null
然后走if (alreadyDispatchedToNewTouchTarget && target == newTouchTarget)。因为市场总监已经处理了,所以总经理就不处理了,所以handled赋值为true,意为已经处理了。

情况②:市场总监不处理,由其他总监处理

刚刚是市场总监处理的情况,但是有可能dispatchTransformedTouchEvent返回false,也就是说市场总监不处理,那么就不会进入if,就开始下一个for循环。
对应上面例图就是说总经理问完市场总监,市场总监不行,那就去问工程总监。

4.总结

在上面的分析之后,我们知道了在DOWN之后的事件分发流程,走完这个流程后,我们就能确定,这个事件到底是谁来处理。也就是说DOWN事件处理完之后就能确定到底是谁来处理此事件。这就是View的事件处理以及ViewGroup的事件分发流程源码解析,后面还有与MOVE相关的知识,我将在下一篇博客中进行相关介绍!

三.MOVE事件处理与分发

1.说明

MOVE事件来了后,还是要先分析总经理,从总经理开始,而不是直接分析DOWN事件选择的处理事件的某位总监。比如刚刚DOWN事件决定市场研发来处理事件,那么MOVE事件来了之后,首先分析总经理,然后分析市场总监,然后是市场研发。
首先还是要走ViewGroupdispatchTouchEvent

2.MOVE正常流程分析源码

public boolean dispatchTouchEvent(MotionEvent ev) {
    
    
      		...
      		//--------------------------
      		//--------------------------
      		//因为是MOVE事件,所以这里不进去
      		//--------------------------
      		//--------------------------
            if (actionMasked == MotionEvent.ACTION_DOWN) {
    
    
                // Throw away all previous state when starting a new touch gesture.
                // The framework may have dropped the up or cancel event for the previous gesture
                // due to an app switch, ANR, or some other state change.
                cancelAndClearTouchTargets(ev);
                resetTouchState();
            }

            //--------------------------
      		//--------------------------
      		//虽然是MOVE事件,但是这里mFirstTouchTarget 不为null,所以还是会进入if
      		//--------------------------
      		//--------------------------
            if (actionMasked == MotionEvent.ACTION_DOWN
                    || mFirstTouchTarget != null) {
    
    
                final boolean disallowIntercept = (mGroupFlags & FLAG_DISALLOW_INTERCEPT) != 0;
                if (!disallowIntercept) {
    
    
                    intercepted = onInterceptTouchEvent(ev);
                    ev.setAction(action); // restore action in case it was changed
                } else {
    
    
                	//--------------------------
		      		//--------------------------
		      		//没有拦截,所以这里为false
		      		//--------------------------
		      		//--------------------------
                    intercepted = false;
                }
            }
            ...
            //--------------------------
      		//--------------------------
      		//会进入此if
      		//--------------------------
      		//--------------------------
            if (!canceled && !intercepted) {
    
    
                View childWithAccessibilityFocus = ev.isTargetAccessibilityFocus()
                        ? findChildWithAccessibilityFocus() : null;
				//--------------------------
	      		//--------------------------
	      		//因为是MOVE事件,所以不会进入此if
	      		//又因为此if是用来分发事件的,所以MOVE事件不会分发事件
	      		//--------------------------
	      		//--------------------------
                if (actionMasked == MotionEvent.ACTION_DOWN
                        || (split && actionMasked == MotionEvent.ACTION_POINTER_DOWN)
                        || actionMasked == MotionEvent.ACTION_HOVER_MOVE) {
    
    
           		...
            //--------------------------
      		//--------------------------
      		//因为不为null,所以它不走if,去else
      		//--------------------------
      		//--------------------------
            if (mFirstTouchTarget == null) {
    
    
                // No touch targets so treat this as an ordinary view.
                handled = dispatchTransformedTouchEvent(ev, canceled, null,
                        TouchTarget.ALL_POINTER_IDS);
            } else {
    
    
                // 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;
                    //--------------------------
		      		//--------------------------
		      		//alreadyDispatchedToNewTouchTarget 在这里之前被赋值为false了,所以这里进入false
		      		//--------------------------
		      		//--------------------------
                    if (alreadyDispatchedToNewTouchTarget && target == newTouchTarget) {
    
    
                        handled = true;
                    } else {
    
    
                    	//--------------------------
			      		//--------------------------
			      		//这里cancelChild 被赋值为false
			      		//--------------------------
			      		//--------------------------
                        final boolean cancelChild = resetCancelNextUpFlag(target.child)
                                || intercepted;
                        //--------------------------
			      		//--------------------------
			      		//分发给市场总监,让我们追踪进入
			      		//--------------------------
			      		//--------------------------        
                        if (dispatchTransformedTouchEvent(ev, cancelChild,
                                target.child, target.pointerIdBits)) {
    
    
                            handled = true;
                        }
//-----------------------------------------------------------------------------------
private boolean dispatchTransformedTouchEvent(MotionEvent event, boolean cancel,
            View child, int desiredPointerIdBits) {
    
    
			 ...
			//--------------------------
      		//--------------------------
      		//发现它这里又调用了ViewGroup的dispatchTouchEvent方法,也就是市场总监再进行一个分发
      		//比如分发给第一个子View,即市场研发,再走到这里,就调用View的dispatchTouchEvent方法
      		//调用View的dispatchTouchEvent方法就是处理事件了(最开始中有说到)
      		//--------------------------
      		//--------------------------  
            handled = child.dispatchTouchEvent(transformedEvent);
        }
        transformedEvent.recycle();
        return handled;
}
//-----------------------------------------------------------------------------------
                       if (cancelChild) {
    
    
                            if (predecessor == null) {
    
    
                                mFirstTouchTarget = next;
                            } else {
    
    
                                predecessor.next = next;
                            }
                            target.recycle();
                            target = next;
                            continue;
                        }
                    }
                    predecessor = target;
                    target = next;
                }
            }

            // Update list of touch targets for pointer up or cancel, if needed.
            if (canceled
                    || actionMasked == MotionEvent.ACTION_UP
                    || actionMasked == MotionEvent.ACTION_HOVER_MOVE) {
    
    
                resetTouchState();
            } else if (split && actionMasked == MotionEvent.ACTION_POINTER_UP) {
    
    
                final int actionIndex = ev.getActionIndex();
                final int idBitsToRemove = 1 << ev.getPointerId(actionIndex);
                removePointersFromTouchTargets(idBitsToRemove);
            }
        }

        if (!handled && mInputEventConsistencyVerifier != null) {
    
    
            mInputEventConsistencyVerifier.onUnhandledEvent(ev, 1);
        }
        return handled;
    }

也就是说市场总监和总经理的流程是一样的

  • 这里说的MOVE不进行事件分发,是说,在事件分发的时候,正常情况下都会去,比如总经理先问市场总监,然后如果他不干就去问下一个总监,这是正常情况下的事件分发。而MOVE事件就不一样了,它是直接找到市场总监,然后直接找到市场研发。也就是找到DOWN决定的事件处理对象

3.MOVE拦截流程:事件冲突

只能在MOVE事件中处理事件冲突。因为在DOWN中进行事件分发。而且父容器可以抢子View的事件,而子View不可以抢父容器的事件。子View一旦拿到了事件,那么事件再由谁处理就是子View说了算的。

内部拦截法:子View处理事件冲突

先看ViewGroupdispatchTouchEvent

 final boolean disallowIntercept = (mGroupFlags & FLAG_DISALLOW_INTERCEPT) != 0;
 if (!disallowIntercept) {
    
    
			 intercepted = onInterceptTouchEvent(ev);
       		 ev.setAction(action); // restore action in case it was changed
 } else {
    
    
       intercepted = false;
 }

如果onInterceptTouchEvent返回为true,那么intercepted 就为true,然后看往下看

if (!canceled && !intercepted) {
    
    

这个if就进不去了,而这个if就是管事件分发的,所以子View不会知道事件,因为事件被拦截了。那么有什么方法,可以不让父容器这么“独裁”吗?答案是肯定的。我们看

 final boolean disallowIntercept = (mGroupFlags & FLAG_DISALLOW_INTERCEPT) != 0;
 if (!disallowIntercept) {
    
    
			 intercepted = onInterceptTouchEvent(ev);
       		 ev.setAction(action); // restore action in case it was changed
 } else {
    
    
       intercepted = false;
 }

intercepted 被赋值之前,会有一个判断,也就是说如果disallowIntercepttrue,那么不管你onInterceptTouchEventtrue还是falseintercepted 都为false。所以事件就不会被拦截。这就是处理时间冲突的第一个思路,就是子View通过控制disallowIntercept来不让父容器拦截事件。
那么问题来了,通过什么方式能控制它的值呢?其实谷歌给我们提供了一个方法(我们有时也称为子View的尚方宝剑),即

  @Override
    public void requestDisallowInterceptTouchEvent(boolean disallowIntercept) {
    
    

        if (disallowIntercept == ((mGroupFlags & FLAG_DISALLOW_INTERCEPT) != 0)) {
    
    
            // We're already in this state, assume our ancestors are too
            return;
        }
	
        if (disallowIntercept) {
    
    
            mGroupFlags |= FLAG_DISALLOW_INTERCEPT;
        } else {
    
    
            mGroupFlags &= ~FLAG_DISALLOW_INTERCEPT;
        }

        // Pass it up to our parent
        if (mParent != null) {
    
    
            mParent.requestDisallowInterceptTouchEvent(disallowIntercept);
        }
    }

如果disallowIntercepttrue,然后 mGroupFlags |= FLAG_DISALLOW_INTERCEPT再& FLAG_DISALLOW_INTERCEPT != 0的结果绝对为true,那么这里就不会进入if
所以就可以看到我们下面这一张图片的代码①假设子ViewListView,父容器是ViewPager。一开始是DOWN的时候,我们不能让父容器拦截事件,要掌握在我们自己手里,一开始也说了,子View一旦拿到了事件,那么事件再由谁处理就是子View说了算的。然后MOVE的时候我们进行一个判断,如果X方向的移动距离大,那么就还是让给父容器处理,如果X方向的移动距离不如Y方向的,那么还是我这个子控件ListView处理。从这里也可以看出来,处理权就掌握在子View手中了
在这里插入图片描述
但是关注了这些还是不够,我们看源码,在赋值disallowIntercept之前,有一个重置处理,我在前面也提到过

			// Handle an initial down.
            if (actionMasked == MotionEvent.ACTION_DOWN) {
    
    
                // Throw away all previous state when starting a new touch gesture.
                // The framework may have dropped the up or cancel event for the previous gesture
                // due to an app switch, ANR, or some other state change.
                cancelAndClearTouchTargets(ev);
                resetTouchState();
            }

我们追踪resetTouchState

private void resetTouchState() {
    
    
        clearTouchTargets();
        resetCancelNextUpFlag(this);
        mGroupFlags &= ~FLAG_DISALLOW_INTERCEPT;
        mNestedScrollAxes = SCROLL_AXIS_NONE;
}

会发现它对mGroupFlags 进行了一个赋值。这就导致了DOWN事件的时候,disallowIntercept 就为false,因为mGroupFlags &= ~FLAG_DISALLOW_INTERCEPT & FLAG_DISALLOW_INTERCEPT != 0的结果绝对为false。然后又进入了if语句,所以我们就白搞了。所以为了解决这个问题,需要父容器做一些处理
,即在ViewPager中添加此方法
在这里插入图片描述
也就是说如果是DOWN事件,我就返回false,不管你重置与否,最后intercepted 都为false,这样就不拦截了。
以上都是讲的怎么不让父容器拦截以及相应流程,那么如果在这张图中,走了②,也就是说让父容器拦截,那么它的流程是怎样的呢
在这里插入图片描述

父容器拦截后的流程

这个父容器拦截,也是子控件让给父容器的,而不是父容器一开始就有的

	@Override
    public boolean dispatchTouchEvent(MotionEvent ev) {
    
    
      			...
      			//----------------------
      			//----------------------
      			//这里intercepted 赋值为true,也就是拦截
      			//----------------------
      			//----------------------
                if (!disallowIntercept) {
    
    
                    intercepted = onInterceptTouchEvent(ev);
                    ev.setAction(action); // restore action in case it was changed
                } else {
    
    
                    intercepted = false;
                }
            } else {
    
    
                // There are no touch targets and this action is not an initial down
                // so this view group continues to intercept touches.
                intercepted = true;
            }
			//下面这个if不走,也就是不进行分发
            if (!canceled && !intercepted) {
    
    
              ....
            if (mFirstTouchTarget == null) {
    
    
                // No touch targets so treat this as an ordinary view.
                handled = dispatchTransformedTouchEvent(ev, canceled, null,
                        TouchTarget.ALL_POINTER_IDS);
            } else {
    
    
                //----------------------
      			//----------------------
      			//要走这个else
      			//----------------------
      			//----------------------
             	...
                     else {
    
    
                    	//----------------------
		      			//----------------------
		      			//注意这里,关键点来了。这里因为intercepted为true,所以cancelchild为true,所以这里
		      			//----------------------
		      			//----------------------
                        final boolean cancelChild = resetCancelNextUpFlag(target.child)
                                || intercepted;
                        //----------------------
		      			//----------------------
		      			//然后追踪dispatchTransformedTouchEvent方法
		      			//----------------------
		      			//----------------------       
                        if (dispatchTransformedTouchEvent(ev, cancelChild,
                                target.child, target.pointerIdBits)) {
    
    
                            handled = true;
                        }
//-------------------------------------------------------------------------------
 private boolean dispatchTransformedTouchEvent(MotionEvent event, boolean cancel,
            View child, int desiredPointerIdBits) {
    
    
        final boolean handled;
     		//----------------------
			//----------------------
			//先保存旧动作,然后因为cancle为true,所以进入if
			//----------------------
			//----------------------       
        final int oldAction = event.getAction();
        if (cancel || oldAction == MotionEvent.ACTION_CANCEL) {
    
    
        	//----------------------
			//----------------------
			//如果我们的研究对象是总经理,那么child就是市场总监等
			//如果研究对象是市场总监等,那么child就是比如市场研发等
			//然后置为cancel
			//----------------------
			//----------------------       
            event.setAction(MotionEvent.ACTION_CANCEL);
            if (child == null) {
    
    
                handled = super.dispatchTouchEvent(event);
            } else {
    
    
	            //----------------------
				//----------------------
				//然后调用child的dispatchTouchEvent。相当于刚刚例子中的listview调用了cancel事件
				//也就是说它自己取消了这个事件,也就是本文中最最开始说的,cancel是上层拦截的时候触发,答案就在这里
				//----------------------
				//----------------------    
                handled = child.dispatchTouchEvent(event);
            }
            event.setAction(oldAction);
            return handled;
        }
		...
//-------------------------------------------------------------------------------              
                        if (cancelChild) {
    
    
                            if (predecessor == null) {
    
    
                            //----------------------
							//----------------------
							//然后这里将mFirstTouchTarget 赋值为next,也就是null
							//----------------------
							//----------------------  
                                mFirstTouchTarget = next;
                            } else {
    
    
                                predecessor.next = next;
                            }
							...

然后第一个MOVE结束了,第一个MOVE事件的目的是为了取消子控件处理事件(即取消ListView处理事件,让它把事件让给父容器),并且把mFirstTouchTarget 赋值为null。此时父容器即ViewPager还没有响应事件。然后进行第二个MOVE,我们分析父容器即ViewPager是怎么把事件抢过来的,即怎么接过来的子控件让给它的事件。

@Override
    public boolean dispatchTouchEvent(MotionEvent ev) {
    
    
     		...
     		//----------------------
			//----------------------
			//因为是MOVE事件,而且mFirstTouchTarget 为null,所以这个if就不进去了,直接去else
			//else把intercepted 赋值为了true
			//----------------------
			//----------------------  
            if (actionMasked == MotionEvent.ACTION_DOWN
                    || mFirstTouchTarget != null) {
    
    
                final boolean disallowIntercept = (mGroupFlags & FLAG_DISALLOW_INTERCEPT) != 0;
                if (!disallowIntercept) {
    
    
                    intercepted = onInterceptTouchEvent(ev);
                    ev.setAction(action); // restore action in case it was changed
                } else {
    
    
                    intercepted = false;
                }
            } else {
    
    
                // There are no touch targets and this action is not an initial down
                // so this view group continues to intercept touches.
                intercepted = true;
            }
			...
            
            //----------------------
			//----------------------
			//这个if就不走了
			//----------------------
			//---------------------- 
			if (!canceled && !intercepted) {
    
    
			...
            // Dispatch to touch targets.
            //----------------------
			//----------------------
			//因为mFirstTouchTarget 为null,所以进入此if,然后就和DOWN事件拦截时的处理是一样的了
			//----------------------
			//---------------------- 
            if (mFirstTouchTarget == null) {
    
    
                // No touch targets so treat this as an ordinary view.
                handled = dispatchTransformedTouchEvent(ev, canceled, null,
                        TouchTarget.ALL_POINTER_IDS);
            } else {
    
    
              ...

所以这也解释了为什么前面说

这里要纠正我们之前一个不太确切的观点:如果连DOWN事件都拿不到,那么MOVE和UP事件也拿不到。其实这是针对叶子节点的,也就是比如上面例图中的高空清洁,但是如果它是总监的话,就不同了。它没有处理DOWN事件,也可能处理MOVE事件。

看这里的父容器,它没有接收到DOWN事件,但是抢到了MOVE事件。

这里也解释了一个现象,就是我们平时使用手机的时候,上下滑动的同时进行左右滑动是可以的,而左右滑动的同时进行上下滑动是不行的。因为上下滑动可能是ListView(不纠结是RecyclerView还是ListView,道理是一样的),是子View,它可以掌握着事件的权力,而左右滑动是ViewPager,它响应事件,是上下滑动即ListView给的权力,是ListView不要这个事件之后,它才响应的。

外部拦截法:父容器处理事件冲突

这个就很简单了
在这里插入图片描述
里面的全部注释掉,换成
在这里插入图片描述
这个很简单,当MOVE事件的时候,如果是水平滑动,就拦截,即返回true。如果是竖直滑动,就不拦截。

猜你喜欢

转载自blog.csdn.net/afdafvdaa/article/details/114823500