Android View 事件分发机制 源码解析

标签: Android 源码解析 View


关于View的事件分发机制,网上有不少好文。基本上的模式都是:总体概括->源码分析(Demo实验)->得出结论,由于每个人的切入角度不同,看得越多反而觉得脑子越晕(二手知识)。因此,本篇博客会直接从结论入手,带着疑问再去源码种寻找答案,尽量做到把二手知识还原成一手知识。不当、不足之处欢迎指出和讨论~

*注:源码基于api-24

1.结论

结论出处来源于主席的《Android开发艺术探索》,在原书中作者是按照整个事件的分发流程来讲述的,这里不再拾人牙慧。我们换一个切入点,直接从结论入手在源码中寻找答案。

1.1《Android开发艺术探索》

1).同一个事件序列是指手机接触屏幕那一刻起,到离开屏幕那一刻结束,有一个down事件,若干个move事件,一个up事件构成。
我们直接先看在事件分发机制中最重要的几个方法的签名:

public boolean dispatchTouchEvent(MotionEvent event)        
public boolean onInterceptTouchEvent(MotionEvent ev)      
public boolean onTouchEvent(MotionEvent event)

不论是哪个方法,无一例外都对于 MotionEvent 而言的。即任意一个事件都可能会独立的走完以上三个步骤。另外个人认为 ACTION_CANCEL 这个事件也是应该被算在一个【事件序列】中的。理由如下:

  1. 在 onTouchEvent 中处理了 ACTION_CANCEL 这个事件,主要是对View状态和标记位的重置:
 public boolean onTouchEvent(MotionEvent event) {
    \\...
    switch(action){
        case MotionEvent.ACTION_UP:
            \\...
            break;
        case MotionEvent.ACTION_DOWN:
            \\...
            break;
        case MotionEvent.ACTION_CANCEL:
              setPressed(false);
              removeTapCallback();
              removeLongPressCallback();
              mInContextButtonPress = false;
              mHasPerformedLongPress = false;
              mIgnoreNextUpEvent = false;
            break;
        case MotionEvent.ACTION_MOVE:
            \\...
            break;
    }
    return true;
    \\...
 }

2.从官方文档中,我也可以看出 ACTION_DOWN 也是应该被算入一个 gesture 中的,也规定了我们应该如何处理它:

The current gesture has been aborted. You will not receive any more points in it. You should treat this as an up event, but not perform any action that you normally would.

2).某个View一旦决定拦截事件,那么这个事件序列之后的事件都会由它来处理,并且不会再调用onInterceptTouchEvent。
一个 View 既然有截拦事件的能力(注意不是消费)那么它一定是一个 ViewGroup 。因为只有 ViewGroup 中有 onInterceptTouchEvent 这个方法。而其既然截拦了事件,就是说它的 onInterceptTouchEvent 方法返回了 true。OK 条件有了,顺着源码我们再去证明后面的结论。找到 onInterceptTouchEvent 方法被调用的地方:

public boolean dispatchTouchEvent(MotionEvent ev){
      // ...
      // Check for interception.
            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) {
        //...
    }
    
      // 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 {
        //...
    }
}

ACTION_DOWN到来时,mFirstTouchTarget 还没有被赋值,但满足前半部分条件,进入 if 分支 ——> 默认没有设置 FLAG_DISALLOW_INTERCEPT 这个标记位,disallowIntercept = false ,再进入下一个 if 分支 ——> 变量 intercepted 被赋值为 onInterceptTouchEvent 的返回值,即为 true。再往下看,最长的那个 if 分支我们进不去了(突然轻松了好多~)。mFirstTouchTarget 为空 ——> 调用 dispatchTransformedTouchEvent 这个方法:

private boolean dispatchTransformedTouchEvent(MotionEvent event, boolean cancel,
            View child, int desiredPointerIdBits) {
 // Perform any necessary transformations and dispatch.
        if (child == null) {
            handled = super.dispatchTouchEvent(transformedEvent);
        } else {
            final float offsetX = mScrollX - child.mLeft;
            final float offsetY = mScrollY - child.mTop;
            transformedEvent.offsetLocation(offsetX, offsetY);
            if (! child.hasIdentityMatrix()) {
                transformedEvent.transform(child.getInverseMatrix());
            }

            handled = child.dispatchTouchEvent(transformedEvent);
        }
}

由于 child 为 null ,直接调用了其父类,即 View 的 dispatchTouchEvent 方法。到这里,后面的代码我们也暂时不需要仔细去分析了。由于 mFirstTouchTarget 在整个 ACTION_DOWN 的流程中都没有被赋值,在之后的事件序列中,变量 intercepted 依然为 true,依然调用其父类 View 的 dispatchTouchEvent 方法,最终都会交由其 onTouchEvent 方法处理,结论 2) 得证。

3)正常情况下,一个事件序列只能被一个View拦截并消耗。这个原因可以参考第2条,因为一旦拦截了某个事件,那么这个事件序列里的其他事件都会交给这个View来处理,所以同一事件序列中的事件不能分别由两个View同时处理,但是我们可以通过特殊手段做到,比如一个View将本该自己处理的事件通过onTouchEvent强行传递给其他View处理。
这条结论的前半句已经分析过了,我们直接来看看后半句的两个结论:
a)同一事件序列中的事件不能分别由两个View同时处理:

 if(!canceled &&!intercepted){
        //...
        final View[] children = mChildren;
        for (int i = childrenCount - 1; i >= 0; i--) {
            final int childIndex = customOrder
                    ? getChildDrawingOrder(childrenCount, i) : i;
            final View child = (preorderedList == null)
                    ? children[childIndex] : preorderedList.get(childIndex);

            // If there is a view that has accessibility focus we want it
            // to get the event first and if not handled we will perform a
            // normal dispatch. We may do a double iteration but this is
            // safer given the timeframe.
            if (childWithAccessibilityFocus != null) {
                if (childWithAccessibilityFocus != child) {
                    continue;
                }
                childWithAccessibilityFocus = null;
                i = childrenCount - 1;
            }

            if (!canViewReceivePointerEvents(child)
                    || !isTransformedTouchPointInView(x, y, child, null)) {
                ev.setTargetAccessibilityFocus(false);
                continue;
            }

            newTouchTarget = getTouchTarget(child);
            if (newTouchTarget != null) {
                // Child is already receiving touch within its bounds.
                // Give it the new pointer in addition to the ones it is handling.
                newTouchTarget.pointerIdBits |= idBitsToAssign;
                break;
            }

            resetCancelNextUpFlag(child);
            if (dispatchTransformedTouchEvent(ev, false, child, idBitsToAssign)) {
                // 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;
                }
                mLastTouchDownX = ev.getX();
                mLastTouchDownY = ev.getY();
                newTouchTarget = addTouchTarget(child, idBitsToAssign);
                alreadyDispatchedToNewTouchTarget = true;
                break;
            }

            // The accessibility focus didn't handle the event, so clear
            // the flag and do a normal dispatch to all children.
            ev.setTargetAccessibilityFocus(false);
        }
        if (preorderedList != null) preorderedList.clear();
    }
    //...
}

回到 Viewgroup 中最长的那个 if 分支(该来的还是回来的)。找到
调用 dispatchTransformedTouchEvent 方法的那个 if 分支:

 if (dispatchTransformedTouchEvent(ev, false, child, idBitsToAssign)){
 //...
 break;
 }

关于 dispatchTransformedTouchEvent 这个上面已经提到过,这里传入的 child 不为空,所以直接调用 child 的 dispatchTouchEvent 方法,该方法又会去调用 child 的 onTouchEvent 方法。根据该结论给出的条件,可知第一个子 View 处理了该事件,即 onTouchEvent 方法返回 true。最终 dispatchTransformedTouchEvent 方法也返回 true 进入到该 if 分支。直接看到这个 if 分支的最后:一个 break 语句。这样我们就直接跳出了最外层遍历子 View 的 for 循环。这个事件也就传递不到其它的子 View 了。结论得证。

b)可以通过特殊手段做到,比如一个View将本该自己处理的事件通过onTouchEvent强行传递给其他View处理。
上面我们证明了:在正常情况下,一个事件是不能同时由两个 View 处理的,那么如果要让两个 View 都去处理同一个事件呢?先不去思考结论中给出的方法,思考一下造成两个 View 不能同时处理某个事件的根本原因。这主要是因为在遍历子 View 的时候 dispatchTransformedTouchEvent 这个方法返回 true ,进入到 if 分支,最后 break 跳出了整个遍历。那么很简单,我们只要让 dispatchTransformedTouchEvent 这个方法返回 false 就可以了。上面我们讲过在正常情况下,这个方法会调用 View 的 dispathcTouchEvent 方法,继而调用 View 的 onTouchEvent 方法。那么我们只需要让 onTouchEvent 这个方法返回 false 即可。

 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:
                    \\...
                    break;
                case MotionEvent.ACTION_DOWN:
                    \\...
                    break;
                case MotionEvent.ACTION_CANCEL:
                    \\...
                    break;
                case MotionEvent.ACTION_MOVE:
                    \\...
                    break;
            }
        return true;
    }
    return false;
 }

根据上面的源码,一个 View 只要是可点击的,就最终会返回 true (消费该事件)。因此,如果我们想要让 View 自己去处理该事件同时还返回 false 的话就,重写 View 的 onTouchEvent 方法,在处理完事件后返回 false 即可。这样,就可以让多个 View 去处理同一事件。最后,我们再看看结论中给出的方法,其实总体上思路也是差不多的。最大的区别在于,我们给出的方法中事件还需要经过层层传递给我们指定的 View,而结论中给出的方法则是直接将事件传递给了指定的 View 更加简单粗暴。最后,结论3得证。

4)一个View如果开始处理事件,如果它不处理down事件(onTouchEvent里面返回了false),那么这个事件序列的其他事件就不会交给它来继续处理了,而是会交给它的父元素去处理。

public boolean dispatchTouchEvent(MotionEvent ev){
      // ...
      // Check for interception.
      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) {
        //...
         for (int i = childrenCount - 1; i >= 0; i--) {
            //...
             if (dispatchTransformedTouchEvent(ev, false, child, idBitsToAssign)) {
                //...
                newTouchTarget = addTouchTarget(child, idBitsToAssign);                                                                 
                //...
                break;
            }
         }
    }
    
      // 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 {
        //...
    }
}

根据上述代码,当子 View 没有去处理 ACTION_DOWN 事件时,dispatchTransformedTouchEvent 方法返回 false。没有调用 addTouchTarget 方法,这个方法又做了些什么呢?

private TouchTarget addTouchTarget(View child, int pointerIdBits) {
    TouchTarget target = TouchTarget.obtain(child, pointerIdBits);
    target.next = mFirstTouchTarget;
    mFirstTouchTarget = target;
    return target;
}

我们可以看到在这个方法中,mFirstTouchTarget 这个变量会被赋值。而由于没有进入到该 if 分支,mFirstTouchTarget 始终为 null,当下一个事件来临时 intercepted 为 true,进入到最后一个 if 分支并调用 dispatchTransformedTouchEvent 这个方法。由于传入的 child 为空, ViewGroup 又会直接去调用父类 View 中的 dispatchTouchEvent 方法,最终调用 View 的 onTouchEvent 方法。即整个事件最终就交给父 View 去处理了。结论4得证。

5)如果一个View处理了down事件,却没有处理其他事件,那么这些事件不会交给父元素处理,并且这个View还能继续受到后续的事件。而这些未处理的事件,最终会交给Activity来处理。
回到结论4)中的代码,当 View 除理了 ACTION_DOWN 时间后,由于 onTouchEvent 返回 true ,所以最终 dispatchTransformedTouchEvent 也会返回 true ,此时执行 addTouchTarget 方法,mFirstTouchTarget 也会被赋值。当其它事件来临时,会进入到最后一个 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;
                    if (alreadyDispatchedToNewTouchTarget && target == newTouchTarget) {
                        handled = true;
                    } else {
                        final boolean cancelChild = resetCancelNextUpFlag(target.child)
                                || intercepted;
                        if (dispatchTransformedTouchEvent(ev, cancelChild,
                                target.child, target.pointerIdBits)) {
                            handled = true;
                        }
                        if (cancelChild) {
                            if (predecessor == null) {
                                mFirstTouchTarget = next;
                            } else {
                                predecessor.next = next;
                            }
                            target.recycle();
                            target = next;
                            continue;
                        }
                    }
                    predecessor = target;
                    target = next;
                }
            }

    //...   
    return handled;        

可以看到在这个分支中会一步步遍历 TouchTarget 这样一个类似链表的结构,若 dispatchTransformedTouchEvent 返回 true ,则 handled 被设置为 true ,整个 ViewGroup 方法也最终会返回 handled 的值,表示事件有没有被处理。若没有处理,则会一级一级向上传递,直到 DecorView, 最后再交给 PhoneWindow :

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

而 PhoneWinow 的 superDispatchTouchEvent 是在 Activity 的 dispatchTouchEvent 方法中被调用的,若该方法返回 false 事件会最终交给 Activity 的 onTouchEvent 去处理。至此,我们也看到事件分发的起源是 Activity。结论 5) 得证。

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

6)ViewGroup 的 onInterceptToucheEvent 默认返回 false,也就是默认不拦截事件。
直接看 ViewGroup 中onInterceptTouchEvent 的默认实现:

public boolean onInterceptTouchEvent(MotionEvent ev) {
    return false;
}

至于为什么返回 false 就代表不回去截拦事件,上面的结论中以及论述过了,这里简单回顾下:
ACTION_DOWN 事件到来 ——> onInterceptTouchEvent 返回 false 所以 intercepted 为 false ——> 进入到遍历子 View 的循环 ——> 事件被分发给子 View ——> mFirstTouchTarget 被赋值 ——> 其它事件到来 ——> mFirstTouchTarget 不为空 ——> onInterceptTouchEvent 返回 false 所以 intercepted 依然为 false ——> 依然进入到到子 View 的循环 ——> 事件被分发给子 View。结论6)得证。

7)View 没有 InterceptTouchEvent 方法,如果有事件传过来,就会直接调用 onTouchEvent 方法。
cmd + F 在 View 类中查询,的确没有 InterceptTouchEvent 方法。我们再反过来想想,道理其实也是显而易见的:由于子 View 的概念是对于 ViewGroup 来讲的,是否要去截拦事件也是对于 ViewGroup 来说的。并且作为一个在 ViewTree 中位于末尾的元素来讲,View 也只需处理好传递给自己的事件即可。我们再来看看 View 的 dispatchTouchEvent 方法:

    public boolean dispatchTouchEvent(MotionEvent event) {
        //...
        if (onFilterTouchEventForSecurity(event)) {
            //noinspection SimplifiableIfStatement
            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;
            }
        }
        //...
        return result;
    }

根据前面的分析,我们可以发现不论是 View 还是 ViewGroup,事件的分发总是从 dispatchTouchEvent 这个方法开始。在 View 的 dispatchTouchEvent 方法中,默认情况下,最终会调用 View 的 onTouchEvent fa那个法。结论7)得证。

8)View的onTouchEvent方法默认都会消耗事件,也就是默认返回true,除非他是不可点击的(longClickable和clickable同时为false)。
9)View的enable属性不会影响onTouchEvent的默认返回值。就算一个View是不可见的,只要他是可点击的(clickable或者longClickable有一个为true),它的onTouchEvent默认返回值也是true。
有关这两条结论的整个这部分的代码如下:

 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:
                    \\...
                    break;
                case MotionEvent.ACTION_DOWN:
                    \\...
                    break;
                case MotionEvent.ACTION_CANCEL:
                    \\...
                    break;
                case MotionEvent.ACTION_MOVE:
                    \\...
                    break;
            }
        return true;
    }
    return false;
 }

可以看到,只要进入了 if 语句,整个 onTouchEvent 方法的返回值就一定是 true ,也就代表消耗了改事件。而要进入该 if 语句的条件则是 View 的 clickable 或 longClickable 以及 contextClickable 这三个属性其中任意一个为 true 即可。需要声明的是 contextClickable 这个属性是在 api23 中 加入的,而作者的结论是基于 api21 的。所以我们这里的结论会更作者的结论有所出入。关于 contextClickable 这个属性根据网上的资料来看可能跟带有触笔或带有鼠标功能的设备有关。结论8)和结论9)得证。

10)onClick 方法会执行的前提是当前 View 是可点击的,并且它收到了 down 和 up 事件。
我们单独抽出 ACTION_DOWN 和 ACTION_UP 这两个分支的代码来看:

        case MotionEvent.ACTION_UP:
                    boolean prepressed = (mPrivateFlags & PFLAG_PREPRESSED) != 0;
                    if ((mPrivateFlags & PFLAG_PRESSED) != 0 || prepressed) {
                        // take focus if we don't have it already and we should in
                        // touch mode.
                        boolean focusTaken = false;
                        if (isFocusable() && isFocusableInTouchMode() && !isFocused()) {
                            focusTaken = requestFocus();
                        }

                        if (prepressed) {
                            // The button is being released before we actually
                            // showed it as pressed.  Make it show the pressed
                            // state now (before scheduling the click) to ensure
                            // the user sees it.
                            setPressed(true, x, y);
                       }

                        if (!mHasPerformedLongPress && !mIgnoreNextUpEvent) {
                            // This is a tap, so remove the longpress check
                            removeLongPressCallback();

                            // Only perform take click actions if we were in the pressed state
                            if (!focusTaken) {
                                // Use a Runnable and post this rather than calling
                                // performClick directly. This lets other visual state
                                // of the view update before click actions start.
                                if (mPerformClick == null) {
                                    mPerformClick = new PerformClick();
                                }
                                if (!post(mPerformClick)) {
                                    performClick();
                                }
                            }
                        }

                        if (mUnsetPressedState == null) {
                            mUnsetPressedState = new UnsetPressedState();
                        }

                        if (prepressed) {
                            postDelayed(mUnsetPressedState,
                                    ViewConfiguration.getPressedStateDuration());
                        } else if (!post(mUnsetPressedState)) {
                            // If the post failed, unpress right now
                            mUnsetPressedState.run();
                        }

                        removeTapCallback();
                    }
                    mIgnoreNextUpEvent = false;
                    break;

        case MotionEvent.ACTION_DOWN:
                    mHasPerformedLongPress = false;

                    if (performButtonActionOnTouchDown(event)) {
                        break;
                    }

                    // Walk up the hierarchy to determine if we're inside a scrolling container.
                    boolean isInScrollingContainer = isInScrollingContainer();

                    // For views inside a scrolling container, delay the pressed feedback for
                    // a short period in case this is a scroll.
                    if (isInScrollingContainer) {
                        mPrivateFlags |= PFLAG_PREPRESSED;
                        if (mPendingCheckForTap == null) {
                            mPendingCheckForTap = new CheckForTap();
                        }
                        mPendingCheckForTap.x = event.getX();
                        mPendingCheckForTap.y = event.getY();
                        postDelayed(mPendingCheckForTap, ViewConfiguration.getTapTimeout());
                    } else {
                        // Not inside a scrolling container, so show the feedback right away
                        setPressed(true, x, y);
                        checkForLongClick(0);
                    }
                    break;

关于“首先 View 能执行点击事件的前提是 View 是可点击的“这一点在上两条的论述中已经说明了。我们再来看看为什么 ACTION_DOWN 和 ACTION_UP 这两个事件都被需要。当 ACTION_DOWN 事件来临时,mHasPerformedLongPress 被赋值为 false ,同时通过调用 setPressed 方法,mPrivateFlags 也被赋值。继而在 ACTION_UP 事件来临时,进入到 if 分支 调用 performClick 方法。performClick 方法如下:

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

        sendAccessibilityEvent(AccessibilityEvent.TYPE_VIEW_CLICKED);
        return result;
    }

可见,在该方法中回调了我们所设置的监听事件,从而 View 的 onClick 方法被调用。结论10)得证。

11)事件传递过程是由外向内的,也就是事件会先传给父元素在向下传递给子元素。但是子元素可以通 requestDisallowInterceptTouchEvent 来干预父元素的分发过程,但是 down 事件除外(因为 down 事件方法里,会清除所有的标志位)。
关于“事件的传递过程是由外向内的这一点”在上面的论述中我们或多或少已经分析到了。这里我们主要关注一下 requestDisallowInterceptTouchEvent 这个方法:

 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);
        }
    }

当我们在子 View 中掉用该方法时,父 View 的 mGroupFlags 会被设置。

            // 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();
            }
            
            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;
            }

由于 mGroupFlags 已经被设置了,所以当 ACTION_MOVE 和 ACTION_UP 事件到来时,此时 disallowIntercept 为 true (当 mGroupFlags 未被设置的时候,disallowIntercept 为 false,也即默认情况下均为 false),最终 intercepted 被赋值为 false 。这样一来,本来应该有父 View 处理的事件都交由子 View 去处理了。那 ACTION_DOWN 事件呢?当 ACTION_DOWN 事件到来时,回到最开头,我们发现会调用 resetTouchState 方法:

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

在该方法中,mGroupFlags 这个标记为会被清空。从而对于 ACTION_DOWN 事件来说,无论子 View 是否调用了 requestDisallowInterceptTochEvent 这个方法设置了父 View 中 mGroupFlags 这标记位,都不会对 ACTION_DOWN 这个事件的分发造成影响。结论11)得证。

2.待续

关于 Android View 事件分发机制这一块,网上还有许许多多“有趣”的结论,本文也会陆续收集一些有趣的结论,然后试着从源码角度分析。欢迎大家和我讨论~

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 212,185评论 6 493
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,445评论 3 385
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 157,684评论 0 348
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,564评论 1 284
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,681评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,874评论 1 290
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,025评论 3 408
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,761评论 0 268
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,217评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,545评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,694评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,351评论 4 332
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,988评论 3 315
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,778评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,007评论 1 266
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,427评论 2 360
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,580评论 2 349

推荐阅读更多精彩内容