ViewGroup的事件分發(fā)

上一篇講了view的事件分發(fā),比較簡單。接下來看看稍微復雜一點的ViewGroup。
我們還是先用log看一下主要方法是如何執(zhí)行的:

ViewGroup事件分發(fā)的核心內(nèi)容主要在dispatchTouchEvent()這個方法中。點進去看一下源碼,代碼有點長,在關鍵部分注釋一下:

@Override
    public boolean dispatchTouchEvent(MotionEvent ev) {
        if (mInputEventConsistencyVerifier != null) {
            mInputEventConsistencyVerifier.onTouchEvent(ev, 1);
        }

        // If the event targets the accessibility focused view and this is it, start
        // normal event dispatch. Maybe a descendant is what will handle the click.
        if (ev.isTargetAccessibilityFocus() && isAccessibilityFocusedViewOrHost()) {
            ev.setTargetAccessibilityFocus(false);
        }

        boolean handled = false;
        //1.沒有被遮擋
        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.
                //2.清空異常及已有狀態(tài) 給所有之前選擇出的Target發(fā)送Cancel事件,
                //確保之前Target能收到完整的事件周期; 
                cancelAndClearTouchTargets(ev);
                resetTouchState();
            }

            // Check for interception.
            final boolean intercepted;
            //3.如果是ACTION_DOWN事件,或者已經(jīng)將之前的ACTION_DOWN事件分發(fā)給childview
            //注:當ViewGroup的childview成功處理時,會將mFirstTouchTarget 指向該childview
            if (actionMasked == MotionEvent.ACTION_DOWN
                    || mFirstTouchTarget != null) {
                //4.是否禁止攔截
                final boolean disallowIntercept = (mGroupFlags & FLAG_DISALLOW_INTERCEPT) != 0;
                //5.如果沒有禁止攔截
                if (!disallowIntercept) {
                    //6.判斷是否攔截,也就是onInterceptTouchEvent的返回值
                    intercepted = onInterceptTouchEvent(ev);
                    ev.setAction(action); // restore action in case it was changed
                }
                  //7.如果禁止攔截,當然就不攔截了
                   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.
                // 8.在這種情況下,actionMasked != ACTION_DOWN && mFirstTouchTarget == null
                // 第一次的down事件沒有被此ViewGroup的children處理掉(要么是它們自己不處理,要么是ViewGroup從一
                // 開始的down事件就開始攔截),則接下來的所有事件
                // 也沒它們的份,即不處理down事件的話,那表示你對后面接下來的事件也不感興趣
                //意味著上一次ACTION_DOWN 事件沒有分發(fā)下去,所以后續(xù)事件不再詢問,直接攔截
                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.
            //9.此touch事件是否cancel
            final boolean canceled = resetCancelNextUpFlag(this)
                    || actionMasked == MotionEvent.ACTION_CANCEL;

            // Update list of touch targets for pointer down, if needed.
            //10.是否拆分事件,與多點觸控有關,默認拆分
            final boolean split = (mGroupFlags & FLAG_SPLIT_MOTION_EVENTS) != 0;
            //11.找到的childview,接下來ViewGroup判斷要將此touch事件交給他處理
            TouchTarget newTouchTarget = null;
            //12.down或pointer_down事件已經(jīng)被處理
            boolean alreadyDispatchedToNewTouchTarget = false;
            //13.沒有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;

                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.
                        final ArrayList<View> preorderedList = buildTouchDispatchChildList();
                        final boolean customOrder = preorderedList == null
                                && isChildrenDrawingOrderEnabled();
                        final View[] children = mChildren;
                        //14.遍歷childview
                        for (int i = childrenCount - 1; i >= 0; i--) {
                            final int childIndex = getAndVerifyPreorderedIndex(
                                    childrenCount, i, customOrder);
                            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.
                            //15.不可見而且沒有正在執(zhí)行的動畫,跳過
                            if (childWithAccessibilityFocus != null) {
                                if (childWithAccessibilityFocus != child) {
                                    continue;
                                }
                                childWithAccessibilityFocus = null;
                                i = childrenCount - 1;
                            }
                            //16.手指不在該childview范圍中,跳過
                            if (!canViewReceivePointerEvents(child)
                                    || !isTransformedTouchPointInView(x, y, child, null)) {
                                ev.setTargetAccessibilityFocus(false);
                                continue;
                            }
                            //17.尋找childview對應的TouchTarget
                            newTouchTarget = getTouchTarget(child);
                            //18.已經(jīng)有對應的TouchTarget,比如在同一個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.
                                //19. newTouchTarget已經(jīng)有了,跳出for循環(huán)
                                newTouchTarget.pointerIdBits |= idBitsToAssign;
                                break;
                            }
                           
                            //20.復位標志位,避免上次操作帶來的影響
                            resetCancelNextUpFlag(child);
                            //21.重點:dispatchTransformedTouchEvent方法當child為空時調用自身dispatchTouchEvent
                            //否則調用child的dispatchTouchEvent方法,在這child不為空,所以將事件分發(fā)下去
                            // 需要注意的是,有可能用戶一個手指按在了child1上,另一個手指按在了child2上
                            // 這樣TouchTarget的鏈就形成了(TouchTarget內(nèi)部類似單鏈表)
                            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();
                                //22. 如果處理掉了的話,將此child添加到touch鏈的頭部
                                // 注意這個方法內(nèi)部會更新 mFirstTouchTarget
                                newTouchTarget = addTouchTarget(child, idBitsToAssign);
                                // 23.down或pointer_down事件已經(jīng)被處理了
                                alreadyDispatchedToNewTouchTarget = true;
                                //24.退出循環(huán)
                                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;
                    }
                }
            }
            // 25.非down事件直接從這里開始處理,不會走上面的一大堆尋找TouchTarget的邏輯

            // Dispatch to touch targets.
            //26.如果沒有找到合適的childview,交給自己處理,調用super.dispatchTouchEvent(event);
            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;
                //27.上面說了,用戶有可能不同的手指按在不同的childview上,所以要遍歷TouchTarget的鏈表
                while (target != null) {
                    final TouchTarget next = target.next;
                    //28.已經(jīng)處理過,不再處理
                    if (alreadyDispatchedToNewTouchTarget && target == newTouchTarget) {
                        handled = true;
                    } else {
                        //檢查是否需要攔截
                        final boolean cancelChild = resetCancelNextUpFlag(target.child)
                                || intercepted;
                        // 如果ViewGroup從半路攔截(cancelChild為true)了touch事件則給touch鏈上的child發(fā)送cancel事件
                        if (dispatchTransformedTouchEvent(ev, cancelChild,
                                target.child, target.pointerIdBits)) {
                            // TouchTarget鏈中任意一個處理了則設置handled為true
                            handled = true;
                        }
                        // 如果cancel的話,則回收此target節(jié)點
                        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.
            // 取消或up事件時resetTouchState
            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;
    }

主要的功能,上面注釋了一部分。這里總結一下:

ACTION_DOWN

首先,ACTION_DOWN 事件的任務就是找到分發(fā)對象(找到接收事件分發(fā)的childview)

1.在viewgroup沒有被遮罩的情況下,先清空異常及已有狀態(tài),給所有之前選擇出的Target發(fā)送Cancel事件,確保之前Target能收到完整的事件周期; 清除已有Target,復位所有標志位(如PFLAG_CANCEL_NEXT_UP_EVENT、FLAG_DISALLOW_INTERCEPT等) 。
對應編號1-2
2.判斷是否需要攔截,某個 view 一旦開始處理事件,如果不消費 ACTION_DOWN 事件,則同一事件序列中的其他事件不會再交給它來處理,直接攔截。如果是ACTION_DOWN 事件,或者向下分發(fā)了ACTION_DOWN 事件,則調用onInterceptTouchEvent以確定當前ViewGroup是否攔截該Down事件。與此同時,還要關注禁止攔截屬性。禁止攔截由public void requestDisallowInterceptTouchEvent(boolean disallowIntercept)方法設置,當disallowIntercept為true時,禁止攔截。
對應編號3-8
3.如果事件沒有被攔截也沒有被cancel,遍歷childview尋找合適的分發(fā)對象。{如果該childview不可見而且沒有正在執(zhí)行的動畫,跳過。如果手指不在該childview范圍中,也跳過。找到合適的childview之后,判斷是否已經(jīng)有一個手指按在此childview上(對應編號18),如果是直接跳出循環(huán),因為已經(jīng)找到了合適的分發(fā)對象(即此newTouchTarget.child)。如果否,先復位該childview的狀態(tài),然后調用childview的dispatchTouchEvent方法把事件分發(fā)下去,并把該childview添加到TouchTarget 鏈表中,對mFirstTouchTarget賦值(編號3處用到,如果mFirstTouchTarget!=null,說明已經(jīng)將事件分發(fā)下去了)。然后退出循環(huán)}(花括號內(nèi)為循環(huán)體)。如果沒有合適的childview,則交給自己處理,調用super.dispatchTouchEvent(event);(編號26)
對應編號13-26 。 至此,找到了合適的childview,

非ACTION_DOWN 事件

上面我們得到了TouchTarget,其中的鏈表記錄了接收事件的childview(可能是多個,多點觸控)。所以我們先遍歷target,檢查是否被ViewGroup從半路攔截,如果是,分發(fā)cancel,否者正常的把事件分發(fā)下去。

圖片說明

最后一圖以蔽之。

最后編輯于
?著作權歸作者所有,轉載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務。

相關閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容