ViewGroup Touch 源碼解析

1、現(xiàn)象分析

我們分別定義四個(gè)自定義view

// 如果是繼承RelativeLayout則實(shí)現(xiàn)其三個(gè)方法,并輸出log
// dispatchTouchEvent
// onInterceptTouchEvent
// onTouchEvent
public class MyRelaLayout1 extends RelativeLayout{}

public class MyRelaLayout2 extends RelativeLayout {}

// 如果是繼承TextView則實(shí)現(xiàn)其兩個(gè)方法,并輸出log
// dispatchTouchEvent
// onTouchEvent
public class MyTextView1 extends TextView {}

public class MyTextView2 extends TextView {}

寫一個(gè)相互嵌套的布局

    <com.djk.test.touch.MyRelaLayout1
        android:id="@+id/p_1"
        android:layout_width="200dp"
        android:layout_height="200dp">

        <com.djk.test.touch.MyRelaLayout2
            android:id="@+id/p_2"
            android:layout_width="200dp"
            android:layout_height="200dp">

            <com.djk.test.touch.MyTextView1
                android:id="@+id/c_1"
                android:layout_width="150dp"
                android:layout_height="150dp"
                android:background="#000000" />

            <com.djk.test.touch.MyTextView2
                android:id="@+id/c_2"
                android:layout_width="100dp"
                android:layout_height="100dp"
                android:background="#ff0000" />

        </com.djk.test.touch.MyRelaLayout2>

    </com.djk.test.touch.MyRelaLayout1>

實(shí)現(xiàn)效果如下圖,MyTextView1為黑色背景,MyTextView2為紅色背景,并覆蓋在MyTextView1之上

Paste_Image.png

下面我們來(lái)進(jìn)行操作來(lái)分析touch事件的傳遞與攔截
現(xiàn)象一:就現(xiàn)在的默認(rèn)情況下,我們點(diǎn)擊紅色區(qū)域

-> MyRelaLayout1.dispatchTouchEvent.Down -> MyRelaLayout1.onInterceptTouchEvent.Down
-> MyRelaLayout2.dispatchTouchEvent.Down -> MyRelaLayout2.onInterceptTouchEvent.Down
-> MyTextView2.dispatchTouchEvent.Down -> MyTextView2.onTouchEvent.Down
-> MyTextView1.dispatchTouchEvent.Down -> MyTextView1.onTouchEvent.Down
-> MyRelaLayout2.onTouchEvent.Down 
-> MyRelaLayout1.onTouchEvent.Down

我們得到touch事件從MyRelaLayout1-> MyRelaLayout2-> MyTextView2-> MyTextView1
可以分析得出touch傳遞是從最外層的MyRelaLayout1傳到第二層MyRelaLayout2
在RelativeLayout布局中,MyTextView2 和MyTextView1屬于平等關(guān)系,那么最上層子view先拿到touch事件,然后再傳遞到下面的子view

現(xiàn)象二:我們給MyTextView2設(shè)置onTouchEvent事件return true

-> MyRelaLayout1.dispatchTouchEvent.Down -> MyRelaLayout1.onInterceptTouchEvent.Down
-> MyRelaLayout2.dispatchTouchEvent.Down -> MyRelaLayout2.onInterceptTouchEvent.Down
-> MyTextView2.dispatchTouchEvent.Down -> MyTextView2.onTouchEvent.Down
// ... Move
-> MyRelaLayout1.dispatchTouchEvent.Up -> MyRelaLayout1.onInterceptTouchEvent.Up
-> MyRelaLayout2.dispatchTouchEvent.Up -> MyRelaLayout2.onInterceptTouchEvent.Up
-> MyTextView2.dispatchTouchEvent.Up -> MyTextView2.onTouchEvent.Up

可以看出我們成功的把MyTextView1的所有事件都截?cái)嗔?/p>

現(xiàn)象三:我們給MyRelaLayout2設(shè)置onInterceptTouchEvent事件return true

-> MyRelaLayout1.dispatchTouchEvent.Down -> MyRelaLayout1.onInterceptTouchEvent.Down
-> MyRelaLayout2.dispatchTouchEvent.Down -> MyRelaLayout2.onInterceptTouchEvent.Down -> MyRelaLayout2.onTouchEvent.Down
-> MyRelaLayout1.dispatchTouchEvent.Up -> MyRelaLayout1.onInterceptTouchEvent.Up
-> MyRelaLayout2.dispatchTouchEvent.Up -> MyRelaLayout2.onTouchEvent.Up

可以看出,成功將后面所有的事件都截?cái)嗔耍⒄{(diào)用了自己的onTouchEvent方法

2、源碼分析(android-25)

    public boolean dispatchTouchEvent(MotionEvent ev) {
        // ...
        boolean handled = false;
        if (onFilterTouchEventForSecurity(ev)) {
            final int action = ev.getAction();
            final int actionMasked = action & MotionEvent.ACTION_MASK;

            if (actionMasked == MotionEvent.ACTION_DOWN) {
                // 清除touch targets  其核心代碼就是  mFirstTouchTarget = null;
                cancelAndClearTouchTargets(ev);
                resetTouchState();
            }

            // 是否要攔截
            final boolean intercepted;
            // 如果是Down事件,或 mFirstTouchTarget != null;
            if (actionMasked == MotionEvent.ACTION_DOWN 
                    || mFirstTouchTarget != null) {
                // 子類請(qǐng)求不要攔截事件
                final boolean disallowIntercept = (mGroupFlags & FLAG_DISALLOW_INTERCEPT) != 0;
                // 子類沒(méi)有請(qǐng)求不要攔截事件,走我們正常的流程
                if (!disallowIntercept) {
                    // 調(diào)用自己的onInterceptTouchEvent方法來(lái)判斷是否要攔截touch事件 默認(rèn)情況下返回 fase
                    intercepted = onInterceptTouchEvent(ev);
                    ev.setAction(action); // restore action in case it was changed
                } else {
                    intercepted = false;
                }
            } else {
                intercepted = true;
            }

            // ...
            TouchTarget newTouchTarget = null;
            boolean alreadyDispatchedToNewTouchTarget = false;
            // 沒(méi)有取消 & 沒(méi)有攔截事件 正常情況下if能夠執(zhí)行
            if (!canceled && !intercepted) {
                if (actionMasked == MotionEvent.ACTION_DOWN
                        || (split && actionMasked == MotionEvent.ACTION_POINTER_DOWN)
                        || actionMasked == MotionEvent.ACTION_HOVER_MOVE) {
                    // ...
                    final int childrenCount = mChildrenCount;
                    if (newTouchTarget == null && childrenCount != 0) {
                        // ...
                        // 獲取其子view的集合
                        final View[] children = mChildren;
                        // 反序for循環(huán),獲取子view(這里就是RelativeLayout中多層覆蓋的情況下,首先拿到最上層的子view)
                        for (int i = childrenCount - 1; i >= 0; i--) {
                            final int childIndex = getAndVerifyPreorderedIndex(
                                    childrenCount, i, customOrder);
                            final View child = getAndVerifyPreorderedView(
                                    preorderedList, children, childIndex);

                            // ...

                            newTouchTarget = getTouchTarget(child);
                            // ...
                            // 詳見(jiàn)下面的第二個(gè)代碼塊,如果dispatchTouchEvent方法返回true 則能進(jìn)入這個(gè)方法
                            if (dispatchTransformedTouchEvent(ev, false, child, idBitsToAssign)) {
                                // ...
                                // addTouchTarget() 方法核心代碼 mFirstTouchTarget = target; 這里將 mFirstTouchTarget 賦值
                                // 然后將mFirstTouchTarget 的值再賦值給 newTouchTarget
                                newTouchTarget = addTouchTarget(child, idBitsToAssign);
                                alreadyDispatchedToNewTouchTarget = true;
                                break;
                            }
                        }
                        if (preorderedList != null) preorderedList.clear();
                    }
                }
            }

            // 這里如果mFirstTouchTarget = null,則去調(diào)用代碼塊2的方法,傳入的child為null,這個(gè)時(shí)候就會(huì)調(diào)用自己的onTouchEvent方法
            if (mFirstTouchTarget == null) {
                // No touch targets so treat this as an ordinary view.
                handled = dispatchTransformedTouchEvent(ev, canceled, null,
                        TouchTarget.ALL_POINTER_IDS);
            } else {
                // Dispatch to touch targets, excluding the new touch target if we already
                // dispatched to it.  Cancel touch targets if necessary.
                TouchTarget predecessor = null;
                TouchTarget target = mFirstTouchTarget;
                while (target != null) {
                    final TouchTarget next = target.next;
                    if (alreadyDispatchedToNewTouchTarget && target == newTouchTarget) {
                        handled = true;
                    } else {
                        final boolean cancelChild = resetCancelNextUpFlag(target.child)
                                || intercepted;
                        if (dispatchTransformedTouchEvent(ev, cancelChild,
                                target.child, target.pointerIdBits)) {
                            handled = true;
                        }
                        if (cancelChild) {
                            if (predecessor == null) {
                                mFirstTouchTarget = next;
                            } else {
                                predecessor.next = next;
                            }
                            target.recycle();
                            target = next;
                            continue;
                        }
                    }
                    predecessor = target;
                    target = next;
                }
            }

            // 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;
    }
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);
            // 如果child 為null,則去調(diào)用父類的dispatchTouchEvent,ViewGroup的父類也是View,
            // 所以相當(dāng)于調(diào)用了View的dispatchTouchEvent,進(jìn)而會(huì)調(diào)用自己的onTouchEvent方法
            if (child == null) {
                handled = super.dispatchTouchEvent(event);
            } else {// 如果child 不為null,則去調(diào)用子類的dispatchTouchEvent,子類的touch事件從這里開(kāi)始
                handled = child.dispatchTouchEvent(event);
            }
            event.setAction(oldAction);
            return handled;
        }

        return handled;
    }

3、源碼變通(android源碼中,所有的touch事件都會(huì)調(diào)用dispatchTouchEvent方法,其實(shí)我們可以將它拆成Down事件和其他事件來(lái)分析)

第一步,當(dāng)前是Down事件:

    public boolean dispatchTouchEvent(MotionEvent ev) {
        boolean handled = false;
        mFirstTouchTarget = null;
        boolean intercepted = false;
        // 調(diào)用自己的onInterceptTouchEvent方法來(lái)判斷是否要攔截touch事件 默認(rèn)情況下返回 false
        intercepted = onInterceptTouchEvent(ev);
        // 這里如果是不攔截touch事件
        if (!intercepted) {
            // 獲取其子view的集合
            final View[] children = mChildren;
            // 反序for循環(huán),獲取子view(這里就是RelativeLayout中多層覆蓋的情況下,首先拿到最上層的子view)
            for (int i = childrenCount - 1; i >= 0; i--) {
                final View child = getChildView(i);
                // 調(diào)用子類的dispatchTouchEvent,如果返回true則將 mFirstTouchTarget 賦值
                handled = child.dispatchTouchEvent(event);
                if (handled) {
                    // 這里將 mFirstTouchTarget 賦值
                    mFirstTouchTarget = target;
                    // 跳出循環(huán),此后的view就收不到touch事件了
                    break;
                }
            }
        }
        if (mFirstTouchTarget == null) {
            // 如果是mFirstTouchTarget == null,則說(shuō)明沒(méi)有一個(gè)子類返回true,
            // 則直接調(diào)用View的dispatchTouchEvent,進(jìn)而會(huì)調(diào)用自己的onTouchEvent方法
            handled = super.dispatchTouchEvent(event);
        } 
        return handled;
    }

第二步,當(dāng)前是 Move or Up 事件:

    public boolean dispatchTouchEvent(MotionEvent ev) {
        // ...
        boolean handled = false;

        // 是否要攔截
        final boolean intercepted;
        // 這里的 mFirstTouchTarget 是由Down事件記錄的
        if (mFirstTouchTarget != null) {
            // 調(diào)用自己的onInterceptTouchEvent方法來(lái)判斷是否要攔截touch事件 默認(rèn)情況下返回 fase
            intercepted = onInterceptTouchEvent(ev);
        } else {// 如果Down事件記錄的 mFirstTouchTarget == null; 則攔截判斷直接置為 true
            intercepted = true;
        }
        // 這里如果是不攔截touch事件
        if (!intercepted) {
            // 獲取其子view的集合
            final View[] children = mChildren;
            // 反序for循環(huán),獲取子view(這里就是RelativeLayout中多層覆蓋的情況下,首先拿到最上層的子view)
            for (int i = childrenCount - 1; i >= 0; i--) {
                final View child = getChildView(i);
                // 調(diào)用子類的dispatchTouchEvent,如果返回true則將 mFirstTouchTarget 賦值
                handled = child.dispatchTouchEvent(event);
                if (handled) {
                    // 這里將 mFirstTouchTarget 賦值
                    mFirstTouchTarget = target;
                    // 跳出循環(huán),后面的view就收不到touch事件
                    break;
                }
            }
        }
        if (mFirstTouchTarget == null) {
            // 如果是mFirstTouchTarget == null,則說(shuō)明沒(méi)有一個(gè)子類返回true,
            // 則直接調(diào)用View的dispatchTouchEvent,進(jìn)而會(huì)調(diào)用自己的onTouchEvent方法
            handled = super.dispatchTouchEvent(event);
        }
        return handled;
    }
Paste_Image.png

4、下面來(lái)總結(jié)一下吧
①、ACTION_DOWN事件為一個(gè)事件序列的開(kāi)始,中間有若干個(gè)ACTION_MOVE,最后以ACTION_UP結(jié)束。
②、一個(gè)clickable或者longClickable的View會(huì)永遠(yuǎn)消費(fèi)Touch事件,不管他是enabled還是disabled的
③、Touch事件是從最頂層的View一直分發(fā)到手指touch的最里層的View,如果最里層View消費(fèi)了ACTION_DOWN事件(設(shè)置onTouchListener,并且onTouch()返回true 或者onTouchEvent()方法返回true)才會(huì)觸發(fā)ACTION_MOVE,ACTION_UP的發(fā)生,如果某個(gè)ViewGroup攔截了Touch事件,則Touch事件交給ViewGroup處理
④、如果某一個(gè)View攔截了事件,那么同一個(gè)事件序列的其他所有事件都會(huì)交由這個(gè)View處理,此時(shí)不再調(diào)用View(ViewGroup)的onIntercept()方法去詢問(wèn)是否要攔截了。

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

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

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