1. 程式人生 > >點選事件分發機制 關鍵原始碼筆記

點選事件分發機制 關鍵原始碼筆記

請注意,涉及到的原始碼 SDK 版本為 27,不同版本可能存在偏差,一切以具體的原始碼為準。

宣告: 文字部分主要參考自 《Android 開發藝術探索》,原始碼部分的解讀主要摘抄自 Android 觸控事件機制(三) View中觸控事件詳解Android 觸控事件機制(四) ViewGroup中觸控事件詳解,但是都加入了自己的思考。


首先,需要明確的就是同一個事件序列,是指從手指接觸螢幕的那一刻起到手指離開螢幕的那一刻結束,這個過程所產生的一系列事件,即一個連續的 ACTION_DOWN -> ACTION_MOVE (0 個或者多個)-> ACTION_UP

事件串。

對於 ACTION_DOWNACTION_MOVEACTION_UP ,在下面簡稱為 DOWNMOVEUP


  1. dispatchTouchEvent (MotionEvent ev)

    用來進行事件分發的,在 View 和 ViewGroup 中(雖然 ViewGroup 繼承自 View,但是重寫了此方法)的實現會有不同。

    如果事件能夠傳遞給當前 View 或 ViewGroup,則該方法一定會被呼叫。

    其返回結果表示是否消耗當前事件(消耗的含義是指返回 true,只要返回 true 就表示消耗了,而不管有沒有利用事件進行某種邏輯的處理),其受到當前 View/ViewGroup 的 onTouchEvent

    和子 View/ViewGroup 的 dispatchTouchEvent 方法的影響。

  2. onInterceptTouchEvent (MotionEvent ev)

    在 ViewGroup 的 dispatchTouchEvent() 方法內部進行呼叫, 用來判斷是否攔截某個事件。在 View 中沒有該方法,只存在於 ViewGroup 中。

    如果當前 ViewGroup 攔截了 DOWN 事件則後續的 MOVE、UP 事件來時都不會呼叫 onInterceptTouchEvent() 了,如果攔截的是 DOWN 後的 MOVE 事件,那麼 UP 事件來的時候還是可能會呼叫 onInterceptTouchEvent()

    方法

  3. onTouchEvent (MotionEvent ev)

    該方法在 View 的 dispatchTouchEvent() 方法內部直接被呼叫;在 ViewGroup 中是間接被呼叫 。

    其用來處理事件,返回 true 表示消耗當前事件。

    如果當前 View/ViewGroup 對於傳遞過來的 DOWN 事件沒有消耗,則無法(因為一般情況下是由上級 ViewGroup 主動傳遞的)接收後續的 MOVE、UP 事件。而如果是沒有消耗 MOVE 事件(前提是消耗了 DOWN 事件),則還是可以接收後續的 UP 事件。


提前總結部分:

摘抄自 《Android 開發藝術探索》,但是內容加入了自己的見解。

(1)正常情況下,一個事件序列只能被一個 ViewGroup 攔截且消耗(或者被一個 View 消耗),因為一旦一個元素攔截消耗了某個事件,那麼同一個事件序列內的接下來的所有事件都會直接交給它處理。 因為同一個事件序列中的事件不能分別由兩個 View 同時處理,但是通過特殊手段可以實現,如一個 View 將本該自己處理的事件通過其他的View 的 onTouchEvent 強行傳遞給其處理。

(2)在一個事件序列中,某個 ViewGroup 一旦決定攔截該事件序列中的某一事件,那麼這一個事件序列之後的事件就只能由它來處理(如果事件序列能夠傳遞給它的話),並且它的 onInterceptTouchEvent() 方法不會再被呼叫。

(3)某個 View/ViewGroup 如果一旦開始處理事件,如果它不消耗 ACTION_DOWN 事件onTouchEvent 返回了 false),那麼同一事件序列中的接下來的事件都不會再交給它來處理了,並且 ACTION_DOWN 事件將重新交由它的父控制元件處理,即父控制元件的 onTouchEvent() 會被呼叫。

(4)如果 View/ViewGroup 不消耗除 ACTION_DOWN 以外的某個事件,那麼該事件就會傳遞到該 View/ViewGroup 就截止了,此時父控制元件的 onTouchEvent() 並不會被呼叫(但是該事件會傳遞給 Activity 處理,因為 Activity 會根據最終返回的 true/false 進行相應的處理),並且當前 View/ViewGroup 可以持續收到後續的事件。

(5)ViewGroup 預設不攔截任何事件。Android 原始碼中的 ViewGroup 的 onInterceptTouchEvent() 預設返回 false。

(6)View 沒有 onInterceptTouchEvent() 方法,一旦事件傳給它其 onTouchEvent() 就會被呼叫。

(7)View 的 onTouchEvent 預設都會消耗事件(返回 true),除非是不可點選的(clickable == false && longClickable == true)。View 的 longClickable 預設為 fasle,2而 clickable 則要分情況,比如 Button 的 clickable 預設為 true,而 TextView 則為 false。

(8)View 的 enable 屬性不影響 onTouchEvent() 的返回值,即使 View 是 disable 狀態的,只要它的 clickable / longClickable 有一個為 true,則 onTouchEvent() 返回 true。

(9)onClick 會發生的前提是當前 View 是可點選的,且接收到了 down 和 up 事件(對於 up 事件接收到了但不一定要消耗,但是對於 down 事件一般情況下不消耗就無法接收後續事件)。更準確的說,是要經過 View 自身的 onTouchEvent() 方法,因為在該方法裡面,當傳遞了 down 和 up 事件之後,就會達到某個條件觸發 onClick。(後面的 View 的 onTouchEvent() 的原始碼分析會說明)

(10)事件傳遞過程是由外向內的,即事件總是先傳遞給父元素,再由父元素分發給子元素,通過 requestDisallowInterceptTouchEvent() 方法可以在子元素中干預父元素的事件分發過程,但是 ACTION_DOWN 除外。


原始碼解讀

包含
 - ViewGroup 的 dispatchTouchEvent(MotionEvent ev)
 - ViewGroup 的 dispatchTransformedTouchEvent(MotionEvent ev)
 - View(不含 ViewGroup) 的 dispatchTouchEvent(MotionEvent ev)
 - View 的 onTouchEvent(MotionEvent ev)

  • ViewGroup 的 dispatchTouchEvent(MotionEvent ev) 原始碼
public boolean dispatchTouchEvent(MotionEvent ev) {
    // mInputEventConsistencyVerifier是除錯用的,不會理會
    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);
    }
    
    /* 第1步:判斷是否要分發該觸控事件 */
    // onFilterTouchEventForSecurity() 表示是否要分發該觸控事件
    // 如果該 View 不是位於頂部,並且有設定屬性使該 View 不在頂部時不響應觸控事件,則不分發該觸控事件,即返回false
    // 否則,則對觸控事件進行分發,即返回true
    boolean handled = false;
    if (onFilterTouchEventForSecurity(ev)) {
        final int action = ev.getAction();
        final int actionMasked = action & MotionEvent.ACTION_MASK;
        /* 第2步:檢測是否需要清空目標和狀態 */
        // 如果是 ACTION_DOWN(即按下事件) ,則清空之前的觸控事件處理目標和狀態。
        // 這裡的情況狀態包括:
        // (01) 清空 mFirstTouchTarget 連結串列,並設定 mFirstTouchTarget 為 null。
        //      mFirstTouchTarget 是"接受觸控事件的 View" 所組成的單鏈表
        // (02) 清空 mGroupFlags 的 FLAG_DISALLOW_INTERCEPT 標記
        //      如果設定了 FLAG_DISALLOW_INTERCEPT ,則不允許 ViewGroup 對觸控事件進行攔截。
        // (03) 清空 mPrivateFlags 的 PFLAG_CANCEL_NEXT_UP_EVENT 標記
        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();
        }
        
        /* 第3步:檢查當前 ViewGroup 是否想要攔截觸控事件(這裡只是單純的檢查是不是要攔截) */
        // 是的話,設定 intercepted 為 true ;否則 intercepted 為 false。
        // 如果是"按下事件(ACTION_DOWN)" 或者 mFirstTouchTarget 不為 null 就執行 if 程式碼塊裡面的內容。
        // 否則的話,設定 intercepted 為 true。
        // mFirstTouchTarget != null 表示有子控制元件接收消耗了 DOWN 事件,因此當 MOVE、UP 事件來的時候能夠進入到 if 程式碼塊中
        final boolean intercepted;
        if (actionMasked == MotionEvent.ACTION_DOWN
                || mFirstTouchTarget != null) {
            // 檢查禁止攔截標記:FLAG_DISALLOW_INTERCEPT
            // 如果呼叫了 requestDisallowInterceptTouchEvent() 標記的話,則 FLAG_DISALLOW_INTERCEPT 會為 true。
            // 例如,ViewPager在處理觸控事件的時候,就會呼叫 requestDisallowInterceptTouchEvent(),禁止它的父類對觸控事件進行攔截
            final boolean disallowIntercept = (mGroupFlags & FLAG_DISALLOW_INTERCEPT) != 0;
            if (!disallowIntercept) {
                // 如果禁止攔截標記為 false 的話,則呼叫 onInterceptTouchEvent() 並返回攔截狀態。
                //如果攔截了 DOWN 則後續的 MOVE、UP 事件時都不會呼叫 onInterceptTouchEvent() 了
                //如果攔截的是 DOWN 後的 MOVE 事件,那麼 UP 事件來的時候還是會呼叫 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);
        }
        
        /* 第4步:檢查當前的觸控事件是否被取消 */
        // (01) 對於 ACTION_DOWN 而言,mPrivateFlags 的 PFLAG_CANCEL_NEXT_UP_EVENT 位肯定是 0;因此,canceled=false。
        // (02) 當前的 View/ViewGroup 要被從父View中 detach 時, PFLAG_CANCEL_NEXT_UP_EVENT 就會被設為 true;
        //      此時,它就不再接受觸控事情。
        final boolean canceled = resetCancelNextUpFlag(this)
                || actionMasked == MotionEvent.ACTION_CANCEL;
                
        /* 第5步:將觸控事件分發給"當前 ViewGroup 的 子 View/ViewGroup" */
        // 如果觸控"沒有被取消",同時也"沒有被攔截"的話,則將觸控事件分發給它的子View和子ViewGroup。
        // 如果當前 ViewGroup 的孩子能接受觸控事件的話,則將該孩子新增到 mFirstTouchTarget 連結串列中。
        // Update list of touch targets for pointer down, if needed.
        final boolean split = (mGroupFlags & FLAG_SPLIT_MOTION_EVENTS) != 0;
        TouchTarget newTouchTarget = null;
        boolean alreadyDispatchedToNewTouchTarget = false;
        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;
            //對於 DOWN、MOVE、UP三個事件,只有 DOWN 事件才有可能進入到判斷語句中,對子控制元件進行遍歷分發
            //而 MOVE、UP 則是在第6步中,直接遍歷 mFirstTouchTarget 連結串列,查詢之前接受 DOWN 事件的孩子,並將觸控事件分配給這些孩子
            if (actionMasked == MotionEvent.ACTION_DOWN
                    || (split && actionMasked == MotionEvent.ACTION_POINTER_DOWN)
                    || actionMasked == MotionEvent.ACTION_HOVER_MOVE) {
                // 這是獲取觸控事件的序號 以及 觸控事件的id資訊。
                // (01) 對於 ACTION_DOWN,actionIndex 肯定是0
                // (02) 而 getPointerId() 是獲取的該觸控事件的id,並將該id資訊儲存到 idBitsToAssign 中。
                //    這個觸控事件的 id 是為多指觸控而新增的;對於單指觸控,getActionIndex() 返回的肯定是0;
                //    而對於多指觸控,第一個手指的 id 是 0,第二個手指的 id 是1,...依次類推。
                final int actionIndex = ev.getActionIndex(); // always 0 for down
                final int idBitsToAssign = split ? 1 << ev.getPointerId(actionIndex)
                        : TouchTarget.ALL_POINTER_IDS;
                // 清空這個手指之前的 TouchTarget 連結串列。
                // Clean up earlier touch targets for this pointer id in case they
                // have become out of sync.
                removePointersFromTouchTargets(idBitsToAssign);
                // 獲取該 ViewGroup 包含的 View/ViewGroup 的數目,
                // 然後遞迴遍歷該 ViewGroup 的孩子,對觸控事件進行分發。
                // 遞迴遍歷 ViewGroup 的孩子:是指對於當前 ViewGroup 的所有孩子,都會逐個遍歷,並分發觸控事件;
                //   對於逐個遍歷到的每一個孩子,若該孩子是 ViewGroup 型別的話,則會遞迴到呼叫該孩子的孩子,...
                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;
                    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;
                        }
                        // 如果 child 可以接受觸控事件,
                        // 並且觸控座標 (x,y) 在 child 的可視範圍之內的話;
                        // 則繼續往下執行。否則,呼叫continue。
                        // child可接受觸控事件:是指child的是可見的(VISIBLE);或者雖然不可見,但是位於動畫狀態。
                        if (!canViewReceivePointerEvents(child)
                                || !isTransformedTouchPointInView(x, y, child, null)) {
                            ev.setTargetAccessibilityFocus(false);
                            continue;
                        }
                        // getTouchTarget() 的作用是查詢 child 是否存在於 mFirstTouchTarget 的單鏈表中。
                        // (如果是後來有為 ViewGroup 新新增子 View/ViewGroup,則有可能還沒有存在於 mFirstTouchTarget 的單鏈表中,此時就會達到  __標記_1,且如果符合條件就會被新加進 mFirstTouchTarget 的單鏈表中)
                        // 是的話,返回對應的 TouchTarget 物件(此時就會跳出迴圈,因為已經找到可以接收 DOWN 事件的子 View/ViewGroup);否則,返回 null。
                        newTouchTarget = getTouchTarget(child);// newTouchTarget 第一次被賦值
                        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;
                            //馬上跳出迴圈,會在第 6 步將事件進一步分發給 mFirstTouchTarget 中的子 View/Group
                            break;
                        }
                        // 重置 child的mPrivateFlags 變數中的 PFLAG_CANCEL_NEXT_UP_EVENT 位。
                        resetCancelNextUpFlag(child);
                        // 呼叫 dispatchTransformedTouchEvent() 將觸控事件分發給child。 __標記_1
                        if (dispatchTransformedTouchEvent(ev, false, child, idBitsToAssign)) {
                            // 如果 child 能夠接受該觸控事件,即 child 消費或者攔截了該觸控事件的話;
                            // 則呼叫 addTouchTarget() 將 child 新增到 mFirstTouchTarget 連結串列的表頭,並返回表頭對應的 TouchTarget
                            // 同時還設定 alreadyDispatchedToNewTouchTarget 為 true。
                            // 然後跳出迴圈
                            // 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);// newTouchTarget 第二次被賦值
                            alreadyDispatchedToNewTouchTarget = true;
                            //此時會馬上跳出遍歷孩子的迴圈,之後即使還有能夠接收 DOWN 事件的子 View/ViewGroup 也不會管了 
                            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();
                }
                // 在 for 迴圈外
                // 如果 newTouchTarget 為 null(即newTouchTarget第一次被賦值時為null且沒有經歷第二次賦值),並且 mFirstTouchTarget 不為 null;
                // 則設定 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;
                }
            }
        }
        
        /* 第6步:進一步的對觸控事件進行分發 */
        // (01) 如果 mFirstTouchTarget 為 null,意味著還沒有任何View來接受該觸控事件;
        //   此時,將當前 ViewGroup 看作一個 View;
        //   將會呼叫"當前的 ViewGroup 的父類 View 的 dispatchTouchEvent() "對觸控事件進行分發處理。
        // (02) 如果mFirstTouchTarget 不為 null,意味著 ViewGroup 的子 View/ViewGroup 中
        //   有可以接受觸控事件的。那麼,就將觸控事件分發給這些可以接受觸控事件的子 View/ViewGroup。
        if (mFirstTouchTarget == null) {
            // 注意:這裡的第3個引數是 null
            handled = dispatchTransformedTouchEvent(ev, canceled, null,
                    TouchTarget.ALL_POINTER_IDS);
        } else {
            //只要 mFirstTouchTarget 不 null,就一定會經過這一步,但是也會根據第 5 步的執行的結果來決定之後邏輯(因為第 5 步中只 break、continue 掉 for 迴圈,並沒有直接 return )
            // 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 {
                    //(1) 當 cancelChild == true 此時在 dispatchTransformedTouchEvent() 方法內部會給 child 分發 ACTION_CANCEL 事件
                    final boolean cancelChild = resetCancelNextUpFlag(target.child)
                            || intercepted;
                    if (dispatchTransformedTouchEvent(ev, cancelChild,
                            target.child, target.pointerIdBits)) {
                        handled = true;
                    }
                    //(2) 當 cancelChild 為 true 時,就會把子 View/ViewGroup 從原本的 mFirstTouchTarget 的單鏈表中剔除掉,
                    //	  所以之後該子 View/ViewGroup 就無法再接收後續事件了
                    if (cancelChild) {
                        if (predecessor == null) {
                            mFirstTouchTarget = next;
                        } else {
                            predecessor.next = next;
                        }
                        target.recycle();
                        target = next;
                        continue;
                    }
                    
					//因此,整個迴圈下來,會把 mFirstTouchTarget 清空
                }
                predecessor = target;
                target = next;
            }
        }
        
        /* 第7步:再次檢查取消標記,並進行相應的處理 */
        // 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);
        }
    }
    
    // mInputEventConsistencyVerifier是除錯用的,不會理會
    if (!handled && mInputEventConsistencyVerifier != null) {
        mInputEventConsistencyVerifier.onUnhandledEvent(ev, 1);
    }
    return handled;
}

  • ViewGroup 的 dispatchTransformedTouchEvent(MotionEvent ev) 原始碼
private boolean dispatchTransformedTouchEvent(MotionEvent event, boolean cancel,
        View child, int desiredPointerIdBits) {
    final boolean handled;
    
    // 檢測是否需要傳送 ACTION_CANCEL。(這裡不是針對 DOWN、MOVE、UP 事件進行分發)
    // 如果 cancel 為 true 或者 action 是 ACTION_CANCEL;      // 則設定訊息為 ACTION_CANCEL,並將 ACTION_CANCEL 訊息分發給對應的物件,並返回。
    // (01) 如果 child 是空,則將 ACTION_CANCEL 訊息分發給當前 ViewGroup;
    //      只不過會將 ViewGroup 看作它的父類 View,呼叫 View 的 dispatchTouchEvent() 介面。
    // (02) 如果 child 不是空,呼叫 child的dispatchTouchEvent()。
    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;
    }
    
    // 計算觸控事件的id資訊
    final int oldPointerIdBits = event.getPointerIdBits();
    final int newPointerIdBits = oldPointerIdBits & desiredPointerIdBits;
    
    // 如果新的id資訊為0,則返回false。
    if (newPointerIdBits == 0) {
        return false;
    }

	// 如果計算得到的前後觸控事件id資訊相同,則執行不需要重新計算MotionEvent,直接執行if語句塊進行消費分發;
    // 否則,就重新計算MotionEvent之後,再進行訊息分發。
    final MotionEvent transformedEvent;
    if (newPointerIdBits == oldPointerIdBits) {
        if (child == null || child.hasIdentityMatrix()) {
            // 這裡才是正常的對 DOWN、MOVE、UP 事件進行分發
            // (01) 如果 child 是空,則將 ViewGroup 看作它的父類 View,呼叫 View 的 dispatchTouchEvent() 介面。
            // (02) 如果 child 不是空,呼叫 child 的 dispatchTouchEvent()。
            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);
    }
    
    // 這裡也是正常的對 DOWN、MOVE、UP 事件進行分發
    // (01) 如果 child 是空,則將 ViewGroup 看作它的父類 View,呼叫 View 的 dispatchTouchEvent() 介面。
    // (02) 如果 child 不是空,呼叫 child的dispatchTouchEvent()。
    if (child == null) {
        handled = super.dispatchTouchEvent(transformedEvent);
    } else {
        final float offsetX = mScrollX - chil