天天看點

最詳細的 Android View 的事件分發原

序言

Android 的 View 的事件分發一直是老生常談的問題,市面上的所有文章資料都在通過 dispatchTouchEvent、onInterceptTouchEvent 和 onTouchEvent 講事件分發的原理,對于一些細節沒有涉及,本文帶你一窺源碼,搞清楚 View 是如何選擇事件處理的。

MotionEvent

MotionEvent 定義了用于報告移動(滑鼠、筆、手指、軌迹球)事件的對象。運動事件可以儲存絕對或相對運動和其他資料,這取決于裝置的類型。

使用者觸摸一次螢幕将會産生 MotionEvent 事件,并将事件傳遞給合适的 View 。MotionEvent 會包含一些觸摸行為的相關資訊,例如事件的類型、觸摸的X和Y坐标、接觸區域的大小和方向的資訊等等。有些裝置可以同時報告多條移動軌迹。多點觸控螢幕為每個手指發出一個運動軌迹。單個的手指或其他産生運動軌迹的對象稱為指針。

通常情況下,使用者的觸摸事件類型包括:按下(ACTION_DOWN)、移動(ACTION_MOVE)、擡起(ACTION_UP)、取消(ACTION_CANCEL)。

使用者的一次觸摸通常以 ACTION_DOWN 事件開始,在螢幕上移動時産生多個 ACTION_MOVE 事件,最後擡起手指觸發 ACTION_UP 或 ACTION_CANCEL 事件。

View 和 ViewGroup

在講解事件分發機制之前,要區分 View 和 ViewGroup ,盡管 ViewGroup 繼承自 View ,但是它們代表了不容的概念。Android 的視圖結構是樹結構,View 代表的是葉子節點,而 ViewGroup 則表示可以擁有子節點的非葉子節點。它們的事件分發邏輯是有差別的。

簡述事件分發

下文的分析可能比較複雜,這裡總結一下事件分發涉及核心方法的流程:

View 的事件分發流程

當觸摸事件來臨時,會觸發 View 的 dispatchTouchEvent(event) 将事件進行分發。如果 View 設定了 onTouchListener ,會優先回調 onTouch(view, event) ,onTouch 方法的傳回值表示是否處理事件。

如果 onTouch 不處理事件,會進入 View 自己的 onTouchEvent ,在沒有覆寫的情況下,該方法内部會識别該事件是點選、長按或其他類型,點選會調用 performClick() ,perfromClick 方法會調用 onClickListener 的 onClick(View) 。

onTouchEvent 方法使用者可以自己來實作,進而實作 View 的拖拽等能力。

如果 onTouchEvent 方法也傳回 false ,說明目前 View 不消耗這個事件,dispatchTouchEvent 傳回 false 。

ViewGroup 的事件分發流程

當觸摸事件來臨時,會觸發 ViewGroup 的 dispatchTouchEvent 将事件進行分發。

ViewGroup 會優先處理事件的分發邏輯,經過視窗的檢查後,首先會通過通過 Flag 檢查是否需要攔截,并調用 onInterceptTouchEvent(event) 确定真正是否進行攔截的結果。然後進行取消的 Flag 檢查。

在不攔截不取消的情況下,通過 View 的渲染順序和 Z 軸順序會生成一個有序的 View 清單,根據清單順序擷取符合事件坐标位置的 View ,然後通過 dispatchTransformedTouchEvent(view, ...) 來分發給這個 View ,dispatchTransformedTouchEvent 方法内部調用 view.dispatchTouchEvent(event) ,如果 view 為空的情況,直接調用 super.dispatchTouchEvent(event) ,也就是調用 ViewGroup 的父類 View 的 dispatchTouchEvent 方法。

如果子 View 不消耗事件(dispatchTouchEvent 方法傳回了 false),ViewGroup 會調用 dispatchTransformedTouchEvent(view = null, ...) 的形式,調用到自己的 super.dispatchTouchEvent(event) ,也就是将自己視為最後 View 來檢查是否消耗。

View dispatchTouchEvent

View 的 dispatchTouchEvent 方法會将觸摸屏運動事件傳遞到目标視圖,如果是目标,則傳遞給這個視圖。并傳回目前 View 是否要處理這個事件的結果(需要處理 true,反之 false)。接下來逐漸分析 dispatchTouchEvent 方法中關鍵的邏輯:

1. 檢查事件序列邏輯自洽

protectedfinalInputEventConsistencyVerifiermInputEventConsistencyVerifier= InputEventConsistencyVerifier.isInstrumentationEnabled() ? newInputEventConsistencyVerifier(this, 0) : null;

publicbooleandispatchTouchEvent(MotionEvent event) {
    // ...if (mInputEventConsistencyVerifier != null) {
        mInputEventConsistencyVerifier.onTouchEvent(event, 0);
    }
    // ...
}
複制代碼
           

mInputEventConsistencyVerifier 是用于調試目的的一個類,用來檢查事件序列是否邏輯自洽,例如連續兩個 down 事件就不符合同一個事件序列的邏輯規則。

2. ACTION_DOWN 觸發新手勢防禦性清理

publicbooleandispatchTouchEvent(MotionEvent event) {
    // 1. 檢查事件序列邏輯自洽booleanresult=false;

    // action finalintactionMasked= event.getActionMasked();
    if (actionMasked == MotionEvent.ACTION_DOWN) {
        // 為新手勢進行防禦性清理, 停止嵌套滑動
        stopNestedScroll();
    }
    // ...
}
複制代碼
           

在這一個步驟前,先定義了一個變量 result ,用來記錄内部邏輯是否處理了這個事件,并在最後傳回結果。後續的步驟也會涉及這個變量。經過事件序列檢查後,檢查是否是 Down 事件,如果是,則需要為新手勢進行防禦性清理, 停止嵌套滑動。

3. 視窗遮擋政策檢查

publicbooleandispatchTouchEvent(MotionEvent event) {
    // 1. 檢查事件序列邏輯自洽// 2. ACTION_DOWN 觸發新手勢防禦性清理if (onFilterTouchEventForSecurity(event)) {
        // ...
    }
    // ...
}
複制代碼
           

在經過 Down 事件的處理後,會通過 onFilterTouchEventForSecurity 方法過濾事件,這個方法的作用是檢查視窗是否被遮擋。當然使用者也可以通過覆寫 onFilterTouchEventForSecurity 的方式,自定義過濾政策。

4. 處理事件

publicbooleandispatchTouchEvent(MotionEvent event) {
    // 1. 檢查事件序列邏輯自洽// 2. ACTION_DOWN 觸發新手勢防禦性清理if (onFilterTouchEventForSecurity(event)) { // 3. 視窗遮擋政策檢查ListenerInfoli= mListenerInfo; // 取出目前 View 的監聽資訊if (li != null && li.mOnTouchListener != null// mOnTouchListener 不為空
            && (mViewFlags & ENABLED_MASK) == ENABLED // 掩碼,與setFlags一起使用,表示該視圖是否啟用的比特位  
            && li.mOnTouchListener.onTouch(this, event)  // onTouch 方法處理事件 在向視圖發送觸摸事件時調用。這讓 listener 有機會在目标視圖之前做出響應。
        ) {
            result = true;
        }
        // 如果這個時候result 還不是 true,調用 onTouchEvent// 實作 onTouchEvent 方法來處理觸摸屏運動事件。如果使用此方法檢測單擊行為,建議通過實作并調用performClick()來執行這些操作。// 這将確定系統行為的一緻性,包括: 1.服從點選聲音偏好設定; 2. 排程OnClickListener調用; 3. 當無障礙功能啟用時處理ACTION_CLICKif (!result && onTouchEvent(event)) {
            result = true;
        }
    }
    // ...
}
複制代碼
           

經過了視窗遮擋檢查,就開始處理事件了,這裡省略了滑鼠拖拽滾動條的邏輯(不是很重要)。首先,取出 mListenerInfo 屬性儲存的監聽資訊,檢查它的 mOnTouchListener 變量不為空、并且目前 View 是否處于啟用狀态(Flag == ENABLED),并且 mOnTouchListener 的 onTouch 方法傳回了 true ,說明目前 View 的 onTouch 處理了事件,事件到此為止,分發結束。

onTouch 優先調用的作用是在向 View 發送觸摸事件時調用。這讓 mOnTouchListener 有機會在交給目标視圖處理之前做出響應。

如果 onTouch 方法傳回了 false ,沒有消耗事件,那麼将調用目前 View 的 onTouchEvent 方法來處理事件。

onTouchEvent(MotionEvent) 是 View 用來開始處理觸摸運動事件的方法,如果使用此方法檢測單擊行為,建議通過實作并調用performClick()來執行這些操作。這樣能夠保證一些系統行為的一緻性,包括:

  • 調用 onClickListener
  • 無障礙處理 ACTION_CLICK 事件
  • 觸發系統點選音效

View 的 onTouchEvent 方法中處理了單擊、長按、UI 的按壓狀态等邏輯,内部通過調用 performClickInternal() 來觸發點選事件。

privatebooleanperformClickInternal() {
        // Must notify autofill manager before performing the click actions to avoid scenarios where// the app has a click listener that changes the state of views the autofill service might// be interested on.
        notifyAutofillManagerOnClick();

        return performClick();
    }
複制代碼
           

5. 通知事件驗證器忽略次事件序列

如果執行到這一步此還沒有處理事件,說明目前 View 不消耗這個事件序列,通知 mInputEventConsistencyVerifier 給定事件未處理,應該忽略事件跟蹤的其餘部分。

publicbooleandispatchTouchEvent(MotionEvent event) {
    // 1. 檢查事件序列邏輯自洽// 2. ACTION_DOWN 觸發新手勢防禦性清理if (onFilterTouchEventForSecurity(event)) { // 3. 視窗遮擋政策檢查// 4. 處理事件
    }
    // 到這裡仍不處理,調試器不為空,交給調試器記錄不處理的事件if (!result && mInputEventConsistencyVerifier != null) {
        mInputEventConsistencyVerifier.onUnhandledEvent(event, 0);
    }
    // ...
}
複制代碼
           

6. 結束手勢到清理工作

如果這個手勢是一個結束手勢(包括 UP 事件、CANCEL 事件、或是 DOWN 事件時,目前 View 不處理事件的情況),停止嵌套滾動。

publicbooleandispatchTouchEvent(MotionEvent event) {
    // 1. 檢查事件序列邏輯自洽// 2. ACTION_DOWN 觸發新手勢防禦性清理if (onFilterTouchEventForSecurity(event)) { // 3. 視窗遮擋政策檢查// 4. 處理事件
    }
    // 5. 通知事件驗證器忽略次事件序列if (actionMasked == MotionEvent.ACTION_UP ||
            actionMasked == MotionEvent.ACTION_CANCEL ||
            (actionMasked == MotionEvent.ACTION_DOWN && !result)) {
        stopNestedScroll();
    }
    return result;
}
複制代碼
           

到這一步,View 的事件分發就結束了。

ViewGroup dispatchTouchEvent

ViewGroup 的 dispatchTouchEvent 邏輯與 View 有所不同。

1. 檢查事件序列邏輯自洽

@OverridepublicbooleandispatchTouchEvent(MotionEvent ev) {
    // 檢查事件序列邏輯自洽if (mInputEventConsistencyVerifier != null) {
        mInputEventConsistencyVerifier.onTouchEvent(ev, 1);
    }
}
複制代碼
           

與 View 相同。

2. 視窗遮擋政策檢查

ViewGroup 的第二步直接進行了過濾政策方法的調用,不存在滾動的能力,是以忽略了 View 的第二步和最後停止嵌套滾動的邏輯。

publicbooleandispatchTouchEvent(MotionEvent event) {
    // 1. 檢查事件序列邏輯自洽booleanhandled=false;

    if (onFilterTouchEventForSecurity(event)) {
        // ...
    }
    // ...
}
複制代碼
           

3. 最初的 DOWN 事件觸發清理邏輯

publicbooleandispatchTouchEvent(MotionEvent event) {
    // 1. 檢查事件序列邏輯自洽booleanhandled=false;

    if (onFilterTouchEventForSecurity(event)) { // 2. 視窗遮擋政策檢查finalintaction= ev.getAction();
        finalintactionMasked= action & MotionEvent.ACTION_MASK;

        // Handle an initial down. 處理第一個 down 事件if (actionMasked == MotionEvent.ACTION_DOWN) {
            // 當開始一個新的觸摸手勢時,抛棄之前所有的狀态。// 由于應用程式切換、ANR或其他一些狀态變化,架構可能已經放棄了前一個手勢的up或cancel事件。
            cancelAndClearTouchTargets(ev); // 取消并清除
            resetTouchState();
        }
    }
    // ...
}
複制代碼
           

經過篩選後,如果事件是 DOWN 事件,需要抛棄之前的狀态,這裡也可能由于應用程式切換、ANR或其他一些狀态變化,架構可能已經放棄了前一個手勢的 UP 或 CANCEL 事件。

4. 攔截檢查

publicbooleandispatchTouchEvent(MotionEvent event) {
    // 1. 檢查事件序列邏輯自洽booleanhandled=false;

    if (onFilterTouchEventForSecurity(event)) { // 2. 視窗遮擋政策檢查// 3. 最初的 DOWN 事件觸發清理邏輯finalboolean intercepted;
        if (actionMasked == MotionEvent.ACTION_DOWN || mFirstTouchTarget != null) {
            finalbooleandisallowIntercept= (mGroupFlags & FLAG_DISALLOW_INTERCEPT) != 0; 
            if (!disallowIntercept) { // 如果允許攔截,走 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; // 首個事件沒有觸摸目标,這個動作不是一個初始的down是以這個視圖組繼續攔截觸摸。
        }
    }
    // ...
}
複制代碼
           

檢查攔截的第一步是判斷如果是 DOWN 事件或目前第一次點選目标不為空,表示不是第一個事件,此時會檢查是否允許攔截,如果允許,調用 onInterceptTouchEvent(MotionEvent) ,根據 onInterceptTouchEvent 方法傳回的結果表示是否攔截,設定到臨時變量 intercepted 上;其他情況表示不允許攔截事件。

5. 擷取事件取消等狀态

publicbooleandispatchTouchEvent(MotionEvent event) {
    // 1. 檢查事件序列邏輯自洽booleanhandled=false;

    if (onFilterTouchEventForSecurity(event)) { // 2. 視窗遮擋政策檢查// 3. 最初的 DOWN 事件觸發清理邏輯// 4. 攔截檢查 // Check for cancelation. 檢查是否取消finalbooleancanceled= resetCancelNextUpFlag(this) || actionMasked == MotionEvent.ACTION_CANCEL;

        // 是否來自滑鼠的事件finalbooleanisMouseEvent= ev.getSource() == InputDevice.SOURCE_MOUSE;
        // FLAG_SPLIT_MOTION_EVENTS 當設定這個屬性時,這個ViewGroup會在合适的時候将 MotionEvents 拆分給多個子視圖。finalbooleansplit= (mGroupFlags & FLAG_SPLIT_MOTION_EVENTS) != 0 && !isMouseEvent; 
        // 新的觸摸目标TouchTargetnewTouchTarget=null; 
        // 已分發給新的觸摸目标booleanalreadyDispatchedToNewTouchTarget=false;

    }
    // ...
}
複制代碼
           

在這一個步驟中,首先事件查詢是否被取消,然後定義了一些後續邏輯中會用到的臨時變量。

6. 分發 DOWN 事件

publicbooleandispatchTouchEvent(MotionEvent event) {
    // 1. 檢查事件序列邏輯自洽booleanhandled=false;

    if (onFilterTouchEventForSecurity(event)) { // 2. 視窗遮擋政策檢查// 3. 最初的 DOWN 事件觸發清理邏輯// 4. 攔截檢查 // 5. 擷取事件取消等狀态// 在確定沒有被取消或攔截的情況下,向下分發事件 (這裡忽略一些滑鼠等輸入裝置和無障礙相關的邏輯)if (!canceled && !intercepted) {
            if (actionMasked == MotionEvent.ACTION_DOWN) {  
                finalintactionIndex= ev.getActionIndex(); // always 0 for downfinalintidBitsToAssign= split ? 1 << ev.getPointerId(actionIndex) : TouchTarget.ALL_POINTER_IDS;
                finalintchildrenCount= mChildrenCount; // 擷取子 View 數量if (newTouchTarget == null && childrenCount != 0) { // 存在觸摸目标,且存在子 View // TODO 
                }
            }
        }
    }
    // ...
}
複制代碼
           

在確定沒有被取消或攔截的情況下,準備開始向子 View 分發事件,這裡忽略一些滑鼠等輸入裝置和無障礙相關的邏輯。

1. 擷取有序的子 View 清單
if (!canceled && !intercepted) {
    if (actionMasked == MotionEvent.ACTION_DOWN) {  
        // ...if (newTouchTarget == null && childrenCount != 0) { // 存在觸摸目标,且存在子 View // 目前坐标 / 目前事件的坐标finalfloatx= isMouseEvent ? ev.getXCursorPosition() : ev.getX(actionIndex);
            finalfloaty= isMouseEvent ? ev.getYCursorPosition() : ev.getY(actionIndex);
            final ArrayList<View> preorderedList = buildTouchDispatchChildList();
            // isChildrenDrawingOrderEnabled 如果子 View 的繪制順序由getChildDrawingOrder(int, int)定義,傳回true,否則傳回falsefinalbooleancustomOrder= preorderedList == null && isChildrenDrawingOrderEnabled();

            // ... 
        }
    }
    // ... 
}
複制代碼
           

這一步主要是 buildTouchDispatchChildList 方法,它提供了按照視圖顯示層級順序的 View 清單。(先按Z排序,然後按子元素的繪制順序(如果适用)排序。);該清單必須在使用後清除,以避免洩漏子視圖。

2. 周遊子 View
if (!canceled && !intercepted) {
    if (actionMasked == MotionEvent.ACTION_DOWN) {  
        // ...if (newTouchTarget == null && childrenCount != 0) { // 存在觸摸目标,且存在子 View // 1. 擷取有序的子 View 清單final View[] children = mChildren;
            // 開始周遊for (inti= childrenCount - 1; i >= 0; i--) {
                // 【todo】這裡周遊邏輯
            }

            // ... 
        }
    }
    // ... 
}
複制代碼
           

【todo】位置執行的周遊邏輯可以拆分成四個步驟:

  1. 周遊子 View 的邏輯比較複雜,首先通過 getAndVerifyPreorderedIndex 方法擷取子 View 在已根據規則排好順序的 preorderedList 中的索引,然後再去 preorderedList 中找到這個 View。
final View[] children = mChildren;

for (inti= childrenCount - 1; i >= 0; i--) {
    finalintchildIndex= getAndVerifyPreorderedIndex(childrenCount, i, customOrder);
    finalViewchild= getAndVerifyPreorderedView(preorderedList, children, childIndex);
}
複制代碼
           
  1. 檢查事件的坐标是否處于子 View 的範圍内,如果不在,直接進行下一次循環;或是子 View 能否接受事件。
// 【坐标點過濾】觸摸點不在子 View 範圍内 or 子 View 不接收事件,直接進行下一次循環if (!child.canReceivePointerEvents() || !isTransformedTouchPointInView(x, y, child, null)) {
    ev.setTargetAccessibilityFocus(false);
    continue;
}
複制代碼
           
  1. 已存在觸摸目标,更新觸摸目标的指針,跳出循環。
newTouchTarget = getTouchTarget(child);
if (newTouchTarget != null) {
    newTouchTarget.pointerIdBits |= idBitsToAssign;
    break;
}
複制代碼
           
  1. 執行到這一步說明坐标檢查通過,且沒有已存在觸摸目标,通過 dispatchTransformedTouchEvent 分發事件給子 View ,如果分發成功了,會更新最後一次的觸摸目标資訊。
if (dispatchTransformedTouchEvent(ev, false, child, idBitsToAssign)) {
    mLastTouchDownTime = ev.getDownTime();
    if (preorderedList != null) {
        // childIndex points into presorted list, find original indexfor (intj=0; j < childrenCount; j++) {
            if (children[childIndex] == mChildren[j]) {
                mLastTouchDownIndex = j;
                break;
            }
        }
    } else {
        mLastTouchDownIndex = childIndex;
    }
    mLastTouchDownX = ev.getX();
    mLastTouchDownY = ev.getY();
    // addTouchTarget 将指定子對象的觸摸目标添加到 TouchTarget 清單的頭。假設目标子元素還不存在。
    newTouchTarget = addTouchTarget(child, idBitsToAssign);
    alreadyDispatchedToNewTouchTarget = true;
    break;
}
複制代碼
           
3. 清除有序的子 View 清單

前面提到了 buildTouchDispatchChildList 方法傳回了 View 清單要在使用後清除,避免造成洩漏:

if (!canceled && !intercepted) {
    if (actionMasked == MotionEvent.ACTION_DOWN) {  
        // ...if (newTouchTarget == null && childrenCount != 0) { // 存在觸摸目标,且存在子 View // 1. 擷取有序的子 View 清單// 2. 周遊子 Viewif (preorderedList != null) preorderedList.clear();
            
        }
    }
    // ... 
}
複制代碼
           
4. 沒有找到子 View 處理事件,将觸摸目标更新成最近添加的觸摸目标
if (!canceled && !intercepted) {
    if (actionMasked == MotionEvent.ACTION_DOWN) {  
        // ...if (newTouchTarget == null && childrenCount != 0) { // 存在觸摸目标,且存在子 View // 1. 擷取有序的子 View 清單// 2. 周遊子 View// 3. 清除有序的子 View 清單
        }

        if (newTouchTarget == null && mFirstTouchTarget != null) {
            newTouchTarget = mFirstTouchTarget;
            while (newTouchTarget.next != null) {
                newTouchTarget = newTouchTarget.next;
            }
            newTouchTarget.pointerIdBits |= idBitsToAssign;
        }
    }
    // ... 
}
複制代碼
           

這一步,也就是說,沒有找到子 View 可以處理事件,将事件交給最近添加的觸摸目标。

7. 分發非 DOWN 事件

這個步驟主要是處理非 DOWN 事件的(除了事件序列的起始事件),因為起始事件回去建立新的觸摸目标,而後續的事件可以直接交給 TouchTarget 來進行,避免再次查找提高效率。

publicbooleandispatchTouchEvent(MotionEvent event) {
    // 1. 檢查事件序列邏輯自洽booleanhandled=false;

    if (onFilterTouchEventForSecurity(event)) { // 2. 視窗遮擋政策檢查// 3. 最初的 DOWN 事件觸發清理邏輯// 4. 攔截檢查 // 5. 擷取事件取消等狀态// 6. 處理 DOWN 事件的分發// 處理非 DOWN 事件if (mFirstTouchTarget == null) {
            handled = dispatchTransformedTouchEvent(ev, canceled, null, TouchTarget.ALL_POINTER_IDS);
        } else {
            // 如果在事件序列的執行過程中,觸摸點滑出目前的觸摸目标,需要取消目前的觸摸目标TouchTargetpredecessor=null;
            TouchTargettarget= mFirstTouchTarget;
            while (target != null) {
                finalTouchTargetnext= target.next;
                if (alreadyDispatchedToNewTouchTarget && target == newTouchTarget) {
                    handled = true;
                } else {
                    finalbooleancancelChild= 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;
            }
        }
    }
    // ...
}
複制代碼
           

8. 處理 UP 事件或取消

publicbooleandispatchTouchEvent(MotionEvent event) {
    // 1. 檢查事件序列邏輯自洽booleanhandled=false;

    if (onFilterTouchEventForSecurity(event)) { // 2. 視窗遮擋政策檢查// 3. 最初的 DOWN 事件觸發清理邏輯// 4. 攔截檢查 // 5. 擷取事件取消等狀态// 6. 處理 DOWN 事件的分發// 7. 分發非 DOWN 事件if (canceled
                || actionMasked == MotionEvent.ACTION_UP
                || actionMasked == MotionEvent.ACTION_HOVER_MOVE) {
            resetTouchState();
        }
    }
    // ...
}
複制代碼
           

如果取消了或者事件是 ACTION_UP 、ACTION_HOVER_MOVE ,重置觸摸狀态。

9. 通知事件驗證器忽略次事件序列

publicbooleandispatchTouchEvent(MotionEvent event) {
    // 1. 檢查事件序列邏輯自洽booleanhandled=false;

    if (onFilterTouchEventForSecurity(event)) { // 2. 視窗遮擋政策檢查// 3. 最初的 DOWN 事件觸發清理邏輯// 4. 攔截檢查 // 5. 擷取事件取消等狀态// 6. 處理 DOWN 事件的分發// 7. 分發非 DOWN 事件// 8. 處理 UP 事件或取消
    }

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

最後一步如果沒有通過過濾政策檢查,通知 mInputEventConsistencyVerifier 忽略這些事件。

ViewGroup 分發邏輯 dispatchTransformedTouchEvent

在 ViewGroup 的分發邏輯中,傳遞給子 View 進行分發的方法是 dispatchTransformedTouchEvent(...),該方法将 MotionEvent 轉換到特定子 View 的坐标空間,過濾不相關的指針id,并在必要時覆寫其操作。如果 child 為 null,則假定 MotionEvent 将被發送到此 ViewGroup 。

privatebooleandispatchTransformedTouchEvent(MotionEvent event, boolean cancel,
        View child, int desiredPointerIdBits) {
    // ...if (child == null) {
        handled = super.dispatchTouchEvent(transformedEvent);
    } else {
        handled = child.dispatchTouchEvent(transformedEvent);
    }
    // ... return handled
}
           

繼續閱讀