ViewGroup事件傳遞原理


觸屏是用戶和手機(jī)交互的基礎(chǔ),手指觸屏?xí)r產(chǎn)生一系列事件边败,控制視圖改變业扒,在樹形視圖中,事件從頂層向下傳遞酣藻。

View和ViewGroup的dispatchTouchEvent方法曹洽,事件傳遞到視圖的第一個方法,它們實現(xiàn)方式不同辽剧,ViewGroup容器視圖送淆,要么消費掉事件,要么派發(fā)給某個子視圖怕轿。View非容器視圖偷崩,自己接手處理。ViewGroup的dispatchTouchEvent方法撞羽。

public boolean dispatchTouchEvent(MotionEvent ev) {
    ...
    boolean handled = false;
    if (onFilterTouchEventForSecurity(ev)) {
        final int action = ev.getAction();
        final int actionMasked = action & MotionEvent.ACTION_MASK;
        //第一部分阐斜,down事件初始化
        if (actionMasked == MotionEvent.ACTION_DOWN) {
            cancelAndClearTouchTargets(ev);
            resetTouchState();
        }
        //第二部分,檢查攔截
        //第三部分诀紊,非打斷谒出,非取消時處理,代碼段貼在后面渡紫,遍歷子視圖
        //第四部分到推,代碼段貼在后面,發(fā)到touch目標(biāo)
        //第五部分惕澎,最終處理莉测。
    }
    ...
    return handled;
}

事件的初始化

down事件,表示手指第一次接觸到屏幕唧喉,清除以前保存的TouchTargets鏈表捣卤,鏈表保存上一次觸屏接收事件的子視圖。

private void cancelAndClearTouchTargets(MotionEvent event) {
    if (mFirstTouchTarget != null) {
        boolean syntheticEvent = false;
        ...
        //發(fā)送ACTION_CANCEL事件
        for (TouchTarget target = mFirstTouchTarget; target != null; target = target.next) {
            resetCancelNextUpFlag(target.child);
            dispatchTransformedTouchEvent(event, true, target.child, target.pointerIdBits);
        }
        //清空TouchTarget八孝,鏈表每一項元素recycle回收
        clearTouchTargets();
        if (syntheticEvent) {
            event.recycle();
        }
    }
}

清理上一次觸摸遺留下來的東西董朝。


攔截判斷

下面是摘取的相關(guān)代碼段。

final boolean intercepted;
if (actionMasked == MotionEvent.ACTION_DOWN
        || mFirstTouchTarget != null) {
    final boolean disallowIntercept = (mGroupFlags & FLAG_DISALLOW_INTERCEPT) != 0;
    if (!disallowIntercept) {
        intercepted = onInterceptTouchEvent(ev);
        //重設(shè)action放置改變干跛。
        ev.setAction(action);
    } else {
        intercepted = false;//設(shè)置過標(biāo)志子姜,永遠(yuǎn)不攔截
    }
} else {
    intercepted = true;
}
    ...
// 是否ACTION_CANCEL類型
final boolean canceled = resetCancelNextUpFlag(this)
        || actionMasked == MotionEvent.ACTION_CANCEL;

final boolean split = (mGroupFlags & FLAG_SPLIT_MOTION_EVENTS) != 0;

down事件和mFirstTouchTarget鏈表不空,這兩種情況需要攔截判斷楼入。
down是第一個事件哥捕,需要攔截判斷牧抽。
非down事件時,鏈表mFirstTouchTarget不空遥赚,說明前面已經(jīng)存在接收事件到目標(biāo)子視圖扬舒,(或許不止一個),可以直接派發(fā)到目標(biāo)凫佛,要經(jīng)過一層攔截判斷讲坎。
這兩種情況進(jìn)行攔截判斷是合理的。

不滿足以上兩個條件愧薛,說明在down事件時晨炕,子視圖中不存在可接收消費事件到目標(biāo),對于非down事件毫炉,不需要向子視圖派發(fā)府瞄,也不需攔截判斷,直接設(shè)置intercepted標(biāo)志碘箍,交給容器視圖onTouchEvent方法。


遍歷查找滿足條件子視圖

經(jīng)過一次攔截判斷鲸郊,不攔截且不取消事件類型時丰榴,優(yōu)先向子視圖派發(fā),事件類型必須是down或pointer_down秆撮,才會向子視圖中查找目標(biāo)四濒。
move事件不會走這一步,會直接派發(fā)給已存在的mFirstTouchTarget目標(biāo)职辨,無目標(biāo)就自己處理盗蟆,不需要在子視圖查找,有intercepted標(biāo)志舒裤,不會來這里喳资。

...
TouchTarget newTouchTarget = null;
boolean alreadyDispatchedToNewTouchTarget = false;

if (!canceled && !intercepted) {//非打斷,非取消處理
    ...
    if (actionMasked == MotionEvent.ACTION_DOWN
                    || (split && actionMasked == MotionEvent.ACTION_POINTER_DOWN)
                    || actionMasked == MotionEvent.ACTION_HOVER_MOVE) {
        final int actionIndex = ev.getActionIndex(); // down事件總是0
        final int idBitsToAssign = split ? 1 << ev.getPointerId(actionIndex)
                        : TouchTarget.ALL_POINTER_IDS;
        //刪除該pointerId曾經(jīng)存儲在某個TouchTarget的記錄腾供。
        //因pointerId重新觸摸仆邓,并確定將被哪個子視圖處理。
        removePointersFromTouchTargets(idBitsToAssign);

        final int childrenCount = mChildrenCount;
        if (newTouchTarget == null && childrenCount != 0) {
            final float x = ev.getX(actionIndex);
            final float y = ev.getY(actionIndex);
            // 找到可以接收事件的View伴鳖,從前向后掃描查找
            final ArrayList<View> preorderedList = buildOrderedChildList();
            final boolean customOrder = preorderedList == null
                            && isChildrenDrawingOrderEnabled();
            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);
                ... //觸摸點坐標(biāo)(x,y)區(qū)域范圍判斷
                if (!canViewReceivePointerEvents(child)
                                || !isTransformedTouchPointInView(x, y, child, null)) {
                    ev.setTargetAccessibilityFocus(false);
                    continue;
                }
                //找到newTouchTarget退出遍歷
                newTouchTarget = getTouchTarget(child);
                if (newTouchTarget != null) {
                    newTouchTarget.pointerIdBits |= idBitsToAssign;
                    break;
                }
                //未找到newTouchTarget节值,繼續(xù)
                resetCancelNextUpFlag(child);
                //看子視圖是否消費。
                if (dispatchTransformedTouchEvent(ev, false, child, idBitsToAssign)) {
                    mLastTouchDownTime = ev.getDownTime();
                    if (preorderedList != null) {
                        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;
                }
                ...
            }
            if (preorderedList != null) preorderedList.clear();
        }

        //未處理的pointerId分配給現(xiàn)有TouchTarget
        if (newTouchTarget == null && mFirstTouchTarget != null) {
            // 新手指未找到可接受事件的View
            // 將idBitsToAssign分配到最早手指的目標(biāo)
            newTouchTarget = mFirstTouchTarget;
            while (newTouchTarget.next != null) {
                newTouchTarget = newTouchTarget.next;
            }
            newTouchTarget.pointerIdBits |= idBitsToAssign;
        }
    }
}

查找一個可以接收事件的子視圖榜聂,創(chuàng)建一個TouchTarget對象搞疗,加入鏈表,如果該視圖已存在TouchTarget,說明是pointer_down類型事件猛拴,已有一個手指觸摸在該視圖,將pointerId合并到該TouchTarget谒获,多個手指觸屏到該子視圖扳埂。

子視圖數(shù)組遍歷順序业簿,如果設(shè)置Z軸值,preorderedList不是空阳懂,按照Z軸排序的列表梅尤,立體的Z軸越大,優(yōu)先分發(fā)岩调,一般情況下不設(shè)置巷燥。從子視圖數(shù)組mChildren尾部開始,按照從大到小的索引遍歷号枕,針對可能重疊放置的子視圖缰揪,保證最上面,也就是最后加入的先接收事件葱淳。數(shù)組索引是xml中定義的索引钝腺,其中,setChildrenDrawingOrderEnabled方法赞厕,可以控制子視圖繪制順序艳狐,getChildDrawingOrder方法,可以獲取該順序皿桑,一般情況下毫目,繪制順序childIndex與數(shù)組索引相同,復(fù)雜情況下诲侮,設(shè)置了setChildrenDrawingOrderEnable(boolean)镀虐,并重寫ViewGroup的getChildDrawingOrder方法,(默認(rèn)的是按照子視圖的添加順序沟绪,即視圖數(shù)組的索引順序)刮便,改變子視圖繪制順序,則子視圖索引childIndex就與遍歷當(dāng)前i值不同近零。

通過兩個方法判斷子視圖滿足事件接收的條件诺核。

private static boolean canViewReceivePointerEvents(View child) {
    return (child.mViewFlags & VISIBILITY_MASK) == VISIBLE
                || child.getAnimation() != null;
}

視圖可見或有動畫。

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

判斷觸摸點坐標(biāo)(x,y)是否位于子視圖區(qū)域范圍久信。根據(jù)MotionEvent的getX和getY方法窖杀,獲取觸控點坐標(biāo)。

區(qū)別getX和getRawX裙士。
getX是相對父視圖坐標(biāo)系的坐標(biāo)值入客,getRawX是相對整個屏幕坐標(biāo)系的坐標(biāo)值。

以父容器坐標(biāo)系為標(biāo)準(zhǔn)。ViewGroup的transformPointToViewLocal方法桌硫,將觸控點坐標(biāo)夭咬,轉(zhuǎn)化成相對子視圖坐標(biāo)系的坐標(biāo)值,減去子視圖相對父視圖mLeft/mTop距離就能實現(xiàn)轉(zhuǎn)換铆隘,若父視圖存在Scroll卓舵,再加上Scroll。
View的pointInView方法膀钠,判斷轉(zhuǎn)換后的坐標(biāo)值是否在子視圖(0,0,width,heigh)區(qū)域范圍掏湾,在該范圍內(nèi)說明觸摸點在該子視圖內(nèi)部。

計算觸屏點在ViewGroup子視圖位置

兩個條件同時滿足肿嘲,找到子視圖融击。遍歷TouchTargets鏈表,查找與該子視圖對應(yīng)的TouchTarget雳窟。

查找到newTouchTarget目標(biāo)

說明pointer_down類型事件尊浪,第2個甚至3、4...個手指觸摸坐標(biāo)均在該子視圖中封救,將手指pointId合并到目標(biāo)Target的pointerIdBits拇涤,結(jié)束遍歷,下面不用再執(zhí)行dispatchTransformedTouchEvent方法去查看子視圖是否消費了誉结,因為已經(jīng)有TouchTarget綁定了該pointerId工育。

未查到newTouchTarget目標(biāo)。

可能存在兩種情況搓彻,down事件,已經(jīng)清理過鏈表嘱朽,pointer_down事件旭贬,新手指觸摸子視圖與前一手指正在觸摸子視圖不同。
將繼續(xù)執(zhí)行dispatchTransformedTouchEvent方法搪泳。
將事件傳遞給子視圖稀轨,成功消費后,新建newTouchTarget岸军,插入鏈表頭部奋刽,宣告down/pointer_down事件被子視圖成功消費,結(jié)束遍歷艰赞,不必再查找其他子視圖啦佣谐。

最后,將未處理的pointerId分配給現(xiàn)有TouchTarget方妖。出現(xiàn)這種情況的場景狭魂。

down事件,如果分發(fā)子視圖成功,會新建newTouchTarget目標(biāo)雌澄,且同時賦值mFirstTouchTarget斋泄,若分發(fā)子視圖失敗,二者都是空镐牺,down事件不會出現(xiàn)這種情況炫掐。
pointer_down事件,第一觸控點在該子視圖的一個兄弟視圖上睬涧,第二觸控點在該子視圖分發(fā)失敗或并未觸摸到任何子視圖募胃,均會導(dǎo)致newTouchTarget是空。說明pointer_down事件未被任何一個子視圖成功消費宙地。idBitsToAssign合并到鏈表最后一項元素的pointerIdBits中摔认。
將未被消費的手指pointerId合并到另一個手指的消費目標(biāo)中,之前已有多個手指觸摸的話宅粥,合并到最早創(chuàng)建的那個TouchTarget目標(biāo)参袱,在鏈表尾部。合并圖秽梅。

TouchTarget目標(biāo)鏈表

圖中三個TouchTarget抹蚀,三個目標(biāo)視圖,四個觸控點企垦,pointer_down事件成功派發(fā)子視圖的TouchTarget插入鏈表前端环壤,pointId代表pointer_down事件產(chǎn)生的手指觸控點Id。如圖钞诡,pointerId是3的觸控點未找到合適的視圖郑现,合并到TouchTarget3中(紅色)。

綜上所述

容器視圖派發(fā)荧降,經(jīng)歷事件初始化接箫,攔截判斷,子視圖遍歷查找朵诫。
攔截判斷辛友,down事件或目標(biāo)鏈存在。
單個手指第一次觸摸時剪返,才會找到觸摸子視圖废累,看他能否承接消費事件,可以才為其創(chuàng)建TouchTarget脱盲。
系統(tǒng)自動為未消費的觸點分配目標(biāo)邑滨,前提是目標(biāo)鏈存在。


目標(biāo)處理
if (mFirstTouchTarget == null) {
    //自身處理
    handled = dispatchTransformedTouchEvent(ev, canceled, null,
                    TouchTarget.ALL_POINTER_IDS);
} else {
    //子視圖傳遞
    TouchTarget predecessor = null;
    TouchTarget target = mFirstTouchTarget;
    while (target != null) {
        final TouchTarget next = target.next;
        if (alreadyDispatchedToNewTouchTarget && target == newTouchTarget) {
            handled = true;//該手指第一次觸屏被處理了钱反,是新目標(biāo)驼修。
        } else {//已有目標(biāo)殿遂。
            final boolean cancelChild = resetCancelNextUpFlag(target.child)
                            || intercepted;
             //根據(jù)目標(biāo)pointerIdBits匹配手指
            if (dispatchTransformedTouchEvent(ev, cancelChild,
                            target.child, target.pointerIdBits)) {
                handled = true;
            }
            if (cancelChild) {
                //若是打斷,將目標(biāo)target回收乙各。
                if (predecessor == null) {
                    mFirstTouchTarget = next;
                } else {
                    predecessor.next = next;
                }
                target.recycle();
                target = next;
                continue;
            }
        }
        //設(shè)置前一個處理目標(biāo)墨礁。
        predecessor = target;
        target = next;
    }
}

鏈表是空,可以推斷出耳峦,無子視圖消費down事件恩静。未找到符合觸摸坐標(biāo)的子視圖或者找到子視圖但未消費。
調(diào)用dispatchTransformedTouchEvent方法蹲坷,子視圖參數(shù)傳空驶乾,交給基類View的dispatchTouchEvent方法,把當(dāng)前容器視圖當(dāng)成一個View視圖循签,View的dispatchTouchEvent方法將觸發(fā)onTouchEvent方法级乐,觸控點id傳ALL_POINTER_IDS。

鏈表不是空县匠,遍歷风科,說明至少存在一個目標(biāo)視圖消費事件,多個節(jié)點說明多個手指觸控點存在子視圖消費事件乞旦。
如果有新增標(biāo)志且目標(biāo)是newTouchTarget贼穆,說明事件是pointer_down或down類型。在前面代碼中兰粉,事件已dispatchTransformedTouchEvent被子視圖消費掉故痊,直接設(shè)置handled標(biāo)志。簡單情況下玖姑,只有一個手指觸摸愕秫,一個目標(biāo),可以將直接handled返回焰络。
如果非當(dāng)前新增豫领,該事件有任何類型的可能,派發(fā)事件到該目標(biāo)的對應(yīng)子視圖舔琅,交給dispatchTransformedTouchEvent處理,根據(jù)處理結(jié)果設(shè)置handled標(biāo)志洲劣。

在onInterceptTouchEvent方法被攔截备蚓,設(shè)置cancelChild標(biāo)志,處理時囱稽,向子視圖發(fā)送cancel事件郊尝,交給容器視圖處理。子視圖在move事件正常消費過程中战惊,突然遭遇容器視圖攔截流昏,傳遞給子視圖的事件改變?yōu)閏ancel事件,這次事件子視圖的返回依然是消費成功。鏈表所有元素依次被回收况凉。下一次move事件再次判斷時谚鄙,表頭mFirstTouchTarget是空,代碼不再執(zhí)行到這里刁绒,事件也不會向下傳遞闷营。
設(shè)置cancelChild標(biāo)志子視圖,從鏈表刪除這個元素知市。前一個元素predecessor引用傻盟,遍歷鏈表刪除元素時,可以找到前面的引用嫂丙,將其next指向next娘赴,刪除當(dāng)前。
如果未被攔截跟啤,
不設(shè)置cancelChild標(biāo)志诽表。子視圖分發(fā)處理。以上情況腥光。該手指觸屏事件有可能是各種類型关顷,鏈表元素也可能有多個。那么武福,一個手指的在某個子視圖的事件都會經(jīng)歷整個目標(biāo)鏈议双,都會派發(fā)么?在真正派發(fā)方法中再分析捉片。


派發(fā)的最終處理

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

cancel和up事件類型平痰,表示事件結(jié)束,清空內(nèi)部目標(biāo)鏈表伍纫,不會再有事件傳遞到該視圖宗雇,pointer_up事件,表示有一個手指觸控點離開莹规,仍有其他手指觸控點赔蒲,將該觸控點對應(yīng)pointerId在TouchTarget中清除。


真正的派發(fā)方法

dispatchTransformedTouchEvent方法良漱,在前面第三和四部分都涉及過該方法舞虱,分別是新接觸點消費和遍歷目標(biāo)鏈表消費。

private boolean dispatchTransformedTouchEvent(MotionEvent event, 
            boolean cancel,View child, int desiredPointerIdBits) {
    final boolean handled;
    final int oldAction = event.getAction();
    if (cancel || oldAction == MotionEvent.ACTION_CANCEL) {
        event.setAction(MotionEvent.ACTION_CANCEL);
        //派發(fā)給子視圖或當(dāng)前視圖父類即View處理母市。
        if (child == null) {
            handled = super.dispatchTouchEvent(event);
        } else {
            handled = child.dispatchTouchEvent(event);
        }
        event.setAction(oldAction);
        return handled;
    }
    //oldPointerIdBits矾兜,所有手指pointerId集合
    //desiredPointerIdBits,目標(biāo)觸控點集合
    final int oldPointerIdBits = event.getPointerIdBits();
    final int newPointerIdBits = oldPointerIdBits & desiredPointerIdBits;
    //目標(biāo)里面一個當(dāng)前的手指pointerId都沒有患久,說明該id離開了屏幕
    if (newPointerIdBits == 0) {
        return false;
    }

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

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

    transformedEvent.recycle();
    return handled;
}

有幾個參數(shù)椅寺,事件對象浑槽,取消標(biāo)志,目標(biāo)視圖以及目標(biāo)觸控點返帕,在每一個TouchTarget中桐玻,都保存pointerIdBits,代表觸控點位溉旋。

如果有cancel標(biāo)志畸冲,或者是cancel事件,
將MotionEvent對象設(shè)置為cancel事件派發(fā)观腊,派發(fā)給子視圖或當(dāng)前視圖邑闲。表示這層視圖有打斷,或者是更上層視圖有打斷梧油,發(fā)給該層視圖的cancel事件苫耸。

根據(jù)MotionEvent對象,獲取觸控點pointerId儡陨,
因為在上面代碼中褪子,每個事件都會經(jīng)歷整個目標(biāo)鏈表,需要將該事件觸控點匹配目標(biāo)內(nèi)部存儲集合骗村,只能向包含該pointerId目標(biāo)TouchTarget的子視圖派發(fā)嫌褪。desiredPointerIdBits表示合并到處理目標(biāo)TouchTarget的觸控點集合

oldPointerIdBits是所有手指觸控點集合胚股,
將它與處理目標(biāo)的觸控點集合與操作笼痛,如果新值是0,沒有位相等琅拌,說明該目標(biāo)的觸控id集合對應(yīng)的手指已經(jīng)不再屏幕缨伊,無法匹配。

如果newPointerIdBits與oldPointerIdBits相等进宝,表示很可能是所有的手指均觸控在目標(biāo)觸控點對應(yīng)子視圖上刻坊。
如果不相等,從所有手指觸控點id中党晋,分離出desiredPointerIdBits中存儲的pointerId谭胚,然后封裝成一個新的事件transformedEvent,分發(fā)到目標(biāo)對應(yīng)子視圖未玻。這里再看一下第四部分的那個問題灾而。

舉例,視圖有兩個子視圖深胳,各有1個手指觸摸,目標(biāo)鏈有兩個TouchTarget铜犬,通過移動其中1個手指產(chǎn)生move事件舞终,按照前面的邏輯轻庆,事件會經(jīng)歷整個目標(biāo)鏈,兩個TouchTarget都會處理敛劝,執(zhí)行兩次dispatchTransformedTouchEvent方法余爆。從2個手指分離出每一個子視圖觸控點集合,封裝事件夸盟,然后發(fā)向每一個子視圖蛾方,只有一個手指move,兩個子視圖都會收到move事件上陕。

調(diào)試圖

定義了兩個View桩砰,兩個手指分別觸摸他們,一個手指不動释簿,滑動另外一個亚隅,產(chǎn)生連續(xù)move事件,結(jié)果兩個視圖都會接收到庶溶,而且是前后連續(xù)的煮纵,說明事件是先后發(fā)送兩個視圖的,就是在遍歷TouchTarget時偏螺。后面的數(shù)字是打印View對象的HashCode行疏。
如果child是空時,父類分發(fā)套像,child不是空時酿联,子視圖分發(fā)。
若子視圖是容器凉夯,處理方法與前面一致货葬,每層樹結(jié)構(gòu)節(jié)點ViewGroup的dispatchTouchEvent方法遞歸,若子視圖是葉子節(jié)點劲够,觸發(fā)基類View的dispatchTouchEvent方法震桶。
派發(fā)給子視圖或本視圖父類View#dispatchTouchEvent。關(guān)鍵點是入?yún)⒆右晥D是否為空征绎。不管是以上哪一種情況蹲姐,事件派發(fā)成功返回true標(biāo)志。
到這里人柿,ViewGroup的dispatchTouchEvent方法的這六個部分分析完了柴墩。


單手指觸控流程

TouchTarget鏈表中僅有一個節(jié)點。
down事件凫岖,觸控點位于子視圖江咳,且子視圖消費,創(chuàng)建TouchTarget哥放,封裝子視圖與觸控點pointerId歼指,子視圖未消費爹土,TouchTarget一直保持空,ViewGroup自己處理踩身。
move事件胀茵,TouchTarget不空,說明down事件已找到合適的消費目標(biāo)挟阻,交給TouchTarget內(nèi)部保存的子視圖處理琼娘,TouchTarget是空,ViewGroup自己處理附鸽。

ViewGroup的dispatchTouchEvent流程

down脱拼,move,up事件來到ViewGroup拒炎,第一站是dispatchTouchEvent方法挪拟。

  • down先來,清理遺留TouchTarget击你,onInterceptTouchEvent決定是否打斷玉组,兩種處理方式。子視圖成功處理時給mFirstTouchTarget賦值TouchTarget對象丁侄。
  • move進(jìn)來惯雳,看mFirstTouchTarget有值么,沒有鸿摇?子視圖不給力石景,down未搞定,必須打斷自己處理拙吉。mFirstTouchTarget有值潮孽,子視圖已經(jīng)搞定down,onInterceptTouchEvent決定是否打斷筷黔,打斷向子視圖發(fā)Cancel往史,置空TouchTarget,繼續(xù)走子視圖處理佛舱,下一次move事件則走另一條TouchTarget為空的線路椎例。
    若不斷的有move事件進(jìn)來則說明自己本身View#dispatchTouchEvent或者子視圖一定成功處理,包括Down事件请祖。
    只有ViewGroup#dispatchTouchEvent從Down事件開始向上層返回true订歪,才會在上層ViewGroup中為其建立一個TouchTarget,對上層視圖來說肆捕,該ViewGroup消費了事件才會有源源不斷的move事件進(jìn)來刷晋。
  • 從上層向下看,該ViewGroup#dispatchTouchEvent返回true,說明在當(dāng)前ViewGroup中事件被處消化眼虱,至于是它的子視圖還是本身消化的或舞,上層不關(guān)心,只要求結(jié)果蒙幻。返回false,對上層來說該ViewGroup總歸是沒消化胆筒。

總結(jié)

單個手指從觸摸到離開屏幕產(chǎn)生的完整事件流ACTION_DOWN邮破、一系列ACTION_MOVE、ACTION_UP仆救。
進(jìn)入視圖的每個事件抒和,先交給dispatchTouchEvent方法分發(fā),本視圖或其子視圖消費事件彤蔽,View和ViewGroup的分發(fā)方案不同摧莽,ViewGroup重點是子視圖分發(fā),View重點是本視圖消費顿痪。
攔截镊辕,一旦攔截成功,即使前期事件有處理目標(biāo)蚁袭,也會將目標(biāo)回收征懈,后續(xù)事件不會再觸發(fā)攔截方法,View類無攔截方法揩悄。
視圖處理卖哎,onTouchEvent方法,ViewGroup視圖删性,攔截或子視圖未消費時調(diào)用亏娜,View視圖,無Touch監(jiān)聽器時調(diào)用蹬挺,當(dāng)前視圖及子視圖的最后一道防線维贺,如果onTouchEvent未消費,上層便不會為該子視圖保存目標(biāo)汗侵,后續(xù)事件再無法向它傳遞了幸缕。
一個子視圖成功處理down事件,父視圖內(nèi)部將為其創(chuàng)建目標(biāo)晰韵,綁定該視圖发乔,不攔截情況下,后續(xù)的move事件將直接傳遞到該視圖處理雪猪。未成功處理down事件栏尚,將down事件交給父視圖onTouchEvent方法處理,其他事件再也不會傳遞到該視圖只恨。
一個子視圖成功處理down事件译仗,父視圖內(nèi)部將為其創(chuàng)建目標(biāo)抬虽,綁定該視圖,不攔截情況下move事件直接傳遞到該視圖處理纵菌,如果move事件未成功處理阐污,事件將無視圖接手,包括父視圖的onTouchEvent方法咱圆,最終將交給Activity的onTouchEvent處理笛辟。


任重而道遠(yuǎn)

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市序苏,隨后出現(xiàn)的幾起案子手幢,更是在濱河造成了極大的恐慌,老刑警劉巖忱详,帶你破解...
    沈念sama閱讀 206,311評論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件围来,死亡現(xiàn)場離奇詭異,居然都是意外死亡匈睁,警方通過查閱死者的電腦和手機(jī)监透,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,339評論 2 382
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來航唆,“玉大人才漆,你說我怎么就攤上這事》鸬悖” “怎么了醇滥?”我有些...
    開封第一講書人閱讀 152,671評論 0 342
  • 文/不壞的土叔 我叫張陵,是天一觀的道長超营。 經(jīng)常有香客問我鸳玩,道長,這世上最難降的妖魔是什么演闭? 我笑而不...
    開封第一講書人閱讀 55,252評論 1 279
  • 正文 為了忘掉前任不跟,我火速辦了婚禮,結(jié)果婚禮上米碰,老公的妹妹穿的比我還像新娘窝革。我一直安慰自己,他們只是感情好吕座,可當(dāng)我...
    茶點故事閱讀 64,253評論 5 371
  • 文/花漫 我一把揭開白布虐译。 她就那樣靜靜地躺著,像睡著了一般吴趴。 火紅的嫁衣襯著肌膚如雪漆诽。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 49,031評論 1 285
  • 那天,我揣著相機(jī)與錄音厢拭,去河邊找鬼兰英。 笑死,一個胖子當(dāng)著我的面吹牛供鸠,可吹牛的內(nèi)容都是我干的畦贸。 我是一名探鬼主播,決...
    沈念sama閱讀 38,340評論 3 399
  • 文/蒼蘭香墨 我猛地睜開眼楞捂,長吁一口氣:“原來是場噩夢啊……” “哼家制!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起泡一,我...
    開封第一講書人閱讀 36,973評論 0 259
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎觅廓,沒想到半個月后鼻忠,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 43,466評論 1 300
  • 正文 獨居荒郊野嶺守林人離奇死亡杈绸,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 35,937評論 2 323
  • 正文 我和宋清朗相戀三年帖蔓,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片瞳脓。...
    茶點故事閱讀 38,039評論 1 333
  • 序言:一個原本活蹦亂跳的男人離奇死亡塑娇,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出劫侧,到底是詐尸還是另有隱情埋酬,我是刑警寧澤,帶...
    沈念sama閱讀 33,701評論 4 323
  • 正文 年R本政府宣布烧栋,位于F島的核電站写妥,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏审姓。R本人自食惡果不足惜珍特,卻給世界環(huán)境...
    茶點故事閱讀 39,254評論 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望魔吐。 院中可真熱鬧扎筒,春花似錦、人聲如沸酬姆。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,259評論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽辞色。三九已至症脂,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背诱篷。 一陣腳步聲響...
    開封第一講書人閱讀 31,485評論 1 262
  • 我被黑心中介騙來泰國打工壶唤, 沒想到剛下飛機(jī)就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人棕所。 一個月前我還...
    沈念sama閱讀 45,497評論 2 354
  • 正文 我出身青樓闸盔,卻偏偏與公主長得像,于是被迫代替她去往敵國和親琳省。 傳聞我的和親對象是個殘疾皇子迎吵,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 42,786評論 2 345