View事件分發機制
前言
Android
原始碼分析之 View
系列之事件分發機制
ofollow,noindex">同步至個人部落格
正文
一. 概述
View
的觸控事件分發是 View
系列中的一個重難點, 主要需要掌握的是 MotionEvent
的傳遞規則和處理規則, 這是自定義 View
中衝突處理的理論來源~
觸控事件分發的處理主要是對 MotionEvent
的處理, MotionEvent
封裝了使用者的一系列行為, 如: ACTION_DOWN
(手指剛觸控式螢幕幕), ACTION_MOVE
(手指在螢幕上滑動), ACTION_UP
(手指抬起)等; 以及事件發生的座標(通過 MotionEvent.getX()
, MotionEvent.getY()
可以得到)等
在開始講解之前需要明確的一些概念是:
-
一個事件序列: 指的是一次完整的觸控過程, 即從
ACTION_DOWN
(手指觸控式螢幕幕)開始, 到中間的一系列ACTION_MOVE
(手指滑動), 最後到ACTION_UP
為止(手指抬起); 總結起來就是down...move...move..up
-
觸控事件的分發其實是一個從上到下不斷遞迴傳遞和攔截的過程; 一個大致的傳遞流程是:
Activity
-->Window
-->ViewGroup
-->View
, 當然如果向下傳遞但是MotionEvent
又沒有消耗的話, 又會逐層返回, 最終將沒有消耗的MotionEvent
交給Activity
處理
二. 事件分發之源
觸控事件產生和分發的源頭是在 Activity
中處理的, 即在 Activity
的 dispatchTouchEvent()
中; 如下; 處理思路也很簡單, 只是單純的向下分發而已, 如果事件沒有得到處理, 那麼最終就交給 Activity
的 onTouchEvent()
處理; 另外, 這裡還為使用者提供了一個監聽和攔截事件的方法, 即 onUserInteraction()
, 該方法在 Activity
中是一個空實現, 可以重寫該方法在事件向下傳遞之前進行特殊攔截和處理
public boolean dispatchTouchEvent(MotionEvent ev) { if (ev.getAction() == MotionEvent.ACTION_DOWN) { onUserInteraction();// 自定義事件攔截 } if (getWindow().superDispatchTouchEvent(ev)) {// 通過Window向下分發事件 return true; } return onTouchEvent(ev);// 如果事件最終沒有被處理, 那麼交給Activity自己的onTouchEvent()來處理 }
Activity
中的 Window
實際上是 PhoneWindow
, 這裡通過 PhoneWindow.superDispatchTouchEvent()
傳遞實際上是隻是簡單呼叫了 mDecor.superDispatchTouchEvent(event)
, 而這裡的 mDecor
實際上是 DecorView
, 是一個 FrameLayout
( ViewGroup
), 在 DecorView
的 superDispatchTouchEvent()
方法中, 也只是簡單的將事件傳遞給 ViewGroup
進行分發(即 ViewGroup.dispatchTouchEvent()
); 到這裡就將事件傳遞給 ViewGroup
和 View
處理了, 也是事件分發處理中最主要的一部分
三. ViewGroup分發事件
ViewGroup.dispatchTouchEvent()
中對事件的分發處理過程比較長, 實際上大致分成了三個部分來處理
3.1 事件攔截
首先, ViewGroup
會判斷是否進行事件攔截, 如下; 從後面將事件分發給子 View
的條件可以看出, 如果 ViewGroup
進行了事件攔截, 那麼該事件序列將不再向下分發; 這裡還需要注意的一點是, ViewGroup
判斷是否進行事件攔截的條件一個是為 ACTION_DOWN
時, 另一個是 mFirstTouchTarget != null
時; 也就是說一個事件序列的在開始時, 即 ACTION_DOWN
時一定會呼叫 ViewGroup
的 onInterceptTouchEvent
(當然, 還有一個影響因素是 FLAG_DISALLOW_INTERCEPT
, 我們稍後講解); 至於 mFirstTouchTarget
的賦值是在後面分發給子 View
時, 如果有子 View
處理了事件那麼 mFirstTouchTarget
將會被賦值;
上面是 ViewGroup
進行事件攔截的基本思路, 簡單總結起來就是:
-
ACTION_DOWN
時, 如果ViewGroup
進行了事件攔截(onInterceptTouchEvent()
返回true
), 那麼同一事件序列將不再向下分發(因為之後的ACTION_MOVE
和ACTION_UP
到來時, 由於之前ACTION_DOWN
時進行了事件攔截,mFirstTouchTarget
沒有機會賦值, 所以仍然為null
, 故直接走else
語句, 即intercepted = true
); -
ACTION_DOWN
時, 如果ViewGroup
不進行事件攔截, 並且在事件向下分發時, 有子View
處理了事件, 那麼mFirstTouchTarget
將會被賦值, 即不為null
, 此時仍然會繼續呼叫ViewGroup
的onInterceptTouchEvent
判斷是否進行事件攔截, 需要注意的是此時仍然在同一事件序列中 -
ACTION_DOWN
時, 如果ViewGroup
不進行事件攔截, 並且在事件向下分發時, 也沒有子View
進行事件處理, 那麼mFirstTouchTarget
仍為null
, 即走else
, 交由ViewGroup
處理事件
注: 只有當 ViewGroup
攔截了事件或者子 View
不處理事件時, onInterceptTouchEvent
才只會呼叫一次
// ViewGroup是否進行事件攔截 final boolean intercepted; if (actionMasked == MotionEvent.ACTION_DOWN || mFirstTouchTarget != null) { final boolean disallowIntercept = (mGroupFlags & FLAG_DISALLOW_INTERCEPT) != 0; if (!disallowIntercept) { 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 (!canceled && !intercepted) { // 如果攔截事件, 將不再分發給子View // 事件分發給子View .... }
另外, 上面還講了, 在 ACTION_DOWN
時, 一定會呼叫 ViewGroup
的 onInterceptTouchEvent
, 這裡還有一個影響因素是標誌位 FLAG_DISALLOW_INTERCEPT
, 該標誌位是通過 requestDisallowInterceptTouchEvent()
設定的, 作用是在子 View
中強制父 ViewGroup
不進行事件攔截, 但是該標誌位不能影響 ACTION_DOWN
, 因為在一個事件序列開始之前會先進行狀態重置, 如下; 在 resetTouchState()
中會將該標誌位重置, 所以就不會影響 ACTION_DOWN
啦~
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.2 事件分發
如果 ViewGroup
不進行事件攔截的話, 會將事件分發給子 View
處理; 事件分發的主要程式碼如下; 邏輯也比較簡單, 就是遍歷所有的子 View
, 然後通過 dispatchTransformedTouchEvent()
進行將事件傳遞給子 View
if (!canceled && !intercepted) { ... for (int i = childrenCount - 1; i >= 0; i--) { if (dispatchTransformedTouchEvent(ev, false, child, idBitsToAssign)) { // 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(); newTouchTarget = addTouchTarget(child, idBitsToAssign); // 設定mFirstTouchTarget的值 alreadyDispatchedToNewTouchTarget = true; break; } } ... }
我們繼續來看 dispatchTransformedTouchEvent()
的處理過程, 如下; 從上面的程式碼中我們可以看出, 將事件分發給子 View
的時候, 呼叫 dispatchTransformedTouchEvent()
傳入的 child
非空, 所以應該呼叫的是 child.dispatchTouchEvent(event)
, 這樣就將事件傳遞到子 View
中去了; 這裡關於子 View
的 dispatchTouchEvent()
處理在後文繼續講解
private boolean dispatchTransformedTouchEvent(MotionEvent event, boolean cancel, View child, int desiredPointerIdBits) { ... if (newPointerIdBits == oldPointerIdBits) { if (child == null || child.hasIdentityMatrix()) { if (child == null) { handled = super.dispatchTouchEvent(event); // child非null } else { final float offsetX = mScrollX - child.mLeft; final float offsetY = mScrollY - child.mTop; event.offsetLocation(offsetX, offsetY); handled = child.dispatchTouchEvent(event); // 呼叫child.dispatchTouchEvent(event) event.offsetLocation(-offsetX, -offsetY); } return handled; } transformedEvent = MotionEvent.obtain(event); } else { transformedEvent = event.split(newPointerIdBits); } ... }
上面我們說過, 如果子 View
處理了事件的話, 將會去設定 mFirstTouchTarget
的值, 該值的設定其實是在 addTouchTarget()
中, 也就是說, 當 dispatchTransformedTouchEvent()
返回 true
, 即有子 View
處理了事件的話, 就會去呼叫該函式, 也就證明了我們前面所說的; 我們來看 addTouchTarget()
, 如下; 可以看出這裡實際上相當於一個單鏈表
private TouchTarget addTouchTarget(@NonNull View child, int pointerIdBits) { final TouchTarget target = TouchTarget.obtain(child, pointerIdBits); target.next = mFirstTouchTarget; mFirstTouchTarget = target; // 設定mFirstTouchTarget return target; }
3.3 ViewGroup處理事件
如果 ViewGroup
攔截了事件或者子 View
沒有進行事件處理, 那麼 ViewGroup
將進行事件處理, 如下; 可以看出, ViewGroup
進行事件處理也是呼叫 dispatchTransformedTouchEvent()
, 只是傳入的 child
為 null
, 那麼從上面的 dispatchTransformedTouchEvent()
程式碼中我們可以看出, 如果 child
為 null
呼叫的應該就是 super.dispatchTouchEvent(event)
進行事件處理
if (mFirstTouchTarget == null) { // No touch targets so treat this as an ordinary view. handled = dispatchTransformedTouchEvent(ev, canceled, null, TouchTarget.ALL_POINTER_IDS); }
需要注意的是不管是 super.dispatchTouchEvent(event)
還是 child.dispatchTouchEvent(event)
, 呼叫的其實都是 View.dispatchTouchEvent()
, 所以接下來我們要看的就是 View
中對事件的處理
四. View事件處理
需要注意的是, View
中沒有 onInterceptTouchEvent()
方法來進行事件攔截; 我們這裡關注的, 主要是 View
對事件的處理, 這裡的 View
包括 ViewGroup
進行事件攔截之後對事件的處理以及子 View
對事件的處理; 因為從前面我們已經說了, 不管是呼叫的 super.dispatchTouchEvent()
( ViewGroup
處理事件)還是 child.dispatchTouchEvent()
(子 View
處理事件), 其實都是呼叫的 View.dispatchTouchEvent()
; 所以二者對事件的處理實際上是一樣的, 同時需要注意的是, 這一節不包括事件的分發了,
事件分發在上一節中已經講解完啦~
觸控事件的處理主要涉及到 OnTouchListener
, onTouchEvent
和 onClick
的處理優先順序
主要程式碼如下; 可以看出先處理的是 OnTouchListener
, 如果 View
沒有設定 OnTouchListener
( View.setOnTouchListener()
)的話, 再去處理 onTouchEvent()
, 所以 OnTouchListener
的優先順序比 onTouchEvent
高; 同時還要注意的一點是, 如果設定了 OnTouchListener
的話, View
的 onTouchEvent
將不再呼叫
public boolean dispatchTouchEvent(MotionEvent event) { ... if (li != null && li.mOnTouchListener != null && (mViewFlags & ENABLED_MASK) == ENABLED && li.mOnTouchListener.onTouch(this, event)) {// OnTouchListener result = true; } if (!result && onTouchEvent(event)) { // onTouchEvent result = true; } ... }
這裡還有一個 onClick()
其實是在 onTouchEvent()
中處理的; 如下; onClick
是在 performClickInternal()
中觸發的, 可以看出, 要觸發 onClick
需要的條件是: View
是可以點選的( clickable
), 這裡的可點選包括了 CLICKABLE
和 LONG_CLICKABLE
, 注意 View
的 enable
屬性不影響 onTouchEvent
的返回值, 只要它可點選, 那麼 onTouchEvent()
就會處理該點選事件
public boolean onTouchEvent(MotionEvent event) { if (clickable || (viewFlags & TOOLTIP) == TOOLTIP) { switch (action) { case MotionEvent.ACTION_UP: ... if (!mHasPerformedLongPress && !mIgnoreNextUpEvent) { if (!focusTaken) { // Use a Runnable and post this rather than calling // performClick directly. This lets other visual state // of the view update before click actions start. if (mPerformClick == null) { mPerformClick = new PerformClick(); } if (!post(mPerformClick)) { performClickInternal(); } } } ... } }
而在 performClickInternal()
中, 則是去呼叫了 performClick()
進行處理, 在 performClick()
會判斷, 如果設定了 OnClickListener
, 則會去呼叫 OnClickListener
, 程式碼比較簡單, 就不貼啦~
五. 總結
到這裡, View
的事件分發和處理流程就分析結束啦~; 我們最開始講 事件分發之源 時講 Activity
對事件的傳遞的時候, 如果 getWindow().superDispatchTouchEvent()
返回 false
的話, 就最終將事件交給 Activity
的 onTouchEvent()
處理, 這種情況其實對應的是 ViewGroup
和 View
都不進行事件處理, 那麼就逐層回傳咯~
最後將上述流程總結為下圖:

View事件分發.png