Android事件分發(fā)機(jī)制學(xué)習(xí)筆記(ViewGroup篇)

本文是學(xué)習(xí)Android事件分發(fā)機(jī)制的學(xué)習(xí)筆記,一是為了鞏固學(xué)習(xí)成果,加深印象;二是為了方便以后查閱液荸。


Activity對(duì)事件的分發(fā)過(guò)程

Activity#dispatchTouchEvent()開(kāi)始看起:

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

首先對(duì)ACTION_DOWN 事件進(jìn)行了特殊判斷,調(diào)用onUserInteraction() ,跟進(jìn)這個(gè)方法脱篙,會(huì)發(fā)現(xiàn)是一個(gè)空方法:

public void onUserInteraction() {
}

不去管它娇钱,接下來(lái)Activity會(huì)通過(guò)getWindow() 獲得自己所屬的Window 進(jìn)行分發(fā),Window 是個(gè)抽象類(lèi)绊困,用來(lái)控制頂級(jí)View的外觀和行為策略文搂,它的唯一實(shí)現(xiàn)類(lèi)是PhoneWindow 。那么PhoneWindow 是如何處理點(diǎn)擊事件的秤朗,PhoneWindow#superDispatchTouchEvent() 如下所示:

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

很簡(jiǎn)單煤蹭,直接傳遞給了mDecor ,這個(gè)mDecor 就是當(dāng)前窗口最頂層的DecorView

// This is the top-level view of the window, containing the window decor.
private DecorView mDecor;

跟進(jìn)DecorView#superDispatchTouchEvent() :

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

居然是調(diào)用父類(lèi)的dispatchTouchEvent() 方法硝皂,DecorView 的父類(lèi)是FrameLayout ,繼續(xù)跟進(jìn)查看常挚,發(fā)現(xiàn)FrameLayout 并沒(méi)有這個(gè)方法,那就繼續(xù)向上追稽物,FrameLayout 的父類(lèi)是ViewGroup 奄毡,也就是說(shuō),觸摸事件經(jīng)過(guò)層層傳遞贝或,最終傳遞到ViewGroup#dispatchTouchEvent() 方法,至此吼过,事件已經(jīng)傳遞到視圖的頂級(jí)View了。


ViewGroup對(duì)事件的分發(fā)過(guò)程

接下來(lái)是重頭戲了...上代碼ViewGroup#dispatchTouchEvent() ...

public boolean dispatchTouchEvent(MotionEvent ev) {
    // 調(diào)試用咪奖,不去管它
    if (mInputEventConsistencyVerifier != null) {
        mInputEventConsistencyVerifier.onTouchEvent(ev, 1);
    }

    // 輔助功能盗忱,有些用戶(hù)由于視力上、身體上羊赵、年齡上使他們不能接受語(yǔ)音或者視覺(jué)信息
    // 不是重點(diǎn)趟佃,也不去管它
    if (ev.isTargetAccessibilityFocus() && isAccessibilityFocusedViewOrHost()) {
        ev.setTargetAccessibilityFocus(false);
    }

    boolean handled = false;
    // onFilterTouchEventForSecurity(ev),觸摸事件安全過(guò)濾
    // 具體實(shí)現(xiàn):當(dāng)窗口被遮擋慷垮,返回false,丟棄觸摸事件揍堕;未被遮擋料身,返回true    
    if (onFilterTouchEventForSecurity(ev)) {
        // 沒(méi)有被遮擋
        final int action = ev.getAction();
        final int actionMasked = action & MotionEvent.ACTION_MASK;

        // Handle an initial down.
        if (actionMasked == MotionEvent.ACTION_DOWN) {
            // 如果是Down事件,則重置所有之前保存的狀態(tài)衩茸,因?yàn)檫@是事件序列的開(kāi)始
            // mFirstTouchTarget會(huì)被設(shè)為Null
            cancelAndClearTouchTargets(ev);
            // 重置FLAG_DISALLOW_INTERCEPT
            resetTouchState();
        }

        // 檢測(cè)是否攔截
        final boolean intercepted;
        if (actionMasked == MotionEvent.ACTION_DOWN
                || mFirstTouchTarget != null) {
            // 標(biāo)記事件不允許被攔截芹血,默認(rèn)為false
            // 可以由requestDisallowInterceptTouchEvent方法來(lái)設(shè)置
            // 設(shè)置為true,ViewGroup將無(wú)法攔截Down以外的點(diǎn)擊事件
            final boolean disallowIntercept = (mGroupFlags & FLAG_DISALLOW_INTERCEPT) != 0;
            if (!disallowIntercept) {
                // 調(diào)用onInterceptTouchEvent(ev)方法楞慈,詢(xún)問(wèn)自己是否要攔截事件
                // ViewGroup的onInterceptTouchEvent(ev)方法默認(rèn)返回false
                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);
            }

            // 通過(guò)標(biāo)記和Action檢查Cancel幔烛,將結(jié)果賦值給局部變量canceled
            final boolean canceled = resetCancelNextUpFlag(this)
                    || actionMasked == MotionEvent.ACTION_CANCEL;

            // split標(biāo)記是否需要將事件分發(fā)給多個(gè)子View,默認(rèn)為true
            // 可通過(guò)setMotionEventSplittingEnabled()方法設(shè)置
            final boolean split = (mGroupFlags & FLAG_SPLIT_MOTION_EVENTS) != 0;
            TouchTarget newTouchTarget = null;
            boolean alreadyDispatchedToNewTouchTarget = false;
          
            // 如果沒(méi)取消也沒(méi)攔截囊蓝,進(jìn)入執(zhí)行語(yǔ)句中
            if (!canceled && !intercepted) {
              
            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;
                // 判斷newTouchTarget為Null饿悬,且ChildrenCount不為0
                if (newTouchTarget == null && childrenCount != 0) {
                    final float x = ev.getX(actionIndex);
                    final float y = ev.getY(actionIndex);
                    // 尋找可以處理觸摸事件的子View
                    // 通過(guò)buildTouchDispatchChildList()方法構(gòu)建子View的List集合preorderedList
                    final ArrayList<View> preorderedList = buildTouchDispatchChildList();
                    final boolean customOrder = preorderedList == null
                            && isChildrenDrawingOrderEnabled();
                    final View[] children = mChildren;
                    // 倒序遍歷所有的子View
                    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.
                        if (childWithAccessibilityFocus != null) {
                            if (childWithAccessibilityFocus != child) {
                                continue;
                            }
                            childWithAccessibilityFocus = null;
                            i = childrenCount - 1;
                        }

                        // 同時(shí)滿(mǎn)足兩種情況下子View可以接收事件的分發(fā)
                        // canViewReceivePointerEvents()方法會(huì)判斷子View是否可見(jiàn)和是否在播放動(dòng)畫(huà)
                        // isTransformedTouchPointInView()方法會(huì)判斷觸摸事件坐標(biāo)是否在子View內(nèi)
                        if (!canViewReceivePointerEvents(child)
                                || !isTransformedTouchPointInView(x, y, child, null)) {
                            ev.setTargetAccessibilityFocus(false);
                            continue;
                        }

                        // 查找當(dāng)前子View是否在mFirstTouchTarget中存儲(chǔ)
                        // mFirstTouchTarget是一種單鏈表結(jié)構(gòu)
                        // 找不到則返回Null
                        newTouchTarget = getTouchTarget(child);
                        if (newTouchTarget != null) {
                            // newTouchTarget不為Nul,說(shuō)明已經(jīng)找到接收的View了聚霜,break跳出for循環(huán)
                            newTouchTarget.pointerIdBits |= idBitsToAssign;
                            break;
                        }
                      
                        resetCancelNextUpFlag(child);
                      
                        // 沒(méi)有跳出循環(huán)狡恬,說(shuō)明我們找到的Child并沒(méi)有在mFirstTouchTarget中
                        // 調(diào)用dispatchTransformedTouchEvent()方法
                        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();
                            // 將child賦值給mFirstTouchTarget
                            newTouchTarget = addTouchTarget(child, idBitsToAssign);
                            // alreadyDispatchedToNewTouchTarget賦值為true,跳出循環(huán)
                            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();
                }

                // 沒(méi)有找到可以接收事件的子View蝎宇,并且之前的mFirstTouchTarget不為空
                // newTouchTarget指向了最初的mFirstTouchTarget
                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;
                }
            }
        }

        if (mFirstTouchTarget == null) {
            // 如果mFirstTouchTarget為null
            // 調(diào)用dispatchTransformedTouchEvent()方法
            // 第三個(gè)參數(shù)為null弟劲,會(huì)調(diào)用super.dispatchTouchEvent()方法
            // 將當(dāng)前ViewGroup當(dāng)做普通的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;
            }
        }

        // 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);
            // 當(dāng)某個(gè)手指抬起時(shí),清除與它相關(guān)的數(shù)據(jù)
            removePointersFromTouchTargets(idBitsToRemove);
        }
    }

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

事件攔截

intercepted 用來(lái)標(biāo)記ViewGroup 是否攔截事件姥芥,當(dāng)事件為MotionEvent.ACTION_DOWN 或者mFirstTouchTarget!=null 時(shí)兔乞,if 判斷成立,然后判斷disallowIntercept 標(biāo)志位,當(dāng)disallowInterceptfalse時(shí)庸追,調(diào)用onInterceptTouchEvent() 方法霍骄,并將返回值賦值給intercepted ,否則當(dāng)disallowIntercepttrue時(shí)锚国,則直接將intercepted 賦值為false 腕巡。

disallowIntercept 標(biāo)記位可以通過(guò)公共方法 requestDisallowInterceptTouchEvent() 設(shè)置,通常由子View調(diào)用血筑,當(dāng)設(shè)置為true 后绘沉,ViewGroup 將無(wú)法攔截除ACTION_DOWN 之外的點(diǎn)擊事件,原因是當(dāng)事件為ACTION_DOWN 時(shí)豺总,ViewGroup 會(huì)重置disallowIntercept 標(biāo)記位车伞,并且將mFirstTouchTarget 設(shè)置為null,因此喻喳,當(dāng)事件為ACTION_DOWN 時(shí)另玖,ViewGroup 總是會(huì)調(diào)用自己的onInterceptTouchEvent()方法。

        if (actionMasked == MotionEvent.ACTION_DOWN) {
            // 如果是Down事件表伦,則重置所有之前保存的狀態(tài)谦去,因?yàn)檫@是事件序列的開(kāi)始
            // mFirstTouchTarget會(huì)被設(shè)為Null
            cancelAndClearTouchTargets(ev);
            // 重置FLAG_DISALLOW_INTERCEPT
            resetTouchState();
        }

        // 檢測(cè)是否攔截
        final boolean intercepted;
        if (actionMasked == MotionEvent.ACTION_DOWN
                || mFirstTouchTarget != null) {
            // 標(biāo)記事件不允許被攔截,默認(rèn)為false
            // 可以由requestDisallowInterceptTouchEvent方法來(lái)設(shè)置
            // 設(shè)置為true蹦哼,ViewGroup將無(wú)法攔截Down以外的點(diǎn)擊事件
            final boolean disallowIntercept = (mGroupFlags & FLAG_DISALLOW_INTERCEPT) != 0;
            if (!disallowIntercept) {
                // 調(diào)用onInterceptTouchEvent(ev)方法鳄哭,詢(xún)問(wèn)自己是否要攔截事件
                // ViewGroup的onInterceptTouchEvent(ev)方法默認(rèn)返回false
                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;
        }

事件分發(fā)

中間經(jīng)過(guò)標(biāo)記和action檢查cancel,將結(jié)果賦值給變量canceled纲熏。if (!canceled && !intercepted) 語(yǔ)句表明妆丘,事件未被取消且interceptedfalse(未攔截),則會(huì)進(jìn)入執(zhí)行語(yǔ)句中局劲。

首先判斷childrenCount不為0勺拣,然后通過(guò)buildTouchDispatchChildList()方法拿到子元素的List集合,接著倒序遍歷所有子元素鱼填,尋找可以接收點(diǎn)擊事件的子元素药有,為什么要倒序遍歷,是因?yàn)?code>buildTouchDispatchChildList()內(nèi)部會(huì)調(diào)用buildOrderedChildList()方法,該方法會(huì)將子元素根據(jù)Z軸排序苹丸,在同一Z平面上的子元素則會(huì)根據(jù)繪制的先后順序排序塑猖,觸摸的時(shí)候我們當(dāng)然會(huì)希望浮在最上層的元素最先響應(yīng)事件。

對(duì)于每一個(gè)子元素來(lái)說(shuō)谈跛,需要canViewReceivePointerEvents()isTransformedTouchPointInView()均返回true羊苟,才說(shuō)明該子元素可以處理觸摸事件,否則直接continue進(jìn)行下一次循環(huán)感憾。

canViewReceivePointerEvents()通過(guò)是否可見(jiàn)及是否在播放動(dòng)畫(huà)來(lái)判斷子元素是否可以接收事件蜡励,isTransformedTouchPointInView()判斷觸摸事件的坐標(biāo)點(diǎn)是否在子元素內(nèi)令花,這樣我們就獲得了可以處理觸摸事件的子元素。

接下來(lái)通過(guò)getTouchTarget()方法判斷當(dāng)前子元素是否已經(jīng)賦值給mFirstTouchTarget凉倚,如果newTouchTarget不為null兼都,說(shuō)明子元素已經(jīng)在mFirstTouchTarget中,執(zhí)行break跳出循環(huán)稽寒。

如果newTouchTargetnull,說(shuō)明子元素并沒(méi)有在mFirstTouchTarget中保存扮碧,此時(shí)調(diào)用dispatchTransformedTouchEvent()方法,該方法十分重要杏糙,在該方法內(nèi)部:如果子元素是ViewGroup并且事件沒(méi)有被攔截慎王,那么遞歸調(diào)用ViewGroupdispatchTouchEvent();如果子元素是View,那么調(diào)用ViewdispatchTouchEvent() 宏侍,最終會(huì)調(diào)用ViewonTouchEvent()赖淤。

dispatchTransformedTouchEvent() 方法是有返回值的,如果返回true谅河,說(shuō)明子元素消耗了觸摸事件咱旱,則在下面的代碼中將子元素賦值給mFirstTouchEvent ,并跳出循環(huán)绷耍,mFirstTouchTarget是否被賦值吐限,將直接影響到ViewGroup對(duì)事件的攔截策略,如果mFirstTouchTargetnull褂始,那么ViewGroup就會(huì)默認(rèn)攔截接下來(lái)同一序列中的所有觸摸事件诸典,這一點(diǎn)在后面分析;如果返回falseViewGroup就會(huì)把事件分發(fā)給下一個(gè)子元素(如果還有下一個(gè)子元素的話)病袄。

            // 如果沒(méi)取消也沒(méi)攔截搂赋,進(jìn)入方法體中
            if (!canceled && !intercepted) {
              
            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;
                // 判斷newTouchTarget為Null赘阀,且ChildrenCount不為0
                if (newTouchTarget == null && childrenCount != 0) {
                    final float x = ev.getX(actionIndex);
                    final float y = ev.getY(actionIndex);
                    // 尋找可以接受觸摸事件的子View
                    // 通過(guò)buildTouchDispatchChildList()方法構(gòu)建子View的List集合preorderedList
                    final ArrayList<View> preorderedList = buildTouchDispatchChildList();
                    final boolean customOrder = preorderedList == null
                            && isChildrenDrawingOrderEnabled();
                    final View[] children = mChildren;
                    // 倒序遍歷所有的子View
                    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.
                        if (childWithAccessibilityFocus != null) {
                            if (childWithAccessibilityFocus != child) {
                                continue;
                            }
                            childWithAccessibilityFocus = null;
                            i = childrenCount - 1;
                        }

                        // 同時(shí)滿(mǎn)足兩種情況下子View可以接收事件的分發(fā)
                        // canViewReceivePointerEvents()方法會(huì)判斷子View是否可見(jiàn)和是否在播放動(dòng)畫(huà)
                        // isTransformedTouchPointInView()方法會(huì)判斷觸摸事件坐標(biāo)是否在子View內(nèi)
                        if (!canViewReceivePointerEvents(child)
                                || !isTransformedTouchPointInView(x, y, child, null)) {
                            ev.setTargetAccessibilityFocus(false);
                            continue;
                        }

                        // 查找當(dāng)前子View是否在mFirstTouchTarget中存儲(chǔ)
                        // 找不到則返回Null
                        newTouchTarget = getTouchTarget(child);
                        if (newTouchTarget != null) {
                            // newTouchTarget不為Nul益缠,說(shuō)明已經(jīng)找到接收的View了,break跳出for循環(huán)
                            newTouchTarget.pointerIdBits |= idBitsToAssign;
                            break;
                        }
                      
                        resetCancelNextUpFlag(child);
                      
                        // 沒(méi)有跳出循環(huán)基公,說(shuō)明我們找到的Child并沒(méi)有在mFirstTouchTarget中
                        // 調(diào)用dispatchTransformedTouchEvent()方法
                        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();
                            // 將child賦值給mFirstTouchTarget
                            newTouchTarget = addTouchTarget(child, idBitsToAssign);
                            // alreadyDispatchedToNewTouchTarget賦值為true幅慌,跳出循環(huán)
                            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();
                }

當(dāng)沒(méi)有任何子元素處理觸摸事件時(shí),調(diào)用dispatchTransformedTouchEvent() 方法轰豆,注意此時(shí)第三個(gè)參數(shù)傳入null胰伍,在方法內(nèi)部就會(huì)調(diào)用super.dispatchTouchEvent() ,也就是View類(lèi)的dispatchTouchEvent()

if (mFirstTouchTarget == null) {
            // 如果mFirstTouchTarget為null
            // 調(diào)用dispatchTransformedTouchEvent()方法
            // 第三個(gè)參數(shù)為null酸休,會(huì)調(diào)用super.dispatchTouchEvent()方法
            // 將當(dāng)前ViewGroup當(dāng)做普通的View處理
            handled = dispatchTransformedTouchEvent(ev, canceled, null,
                    TouchTarget.ALL_POINTER_IDS);

onInterceptTouchEvent() 方法

if語(yǔ)句判斷觸摸事件來(lái)源是否為鼠標(biāo)或其他指針式設(shè)備骂租,其他情況下默認(rèn)返回false

public boolean onInterceptTouchEvent(MotionEvent ev) {
    if (ev.isFromSource(InputDevice.SOURCE_MOUSE)
            && ev.getAction() == MotionEvent.ACTION_DOWN
            && ev.isButtonPressed(MotionEvent.BUTTON_PRIMARY)
            && isOnScrollbarThumb(ev.getX(), ev.getY())) {
        return true;
    }
    return false;
}

dispatchTransformedTouchEvent() 方法

dispatchTransformedTouchEvent() 源碼中發(fā)現(xiàn)多次對(duì)于傳入的child是否為null做判斷斑司,并且都做類(lèi)似的操作:

當(dāng)child==null時(shí)渗饮,調(diào)用super.dispatchTouchEvent(),也就是View類(lèi)的dispatchTouchEvent()方法,因?yàn)?code>ViewGroup的父類(lèi)是View互站,最終會(huì)調(diào)用ViewonTouchEvent()方法私蕾。

當(dāng)child!=null時(shí),遞歸調(diào)用child.dispatchTouchEvent()胡桃,此時(shí)child可能是View踩叭,也可能是ViewGroup

從源碼中可以看出dispatchTransformedTouchEvent() 方法的返回值翠胰,最終取決于onTouchEvent()方法容贝,也就是說(shuō),onTouchEvent()是否消費(fèi)了事件亡容,決定了dispatchTransformedTouchEvent() 方法的返回值嗤疯,從而決定mFirstTouchTarget是否為null 。因?yàn)槿绻?code>dispatchTransformedTouchEvent() 方法的返回值為false闺兢,就無(wú)法執(zhí)行addTouchTarget()方法茂缚,而mFirstTouchTarget就是在該方法中被賦值的。

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

重要結(jié)論

  1. ViewGroup一旦攔截ACTION_DOWN事件屋谭,那么當(dāng)ACTION_MOVE脚囊、ACTION_UP事件到來(lái)時(shí),將不再調(diào)用ViewGrouponInterceptTouchEvent()方法桐磁,并且同一序列中的其他事件都會(huì)默認(rèn)交給它處理悔耘。

    分析:ViewGroup攔截ACTION_DOWN事件,會(huì)導(dǎo)致interceptedtrue我擂,從而導(dǎo)致if (!canceled && !intercepted)判斷不成立衬以,跳過(guò)執(zhí)行語(yǔ)句,mFirstTouchTarget也為null校摩。那么當(dāng)ACTION_MOVE看峻、ACTION_UP事件到來(lái)時(shí),if (actionMasked == MotionEvent.ACTION_DOWN || mFirstTouchTarget != null)判斷語(yǔ)句不成立衙吩,會(huì)直接將intercepted賦值為true互妓,即默認(rèn)攔截后續(xù)的所有事件。

  2. 某個(gè)View一旦開(kāi)始處理事件坤塞,如果它不消費(fèi)ACTION_DOWN事件冯勉,那么同一事件序列中的其他事件也不會(huì)再交給它來(lái)處理,并且事件將重新交給它的父容器去處理摹芙。

    分析:某個(gè)View不消費(fèi)ACTION_DOWN事件灼狰,即dispatchTransformedTouchEvent()方法返回了false识埋,導(dǎo)致if (dispatchTransformedTouchEvent(ev, false, child, idBitsToAssign))判斷語(yǔ)句不成立族操,跳過(guò)執(zhí)行語(yǔ)句,同樣導(dǎo)致mFirstTouchTargetnull 荷憋,那么和第一條結(jié)論的分析一樣,當(dāng)ACTION_MOVE承绸、ACTION_UP事件到來(lái)時(shí)裸影,if (actionMasked == MotionEvent.ACTION_DOWN || mFirstTouchTarget != null)判斷語(yǔ)句不成立,同樣會(huì)直接將intercepted賦值為true 军熏,所以后續(xù)的事件都無(wú)法傳遞到這個(gè)View轩猩,而是交給ViewGroup處理。

  3. ViewGroup(絕大多數(shù)情況下)默認(rèn)不攔截任何事件荡澎。Android源碼中ViewGrouponInterceptTouchEvent()方法默認(rèn)返回false均践。

  4. ViewGroup沒(méi)有重寫(xiě)父類(lèi)ViewonTouchEvent()方法。


  • 到這里就分析完了摩幔,查了很多資料彤委,中間也有可能有理解錯(cuò)誤的地方,如果哪里錯(cuò)了或衡,還請(qǐng)大家指正焦影,謝謝。
  • Github
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末封断,一起剝皮案震驚了整個(gè)濱河市斯辰,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌坡疼,老刑警劉巖彬呻,帶你破解...
    沈念sama閱讀 219,039評(píng)論 6 508
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異柄瑰,居然都是意外死亡闸氮,警方通過(guò)查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,426評(píng)論 3 395
  • 文/潘曉璐 我一進(jìn)店門(mén)教沾,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)蒲跨,“玉大人,你說(shuō)我怎么就攤上這事详囤〔乒牵” “怎么了镐作?”我有些...
    開(kāi)封第一講書(shū)人閱讀 165,417評(píng)論 0 356
  • 文/不壞的土叔 我叫張陵藏姐,是天一觀的道長(zhǎng)。 經(jīng)常有香客問(wèn)我该贾,道長(zhǎng)羔杨,這世上最難降的妖魔是什么? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 58,868評(píng)論 1 295
  • 正文 為了忘掉前任杨蛋,我火速辦了婚禮兜材,結(jié)果婚禮上理澎,老公的妹妹穿的比我還像新娘。我一直安慰自己曙寡,他們只是感情好糠爬,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,892評(píng)論 6 392
  • 文/花漫 我一把揭開(kāi)白布。 她就那樣靜靜地躺著举庶,像睡著了一般执隧。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上户侥,一...
    開(kāi)封第一講書(shū)人閱讀 51,692評(píng)論 1 305
  • 那天镀琉,我揣著相機(jī)與錄音,去河邊找鬼蕊唐。 笑死屋摔,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的替梨。 我是一名探鬼主播钓试,決...
    沈念sama閱讀 40,416評(píng)論 3 419
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼副瀑!你這毒婦竟也來(lái)了亚侠?” 一聲冷哼從身側(cè)響起,我...
    開(kāi)封第一講書(shū)人閱讀 39,326評(píng)論 0 276
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤俗扇,失蹤者是張志新(化名)和其女友劉穎硝烂,沒(méi)想到半個(gè)月后,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體铜幽,經(jīng)...
    沈念sama閱讀 45,782評(píng)論 1 316
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡滞谢,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,957評(píng)論 3 337
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了除抛。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片狮杨。...
    茶點(diǎn)故事閱讀 40,102評(píng)論 1 350
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖到忽,靈堂內(nèi)的尸體忽然破棺而出橄教,到底是詐尸還是另有隱情,我是刑警寧澤喘漏,帶...
    沈念sama閱讀 35,790評(píng)論 5 346
  • 正文 年R本政府宣布护蝶,位于F島的核電站,受9級(jí)特大地震影響翩迈,放射性物質(zhì)發(fā)生泄漏持灰。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,442評(píng)論 3 331
  • 文/蒙蒙 一负饲、第九天 我趴在偏房一處隱蔽的房頂上張望堤魁。 院中可真熱鬧喂链,春花似錦、人聲如沸妥泉。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 31,996評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)盲链。三九已至赏表,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間匈仗,已是汗流浹背瓢剿。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 33,113評(píng)論 1 272
  • 我被黑心中介騙來(lái)泰國(guó)打工, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留悠轩,地道東北人间狂。 一個(gè)月前我還...
    沈念sama閱讀 48,332評(píng)論 3 373
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像火架,于是被迫代替她去往敵國(guó)和親鉴象。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,044評(píng)論 2 355

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