序言
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】位置執行的周遊邏輯可以拆分成四個步驟:
- 周遊子 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);
}
複制代碼
- 檢查事件的坐标是否處于子 View 的範圍内,如果不在,直接進行下一次循環;或是子 View 能否接受事件。
// 【坐标點過濾】觸摸點不在子 View 範圍内 or 子 View 不接收事件,直接進行下一次循環if (!child.canReceivePointerEvents() || !isTransformedTouchPointInView(x, y, child, null)) {
ev.setTargetAccessibilityFocus(false);
continue;
}
複制代碼
- 已存在觸摸目标,更新觸摸目标的指針,跳出循環。
newTouchTarget = getTouchTarget(child);
if (newTouchTarget != null) {
newTouchTarget.pointerIdBits |= idBitsToAssign;
break;
}
複制代碼
- 執行到這一步說明坐标檢查通過,且沒有已存在觸摸目标,通過 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
}