Android事件分發機制全面解析
事件分發機制
事件分發機制的兩個階段:
- 分發:事件從父視圖往子視圖分發,被攔截後不再傳遞,進入回溯階段
- 回溯:事件從子視圖往父視圖回溯,被消費後不再回溯
關鍵方法:
- ViewGroup.dispatchTouchEvent 往子視圖分發事件
- ViewGroup.onInterceptTouchEvent 返回 true 表示攔截分發事件,不再傳遞,進入當前視圖 onTouchEvent
- View.dispatchTouchEvent 默認事件分發,調用 onTouchEvent
- View.onTouchEvent 通常重載此方法處理事件,返回 true 表示消費事件,不再傳遞,返回 false 往上回溯
- ViewParent.requestDisallowInterceptTouchEvent(true) 可以確保事件分發到子視圖前不被攔截
假設視圖層次為 A.B.C.D,事件分發回溯默認過程為:
A.dispatchTouchEvent B.dispatchTouchEvent C.dispatchTouchEvent D.dispatchTouchEvent D.onTouchEvent C.onTouchEvent B.onTouchEvent A.onTouchEvent
假設 B 攔截瞭事件:
A.dispatchTouchEvent B.dispatchTouchEvent -> B.onInterceptTouchEvent B.onTouchEvent A.onTouchEvent
假設 C.onTouchEvent 消費瞭事件:
A.dispatchTouchEvent B.dispatchTouchEvent C.dispatchTouchEvent D.dispatchTouchEvent D.onTouchEvent C.onTouchEvent
事件分發機制偽代碼:
class Activity { fun dispatchTouchEvent(ev) { if (parent.dispatchTouchEvent(ev)) { return true } return onTouchEvent(ev) } fun onTouchEvent(ev):Boolean {...} } class ViewGroup : View { fun dispatchTouchEvent(ev) { var handled = false if (!onInterceptTouchEvent(ev)) { handled = child.dispatchTouchEvent(ev) } return handled || super.dispatchTouchEvent(ev) } fun onInterceptTouchEvent(ev):Boolean {...} fun onTouchEvent(ev):Boolean {...} } class View { fun dispatchTouchEvent(ev) { var result = false if (handleScrollBarDragging(ev)) { result = true } if (!result && mOnTouchListener.onTouch(ev)) { result = true } if (!result && onTouchEvent(ev)) { result = true } return result } fun onTouchEvent(ev):Boolean {...} }
ViewGroup.dispatchTouchEvent 源碼分析
1.開始:ACTION_DOWN 事件開始一個新的事件序列,清除之前觸摸狀態
2.攔截:
2.1. 非 ACTION_DOWN 事件如果當前沒有子視圖消費事件,表示事件序列已被攔截
2.2. 事件未被攔截且子視圖未申請禁止攔截時,再通過 onInterceptTouchEvent 嘗試攔截事件
3.分發:如果事件未被攔截也未被取消,就遍歷子視圖分發事件,並尋找當前事件的觸摸目標
3.1. 在觸摸目標鏈表中找到瞭可以消費當前事件的視圖觸摸目標 -> 將其標記為當前觸摸目標,延遲到步驟4分發事件給它
3.2. 一個不在觸摸目標鏈表中的視圖消費瞭事件 -> 將其標記為當前觸摸目標,並設置為觸摸目標鏈表表頭
3.3. 未找到消費當前事件的視圖,但觸摸目標鏈表不為空 -> 將觸摸目標鏈表末端標記為當前觸摸目標
4.分發:觸摸目標鏈表不為空,則遍歷觸摸目標鏈嘗試傳遞事件或取消觸摸目標(事件被攔截)
5.回溯:觸摸目標鏈表為空(當前沒有子視圖消耗事件序列),則將事件轉發給基類 dispatchTouchEvent 處理
註:觸摸目標(ViewGourp.TouchTarget) 描述一個被觸摸的子視圖和它捕獲的指針ids
public boolean dispatchTouchEvent(MotionEvent ev) { // 省略代碼 ... boolean handled = false; if (onFilterTouchEventForSecurity(ev)) { if (actionMasked == MotionEvent.ACTION_DOWN) { // 1. `ACTION_DOWN` 事件開始一個新的事件序列,清除之前觸摸狀態 ... } // 省略代碼 ... final boolean intercepted; // 2. 攔截 if (actionMasked == MotionEvent.ACTION_DOWN || mFirstTouchTarget != null) { final boolean disallowIntercept = (mGroupFlags & FLAG_DISALLOW_INTERCEPT) != 0; if (!disallowIntercept) { // 2.2. 事件未被攔截且子視圖未申請禁止攔截時,再通過 onInterceptTouchEvent 嘗試攔截事件 intercepted = onInterceptTouchEvent(ev); // 省略代碼 ... } else { intercepted = false; } } else { // 2.1. 非 `ACTION_DOWN` 事件如果當前沒有子視圖消費事件,表示事件序列已被攔截 intercepted = true; } // 省略代碼 ... if (!canceled && !intercepted) { // 省略代碼 ... // 3. 分發:如果事件未被攔截也未被取消,就遍歷子視圖分發事件,並尋找當前事件的觸摸目標 for (int i = childrenCount - 1; i >= 0; i--) { // 省略代碼 ... newTouchTarget = getTouchTarget(child); if (newTouchTarget != null) { // 3.1. 在觸摸目標鏈表中找到瞭可以消費當前事件的視圖觸摸目標 -> 將其標記為當前觸摸目標,延遲到步驟4分發事件給它 // 省略代碼 ... break; } if (dispatchTransformedTouchEvent(ev, false, child, idBitsToAssign)) { // 省略代碼 ... // 3.2. 一個不在觸摸目標鏈表中的視圖消費瞭事件 -> 將其標記為當前觸摸目標,並設置為觸摸目標鏈表表頭 newTouchTarget = addTouchTarget(child, idBitsToAssign); alreadyDispatchedToNewTouchTarget = true; break; } // 省略代碼 ... } if (newTouchTarget == null && mFirstTouchTarget != null) { // 3.3. 未找到消費當前事件的視圖,但觸摸目標鏈表不為空 -> 將觸摸目標鏈表末端標記為當前觸摸目標 newTouchTarget = mFirstTouchTarget; while (newTouchTarget.next != null) { newTouchTarget = newTouchTarget.next; } newTouchTarget.pointerIdBits |= idBitsToAssign; } // 省略代碼 ... } // Dispatch to touch targets. if (mFirstTouchTarget == null) { // 5. 回溯:觸摸目標鏈表為空(當前沒有子視圖消耗事件序列),則將事件轉發給基類 dispatchTouchEvent 處理 handled = dispatchTransformedTouchEvent(ev, canceled, null, TouchTarget.ALL_POINTER_IDS); } else { // 省略代碼 ... // 4. 分發:觸摸目標鏈表不為空,則遍歷觸摸目標鏈嘗試傳遞事件或取消觸摸目標(事件被攔截) TouchTarget target = mFirstTouchTarget; while (target != null) { final TouchTarget next = target.next; // 省略代碼 ... if (dispatchTransformedTouchEvent(ev, cancelChild, target.child, target.pointerIdBits)) { handled = true; } // 省略代碼 ... target = next; } } // 省略代碼 ... } // 省略代碼 ... return handled; }
View.dispatchTouchEvent 和 View.onTouchEvent 源碼分析
- 滾動條消費鼠標事件
- OnTouchListener 消費觸摸事件
- onTouchEvent 消費觸摸事件
TouchDelegate 消費觸摸事件
public boolean dispatchTouchEvent(MotionEvent event) { // 省略代碼 ... boolean result = false; // 省略代碼 ... if (onFilterTouchEventForSecurity(event)) { // 滾動條消費鼠標事件 if ((mViewFlags & ENABLED_MASK) == ENABLED && handleScrollBarDragging(event)) { result = true; } // OnTouchListener 消費觸摸事件 ListenerInfo li = mListenerInfo; if (li != null && li.mOnTouchListener != null && (mViewFlags & ENABLED_MASK) == ENABLED && li.mOnTouchListener.onTouch(this, event)) { result = true; } // View默認的事件處理邏輯,事件可能在其中被設置的 TouchDelegate 消費 if (!result && onTouchEvent(event)) { result = true; } } // 省略代碼 ... return result; } public boolean onTouchEvent(MotionEvent event) { // 省略代碼 ... if (mTouchDelegate != null) { // TouchDelegate 消費觸摸事件 if (mTouchDelegate.onTouchEvent(event)) { return true; } } // 省略代碼 ... return false; }
以上就是Android事件分發機制全面解析的詳細內容,更多關於Android事件分發機制的資料請關註WalkonNet其它相關文章!
推薦閱讀:
- Android 深入探究自定義view之事件的分發機制與處理詳解
- 關於Android觸摸事件分發的原理詳析
- View事件分發原理和ViewPager+ListView嵌套滑動沖突
- ViewPager2滑動沖突解決方案
- Android自定義View實現拖動自動吸邊效果