Android 事件分發(fā)機制源碼攻略(二) —— ViewGroup篇

ViewGroup

這一篇是續(xù)上篇Android 事件分發(fā)機制源碼攻略(一) —— Activity篇 的ViewGroup,想了解Activity篇的也可以點擊查看(本來應該是很快就發(fā)布這一篇了,結(jié)果被CSDN的不自動保存坑死了斥黑,拖了一周)倦卖。
這篇算是Android事件分發(fā)中最為關鍵的一篇弦聂,因為這里會分析哪些事件會被攔截钮惠,是以何種形式獲取子View亮曹,以及對ACTION_DOWN后續(xù)事件傳遞等問題橄杨,都會在這里得到答案。好了照卦,廢話不多說式矫,現(xiàn)在開始分析。
上一篇役耕,我們走到的ViewGroup的dispatchTouchEvent()這個方法采转。 先來說下我待會的講解思路,首先瞬痘,我們可以通過目測判斷這個方法很長故慈,對于很長的源碼,以我個人的經(jīng)驗最好是找出關鍵點框全,然后逐個擊破察绷。

如果是ACTION_DOWN事件,就會去尋找子View來處理津辩,如果找不到子View來處理拆撼,就自己處理。
如果不是ACTION_DOWN事件丹泉,就會把這個事件傳給處理了ACTION_DOWN事件的View來處理情萤。

大致就這兩個邏輯,雖說比較粗略摹恨,不過筋岛,這對于接下來看源碼就足夠了,并且源碼有比較多的注釋晒哄,基本上大致的方向是可以弄懂了睁宰。

@Override
    public boolean dispatchTouchEvent(MotionEvent ev) {
        
        ... 
        //返回值的關鍵,注意留意handled的值發(fā)生改變的地方
        boolean handled = false;
        //判斷當前window是否有被遮擋寝凌,true為分發(fā)這個事件柒傻,false為丟棄這個事件
        if (onFilterTouchEventForSecurity(ev)) {
            final int action = ev.getAction();
            final int actionMasked = action & MotionEvent.ACTION_MASK;

            // 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
                // due to an app switch, ANR, or some other state change.
//在新的事件開始(即是新的ACTION_DOWN事件),需要清除掉之前的狀態(tài)以及設置mFirstTouchTarget=null;
                cancelAndClearTouchTargets(ev);
                resetTouchState();
            }

            // Check for interception.
            final boolean intercepted;
            //子View唯一一個可以用來控制父類事件傳遞
            //只有ACTION_DOWN事件跟mFirstTouchTarget不為空的情況较木,后面的討論大多是圍繞著mFirstTouchTarget來進行的
            if (actionMasked == MotionEvent.ACTION_DOWN
                    || mFirstTouchTarget != null) {
                //是否攔截事件红符,disallowIntercept為true是不攔截,false是攔截
                final boolean disallowIntercept = (mGroupFlags & FLAG_DISALLOW_INTERCEPT) != 0;
                if (!disallowIntercept) {
                    //一般重寫onInterceptTouchEvent方法
                    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 intercepted, start normal event dispatch. Also if there is already
            // a view that is handling the gesture, do normal event dispatch.
            if (intercepted || mFirstTouchTarget != null) {
                ev.setTargetAccessibilityFocus(false);
            }

            // Check for cancelation.
            final boolean canceled = resetCancelNextUpFlag(this)
                    || actionMasked == MotionEvent.ACTION_CANCEL;

            // Update list of touch targets for pointer down, if needed.
            //split是否分發(fā)給多個子View,默認為false
            final boolean split = (mGroupFlags & FLAG_SPLIT_MOTION_EVENTS) != 0;
            TouchTarget newTouchTarget = null;
            boolean alreadyDispatchedToNewTouchTarget = false;
            //如果不被攔截即可進入或者不是ACTION_CANCEL事件
            if (!canceled && !intercepted) {

                // If the event is targeting accessiiblity focus we give it to the
                // view that has accessibility focus and if it does not handle it
                // we clear the flag and dispatch the event to all children as usual.
                // We are looking up the accessibility focused host to avoid keeping
                // state since these events are very rare.
                View childWithAccessibilityFocus = ev.isTargetAccessibilityFocus()
                        ? findChildWithAccessibilityFocus() : null;
                //只有ACTION_DOWN等事件能夠進入
                if (actionMasked == MotionEvent.ACTION_DOWN
                        || (split && actionMasked == MotionEvent.ACTION_POINTER_DOWN)
                        || actionMasked == MotionEvent.ACTION_HOVER_MOVE) {
                    final int actionIndex = ev.getActionIndex(); // always 0 for down
                    final int idBitsToAssign = split ? 1 << ev.getPointerId(actionIndex)
                            : TouchTarget.ALL_POINTER_IDS;

                    // Clean up earlier touch targets for this pointer id in case they
                    // have become out of sync.
                    removePointersFromTouchTargets(idBitsToAssign);

                    final int childrenCount = mChildrenCount;
                    if (newTouchTarget == null && childrenCount != 0) {
                        final float x = ev.getX(actionIndex);
                        final float y = ev.getY(actionIndex);
                        // Find a child that can receive the event.
                        // Scan children from front to back.
                        //獲取按Z軸從大到小排序的子View列表
                        final ArrayList<View> preorderedList = buildTouchDispatchChildList();
                        //是否有自定義順序预侯,一般為false
                        final boolean customOrder = preorderedList == null
                                && isChildrenDrawingOrderEnabled();
                        final View[] children = mChildren;
                        for (int i = childrenCount - 1; i >= 0; i--) {
                            //確認這個子View的下標
                            final int childIndex = getAndVerifyPreorderedIndex(
                                    childrenCount, i, customOrder);
                            //根據(jù)上面獲得的下標致开,確認這個子View
                            final View child = getAndVerifyPreorderedView(
                                    preorderedList, children, 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.
                            // 如果當前視圖無法獲取用戶焦點,則跳過本次循環(huán)
                            if (childWithAccessibilityFocus != null) {
                                if (childWithAccessibilityFocus != child) {
                                    continue;
                                }
                                childWithAccessibilityFocus = null;
                                i = childrenCount - 1;
                            }
                            //是否獲得可見萎馅,并且落在child的布局范圍內(nèi)
                            if (!canViewReceivePointerEvents(child)
                                    || !isTransformedTouchPointInView(x, y, child, null)) {
                                ev.setTargetAccessibilityFocus(false);
                                continue;
                            }
                            //Child是否已經(jīng)處理過事件了双戳,有的話更改pointerIdBits值,并結(jié)束查找
                            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);
                            //分發(fā)給View的dispatchTouchEvent
                            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();
                                //給mFirstTouchTarget賦值糜芳,該事件已經(jīng)被子View確認處理了
                                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();
                    }

                    if (newTouchTarget == null && mFirstTouchTarget != null) {
                        // Did not find a child to receive the event.
                        // Assign the pointer to the least recently added target.
                        newTouchTarget = mFirstTouchTarget;
                        while (newTouchTarget.next != null) {
                            newTouchTarget = newTouchTarget.next;
                        }
                        newTouchTarget.pointerIdBits |= idBitsToAssign;
                    }
                }
            }

            // Dispatch to touch targets.
            // 沒有子View處理飒货,則自己處理
            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.
                //處理除了ACTION_DOWN以外的事件
                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;
                        //如果這個事件被攔截了,intercepted為true
                        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;
                }
            }

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

像這么長的代碼塘辅,很多地方是可以跳過的,不過仔仔細細分析邪驮,特別是像Google出品的(個人愚見)莫辨,因為這些東西考慮的方方面面比較多,而我們這個只是為了了解事件的分發(fā)毅访,繪制那塊我們不會過多涉及。(說跑題了)回到正題來盘榨,像這么長的代碼喻粹,之前學習的時候,有個牛人是這么寫的(個人總結(jié))草巡。

從結(jié)果出發(fā)守呜,留意改變的結(jié)果的地方

上面的dispatchTouchEvent返回值是由handle決定,我們先來看第一處第8行代碼

 boolean handled = false;
        if (onFilterTouchEventForSecurity(ev)) {
            final int action = ev.getAction();
            final int actionMasked = action & MotionEvent.ACTION_MASK;
            // 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();
            }
        ...
        }
        return false;

這個onFilterTouchEventForSecurity方法如果返回false的話山憨,基本上里面的代碼都不用分析了查乒,直接返回false。那我們進去看看這個方法做了什么郁竟。

    /**
     * Filter the touch event to apply security policies.
     *
     * @param event The motion event to be filtered.
     * @return True if the event should be dispatched, false if the event should be dropped.
     *
     * @see #getFilterTouchesWhenObscured
     */
    public boolean onFilterTouchEventForSecurity(MotionEvent event) {
        //noinspection RedundantIfStatement
        if ((mViewFlags & FILTER_TOUCHES_WHEN_OBSCURED) != 0
                && (event.getFlags() & MotionEvent.FLAG_WINDOW_IS_OBSCURED) != 0) {
            // Window is obscured, drop this touch.
            return false;
        }
        return true;
    }

這是一個安全策略方面的過濾玛迄,我們來看下這兩個變量FILTER_TOUCHES_WHEN_OBSCURED、MotionEvent.FLAG_WINDOW_IS_OBSCURED是什么意思

    /**
     * Indicates that the view should filter touches when its window is obscured.
     * Refer to the class comments for more information about this security feature.
     * {@hide}
     */
    static final int FILTER_TOUCHES_WHEN_OBSCURED = 0x00000400;
    /**
     * This flag indicates that the window that received this motion event is partly
     * or wholly obscured by another visible window above it.  This flag is set to true
     * even if the event did not directly pass through the obscured area.
     * A security sensitive application can check this flag to identify situations in which
     * a malicious application may have covered up part of its content for the purpose
     * of misleading the user or hijacking touches.  An appropriate response might be
     * to drop the suspect touches or to take additional precautions to confirm the user's
     * actual intent.
     */
    public static final int FLAG_WINDOW_IS_OBSCURED = 0x1;

從上面的代碼注釋可以看出來棚亩,這個View不能被其他的window遮擋住蓖议,這是谷歌的一個安全策略,避免被惡意程序誤導用戶或劫持觸摸讥蟆。
第二處handle的改變是在172行

        if (mFirstTouchTarget == null) {
               // No touch targets so treat this as an ordinary view.
               handled = dispatchTransformedTouchEvent(ev, canceled, null,
                    TouchTarget.ALL_POINTER_IDS);
        } else {
        ...
        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;
                    }
                ...

很明顯handled的值又跟mFirstTouchTarget勒虾、alreadyDispatchedToNewTouchTarget這兩個值有關,另外還跟dispatchTransformedTouchEvent()這個方法有關瘸彤,dispatchTransformedTouchEvent()方法修然,我們留在后面分析,我們先來看看這兩個值是在什么時候在哪里被改變的。

         mLastTouchDownX = ev.getX();
         mLastTouchDownY = ev.getY();
         //給mFirstTouchTarget賦值愕宋,該事件已經(jīng)被子View確認處理了
         newTouchTarget = addTouchTarget(child, idBitsToAssign);
         alreadyDispatchedToNewTouchTarget = true;
   

這個是第145行的代碼玻靡,這里是找到處理事件的子View后,做的賦值掏婶,addTouchTarget這個方法里面會對
mFirstTouchTarget賦值啃奴。

好了,如果是這樣雄妥,我們再從上面的第13行開始分析最蕾。

 // 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
                // due to an app switch, ANR, or some other state change.
//在新的事件開始(即是新的ACTION_DOWN事件),需要清除掉之前的狀態(tài)以及設置mFirstTouchTarget=null;
                cancelAndClearTouchTargets(ev);
                resetTouchState();
            }

這里先對該事件進行判斷老厌,如果是ACTION_DOWN事件會進到這個方法里面瘟则,做一些處理。我們來看下這兩個方法都做了哪些枝秤。

    /**
     * Cancels and clears all touch targets.
     */
    private void cancelAndClearTouchTargets(MotionEvent event) {
        if (mFirstTouchTarget != null) {
            boolean syntheticEvent = false;
            //假如event為null,重新實例一個取消(MotionEvent)的事件
            if (event == null) {
                final long now = SystemClock.uptimeMillis();
                event = MotionEvent.obtain(now, now,
                        MotionEvent.ACTION_CANCEL, 0.0f, 0.0f, 0);
                event.setSource(InputDevice.SOURCE_TOUCHSCREEN);
                syntheticEvent = true;
            }
            
            for (TouchTarget target = mFirstTouchTarget; target != null; target = target.next) {
                resetCancelNextUpFlag(target.child);
                //分發(fā)事件
                dispatchTransformedTouchEvent(event, true, target.child, target.pointerIdBits);
            }
           //重置mFirstTouchTarget
            clearTouchTargets();

            if (syntheticEvent) {
                event.recycle();
            }
        }
    }

從這個方法的名字可以看出來醋拧,這個方法做了兩件事取消跟清除TouchTarget,首先是取消淀弹,這里的取消是指分發(fā)ACTION_CANCEL事件丹壕,在我上面注釋代碼的第18行,dispatchTransformedTouchEvent()這個方法的第二個參數(shù)為true薇溃,這個值會在更改事件為ACTION_CANCEL菌赖,并分發(fā)給上次處理事件的View。這個分發(fā)事件的方法沐序,我們留在后面分析琉用,現(xiàn)在繼續(xù)分析清除。

    /**
     * Clears all touch targets.
     */
    private void clearTouchTargets() {
        TouchTarget target = mFirstTouchTarget;
        if (target != null) {
            do {
                TouchTarget next = target.next;
                target.recycle();
                target = next;
            } while (target != null);
            mFirstTouchTarget = null;
        }
    }

這個方法很簡單了策幼,就對TouchTarget的next是回收邑时,最后再把mFirstTouchTarget置null。好了特姐,這兩個方法分析完晶丘,我們再回到剛剛的那個地方,看到還有一個方法resetTouchState()

    /**
     * Resets all touch state in preparation for a new cycle.
     */
    private void resetTouchState() {
        clearTouchTargets();
        resetCancelNextUpFlag(this);
        mGroupFlags &= ~FLAG_DISALLOW_INTERCEPT;
        mNestedScrollAxes = SCROLL_AXIS_NONE;
    }

這個方法除了clearTouchTargets()到逊、resetCancelNextUpFlag()這兩個方法外铣口,還對 mGroupFlags 這個標志做一個攔截方面的修改,這個標志可以讓子View請求父布局不要去攔截某個事件(ACTION_DOWN除外)觉壶,并且可通過getParent().requestDisallowInterceptTouchEvent()去修改這個值脑题。

  // Check for interception.
            final boolean intercepted;
            //子View唯一一個可以用來控制父類事件傳遞
            //只有ACTION_DOWN事件跟mFirstTouchTarget不為空的情況,后面的討論大多是圍繞著mFirstTouchTarget來進行的
            if (actionMasked == MotionEvent.ACTION_DOWN
                    || mFirstTouchTarget != null) {
                //是否攔截事件铜靶,disallowIntercept為true是不攔截叔遂,false是攔截
                final boolean disallowIntercept = (mGroupFlags & FLAG_DISALLOW_INTERCEPT) != 0;
                if (!disallowIntercept) {
                    //一般重寫onInterceptTouchEvent方法
                    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;
            }

這里有個判斷他炊,只有ACTION_DOWN以及mFirstTouchTarget不為的空的情況下,才允許進入已艰。我們來先說下痊末,什么時候mFirstTouchTarget會不為空,我這邊先簡單說下哩掺,后面代碼會提及凿叠;mFirstTouchTarget是在這個事件被所在的子View消費了,這個值才不會空嚼吞,即使是本身ViewGroup消費了盒件,這個值也是為空。按照這個思路的話舱禽,大家估計也不難理解我上面說的子View可以請求父布局對ACTION_DOWN以外的事件不做攔截炒刁,另外還有一點就是,一般重寫只針對onInterceptTouchEvent這個方法誊稚,而dispatchTouchEvent這個方法倒是很少重寫翔始。像我們經(jīng)常遇到的ViewPager跟ScrollView這個橫豎滑動沖突的問題,你們?nèi)タ催@兩個控件源碼里伯,就可以看到都是重寫了onInterceptTouchEvent這個方法城瞎。

我們回到我上面提供的源碼注解中,執(zhí)行上述判斷后疾瓮,如果canceled跟intercepted都為false的話全谤,并且這個事件為ACTION_DOWN事件,接下來將尋找滿足消費條件的子View爷贫。我們來看下,是按照什么順序來尋找View的补憾。

按照我上面提供源碼走下來漫萄,在87行處有著下面這個方法,這個方法主要是將子View按照Z軸的大小排序盈匾。

  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);
        }
        
        //自定義View排序
        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;
           //有點類似于插入排序腾务,按Z軸從小到大排序
            while (insertIndex > 0 && mPreSortedChildren.get(insertIndex - 1).getZ() > currentZ) {
                insertIndex--;
            }
            mPreSortedChildren.add(insertIndex, nextChild);
        }
        return mPreSortedChildren;
    }

其中g(shù)etAndVerifyPreorderedIndex只是對View的下標進行再次確定。這里面提到一個自定義排序的問題削饵,正常情況的布局排序是根據(jù)xml的順序或者addView的順序決定的岩瘦。當然google也提供了setChildrenDrawingOrderEnabled(),getChildDrawingOrder()這兩個方法進行自定義排序窿撬,有需求的可以去自行了解下启昧,我們就不深入探討了。

  if (childWithAccessibilityFocus != null) {
        if (childWithAccessibilityFocus != child) {
              continue;
        }
        childWithAccessibilityFocus = null;
        i = childrenCount - 1;
  }

現(xiàn)在是取到了所有的子View劈伴,那么接下來就是篩選哪些View可以處理了密末。首先是先獲取到哪個是獲取焦點的View,并且這個View是否在這些子View里面。如果找到了就走到下一步严里。

//判斷這個View是否具備處理的條件
if (!canViewReceivePointerEvents(child)
        || !isTransformedTouchPointInView(x, y, child, null)) {
    ev.setTargetAccessibilityFocus(false);
    continue;
}

我們來看看第一個判斷方法

  /**
     * Returns true if a child view can receive pointer events.
     * @hide
     */
    private static boolean canViewReceivePointerEvents(@NonNull View child) {
        return (child.mViewFlags & VISIBILITY_MASK) == VISIBLE
                || child.getAnimation() != null;
    }

第二個方法

/**
 * Returns true if a child view contains the specified point when transformed
 * into its coordinate space.
 * Child must not be null.
 * @hide
 */
protected boolean isTransformedTouchPointInView(float x, float y, View child,
        PointF outLocalPoint) {
    final float[] point = getTempPoint();
    point[0] = x;
    point[1] = y;
    transformPointToViewLocal(point, child);
    final boolean isInView = child.pointInView(point[0], point[1]);
    if (isInView && outLocalPoint != null) {
        outLocalPoint.set(point[0], point[1]);
    }
    return isInView;
}

可見或者是正在執(zhí)行動畫的新啼,并且位置是落在這個View的范圍的。滿足這些條件外刹碾,再判斷這個View是否已經(jīng)是在mFirstTouchTarget的子View里面了燥撞,如果是的話,也是結(jié)束循環(huán)了迷帜。

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

以上條件都滿足的話物舒,我們就進行分發(fā)事件的方法,我們來看下這個方法做了什么操作瞬矩。

/**
     * Transforms a motion event into the coordinate space of a particular child view,
     * filters out irrelevant pointer ids, and overrides its action if necessary.
     * If child is null, assumes the MotionEvent will be sent to this ViewGroup instead.
     */
    private boolean dispatchTransformedTouchEvent(MotionEvent event, boolean cancel,
            View child, int desiredPointerIdBits) {
        final boolean handled;

        // Canceling motions is a special case.  We don't need to perform any transformations
        // or filtering.  The important part is the action, not the contents.
        final int oldAction = event.getAction();
        if (cancel || oldAction == MotionEvent.ACTION_CANCEL) {
            event.setAction(MotionEvent.ACTION_CANCEL);
            if (child == null) {
                handled = super.dispatchTouchEvent(event);
            } else {
                handled = child.dispatchTouchEvent(event);
            }
            event.setAction(oldAction);
            return handled;
        }

        // Calculate the number of pointers to deliver.
        final int oldPointerIdBits = event.getPointerIdBits();
        final int newPointerIdBits = oldPointerIdBits & desiredPointerIdBits;

        // If for some reason we ended up in an inconsistent state where it looks like we
        // might produce a motion event with no pointers in it, then drop the event.
        if (newPointerIdBits == 0) {
            return false;
        }

        // If the number of pointers is the same and we don't need to perform any fancy
        // irreversible transformations, then we can reuse the motion event for this
        // dispatch as long as we are careful to revert any changes we make.
        // Otherwise we need to make a copy.
        final MotionEvent transformedEvent;
        if (newPointerIdBits == oldPointerIdBits) {
            if (child == null || child.hasIdentityMatrix()) {
                if (child == null) {
                    handled = super.dispatchTouchEvent(event);
                } else {
                    final float offsetX = mScrollX - child.mLeft;
                    final float offsetY = mScrollY - child.mTop;
                    event.offsetLocation(offsetX, offsetY);

                    handled = child.dispatchTouchEvent(event);

                    event.offsetLocation(-offsetX, -offsetY);
                }
                return handled;
            }
            transformedEvent = MotionEvent.obtain(event);
        } else {
            transformedEvent = event.split(newPointerIdBits);
        }

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

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

這個方法一看就有點長了茶鉴,慌不慌~其實這個方法就做了兩件事,第一件事景用,就是如果cancel為true的話涵叮,更改這個事件為ACTION_CANCEL;第二件事伞插,就是child為null的話割粮,調(diào)用super.dispatchTouchEvent(event);child不為空的話,就調(diào)用super.dispatchTouchEvent(event);好吧媚污,其實這個方法舀瓢,只需要看上面那部分就差不多了。

...
if (cancel || oldAction == MotionEvent.ACTION_CANCEL) {
            event.setAction(MotionEvent.ACTION_CANCEL);
            if (child == null) {
                handled = super.dispatchTouchEvent(event);
            } else {
                handled = child.dispatchTouchEvent(event);
            }
            event.setAction(oldAction);
            return handled;
        }
...

精簡版的分發(fā)~~~

如果dispatchTransformedTouchEvent方法返回true的話耗美,就代表了這個事件已經(jīng)被子View消費了京髓,接下來關鍵的方法就是調(diào)用addTouchTarget()這個方法,給mFirstTouchTarget賦值商架。

 /**
     * Adds a touch target for specified child to the beginning of the list.
     * Assumes the target child is not already present.
     */
    private TouchTarget addTouchTarget(@NonNull View child, int pointerIdBits) {
        final TouchTarget target = TouchTarget.obtain(child, pointerIdBits);
        target.next = mFirstTouchTarget;
        mFirstTouchTarget = target;
        return target;
    }

如果dispatchTransformedTouchEvent方法返回false的話堰怨,那么就代表這個事件沒有View消費,那就是只能自己消費了

 if (mFirstTouchTarget == null) {
           // No touch targets so treat this as an ordinary view.
           handled = dispatchTransformedTouchEvent(ev, canceled, null,
                   ouchTarget.ALL_POINTER_IDS);
 }

其實到這里蛇摸,整個ACTION_DOWN事件的傳遞就結(jié)束了备图。我們來做了小結(jié),當有觸摸事件傳遞過來時

1赶袄、先對當前設備狀態(tài)進行判斷揽涮,是否沒被遮擋

2、緊接著如果是ACTION_DOWN事件的話饿肺,就清除狀態(tài)

3蒋困、如果onInterceptTouchEvent返回true,則事件交給自己處理

3唬格、如果是ACTION_DOWN事件的話家破,先去尋找獲得焦點的View颜说,如果找到了,就分發(fā)給View去處理汰聋;如果找不到就交給自己處理门粪。

接著我們再來說下除了ACTION_DOWN以外的事件傳遞情況,從上面的demo我們可以得知烹困,消費了ACTION_DOWN事件玄妈,后續(xù)的事件也將給這個View消費。也即是mFirstTouchTarget != null的情況髓梅。

// Dispatch to touch targets, excluding the new touch target if we already
                // dispatched to it.  Cancel touch targets if necessary.
                //處理除了ACTION_DOWN以外的事件
                TouchTarget predecessor = null;
                TouchTarget target = mFirstTouchTarget;
                while (target != null) {
                    final TouchTarget next = target.next;
                   //alreadyDispatchedToNewTouchTarget為true的話拟蜻,說明已經(jīng)被消費了
                    if (alreadyDispatchedToNewTouchTarget && target == newTouchTarget) {
                        handled = true;
                    } else {
                        final boolean cancelChild = resetCancelNextUpFlag(target.child)
                                || intercepted;
                        //如果這個事件被攔截了,intercepted為true
                        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;
                }
            }

上面代碼的第9行酝锅,這一塊的的判斷我們可以回溯到之前的mFirstTouchTarget賦值,也即是addTouchTarget()這個方法奢方∩Ρ猓可以發(fā)現(xiàn),上述的判斷如果為true蟋字,說明這個事件已經(jīng)被消費了稿蹲,所以handled就為true了。

上面代碼的第12行鹊奖,如果intercepted為true的話苛聘,那cancelChild也就為true了。而dispatchTransformedTouchEvent()上面已經(jīng)分析過忠聚,cancelChild為true设哗,會向之前消費事件的View發(fā)送ACTION_CANCEL事件。后面再把mFirstTouchTarget置成next两蟀,也即是null熬拒,那么接下來的事件將被本身給消費掉。這也驗證了我們上面的demo垫竞。當然,大家也可以多做幾個例子好好理解理解蛀序。

下面是整個dispatchTouchEvent()里面關鍵方法的調(diào)用流程欢瞪,可以方便理解。

dispatchTouchEvent方法調(diào)用順序

好了徐裸,整個ViewGroup層dispatchTouchEvent傳遞到View層的dispatchTouchEvent或者傳遞給super.dispatchTouchEvent(event)遣鼓,下一節(jié)將對View層的源碼進行解析。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末重贺,一起剝皮案震驚了整個濱河市骑祟,隨后出現(xiàn)的幾起案子回懦,更是在濱河造成了極大的恐慌,老刑警劉巖次企,帶你破解...
    沈念sama閱讀 218,386評論 6 506
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件怯晕,死亡現(xiàn)場離奇詭異,居然都是意外死亡缸棵,警方通過查閱死者的電腦和手機舟茶,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,142評論 3 394
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來堵第,“玉大人吧凉,你說我怎么就攤上這事√ぶ荆” “怎么了阀捅?”我有些...
    開封第一講書人閱讀 164,704評論 0 353
  • 文/不壞的土叔 我叫張陵,是天一觀的道長针余。 經(jīng)常有香客問我饲鄙,道長,這世上最難降的妖魔是什么涵紊? 我笑而不...
    開封第一講書人閱讀 58,702評論 1 294
  • 正文 為了忘掉前任傍妒,我火速辦了婚禮,結(jié)果婚禮上摸柄,老公的妹妹穿的比我還像新娘颤练。我一直安慰自己,他們只是感情好驱负,可當我...
    茶點故事閱讀 67,716評論 6 392
  • 文/花漫 我一把揭開白布嗦玖。 她就那樣靜靜地躺著,像睡著了一般跃脊。 火紅的嫁衣襯著肌膚如雪宇挫。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 51,573評論 1 305
  • 那天酪术,我揣著相機與錄音器瘪,去河邊找鬼。 笑死绘雁,一個胖子當著我的面吹牛橡疼,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播庐舟,決...
    沈念sama閱讀 40,314評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼欣除,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了挪略?” 一聲冷哼從身側(cè)響起历帚,我...
    開封第一講書人閱讀 39,230評論 0 276
  • 序言:老撾萬榮一對情侶失蹤滔岳,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后挽牢,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體谱煤,經(jīng)...
    沈念sama閱讀 45,680評論 1 314
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,873評論 3 336
  • 正文 我和宋清朗相戀三年卓研,在試婚紗的時候發(fā)現(xiàn)自己被綠了趴俘。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 39,991評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡奏赘,死狀恐怖寥闪,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情磨淌,我是刑警寧澤疲憋,帶...
    沈念sama閱讀 35,706評論 5 346
  • 正文 年R本政府宣布,位于F島的核電站梁只,受9級特大地震影響缚柳,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜搪锣,卻給世界環(huán)境...
    茶點故事閱讀 41,329評論 3 330
  • 文/蒙蒙 一秋忙、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧构舟,春花似錦灰追、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,910評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至努咐,卻和暖如春苦蒿,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背渗稍。 一陣腳步聲響...
    開封第一講書人閱讀 33,038評論 1 270
  • 我被黑心中介騙來泰國打工佩迟, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人竿屹。 一個月前我還...
    沈念sama閱讀 48,158評論 3 370
  • 正文 我出身青樓音五,卻偏偏與公主長得像,于是被迫代替她去往敵國和親羔沙。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 44,941評論 2 355

推薦閱讀更多精彩內(nèi)容