View 的事件分發淺*3析
一說到事件傳遞,我們自然而然的就會聯想到MotionEvent,是的,在事件分發機制中,事件的傳遞就是通過MotionEvent來傳遞的,MotionEvent 攜帶了我們觸控式螢幕幕所產生的一系列事件,如我們熟悉的ACTION_DOWN,ACTION_MOVE,ACTION_UP等事件。今天,我們就對分發事件做一個淺析,起一個拋磚引玉的作用。
三個重要代表
點選事件的分發過程主要是由三個重要代表來完成的,他們分別是:
- dispatchTouchEvent
- onInterceptTouchEvent
- onTouchEvent
- public boolean dispatchTouchEvent(MotionEvent ev)
先來說說dispatchTouchEvent方法,這個方法是Activity、ViewGroup 和View都具有的,根據名字我們就可以知道,它主要是用來進行事件的分發的,如果一個事件能夠傳遞給到當前View的話,那麼這個方法就會被呼叫,它返回的結果受當前View的onTouchEvent和子View的 dispatchTouchEvent 的返回結果影響。
- public boolean onInterceptTouchEvent(MotionEvent ev)
intercept,阻止,攔截的意思,所以從方法名上看,其返回值表示是否對當前事件進行攔截。如果當前的View 攔截了某個事件,那麼在同一個事件序列中,這個方法將不會被再次呼叫。這個方法在 dispatchTouchEvent 中呼叫。
- public boolean onTouchEvent(MotionEvent ev)
這個方法就是用來處理點選事件的,它的返回值表示它是否消耗事件,如果不消耗,那麼同一個事件序列中,它將不會再次接收到事件。
具體的原始碼這裡就不看了,大家可以自行前往 sdk 檢視相關原始碼,加深印象。這三個代表之間的關係呢,我們這裡用一段虛擬碼來表示:
public boolean dispatchTouchEvent(MotionEvent ev){ boolean consume = false; if (onInterceptTouchEvent(ev)){ consume = onTouchEvent(ev); } else { consume = child.dispatchTouchEvent(ev); } return consume; }

相應圖例,圖片來源網路
對於ViewGroup來說,當一個事件傳遞到它這,那麼則會呼叫dispatchTouchEvent這個方法,系統會詢問它是否要對此事件進行攔截,如果攔截,則回撥其的onTouchEvent方法,如果不攔截,則往下傳遞給子View,呼叫子View的 dispatchTouchEvent方法,這樣一來,事件就用當前View傳遞給了子View.
干擾ViewGroup的攔截事件
下面我們來看一個程式碼片段
// Check for interception. 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; }
這個片段擷取於ViewGroup 的dispatchTouchEvent方法,從片段中我們看到,當事件傳遞到當前View時,想要走到 onInterceptTouchEvent方法,必須經過幾個條件的判斷,其中有一個條件就是(mGroupFlags & FLAG_DISALLOW_INTERCEPT) != 0,這個FLAG_DISALLOW_INTERCEPT這個標誌位也起著干擾事件攔截的作用,這個標誌位可以通過呼叫requestDisallowInterceptTouchEvent方法進行賦值,子View 可以在想要干擾父控制元件攔截事件的時候,可以呼叫該方法,告訴父控制元件說,我需要自己處理相應的事件,當然,這前提是父控制元件不攔截ACTION_DOWN事件,因為如果ACTION_DOWN被攔截了,那麼後續的事件都會交由當前控制元件處理。這在處理滑動衝突的時候也提供了一個很好的解決辦法。
當同時設定 onTouchListener和onClickListenr時,如何響應?
帶著問題,我們看一下這兩個片段:
public boolean dispatchTouchEvent(MotionEvent event) { ... if (onFilterTouchEventForSecurity(event)) { //noinspection SimplifiableIfStatement ListenerInfo li = mListenerInfo; if (li != null && li.mOnTouchListener != null && (mViewFlags & ENABLED_MASK) == ENABLED && li.mOnTouchListener.onTouch(this, event)) { result = true; } if (!result && onTouchEvent(event)) { result = true; } } if (!result && mInputEventConsistencyVerifier != null) { mInputEventConsistencyVerifier.onUnhandledEvent(event, 0); } ... return result; }
public boolean onTouchEvent(MotionEvent event) { ... if (((viewFlags & CLICKABLE) == CLICKABLE || (viewFlags & LONG_CLICKABLE) == LONG_CLICKABLE) || (viewFlags & CONTEXT_CLICKABLE) == CONTEXT_CLICKABLE) { switch (action) { case MotionEvent.ACTION_UP: boolean prepressed = (mPrivateFlags & PFLAG_PREPRESSED) != 0; ... if (prepressed) { // The button is being released before we actually // showed it as pressed.Make it show the pressed // state now (before scheduling the click) to ensure // the user sees it. setPressed(true, x, y); } if (!mHasPerformedLongPress && !mIgnoreNextUpEvent) { // This is a tap, so remove the longpress check removeLongPressCallback(); // Only perform take click actions if we were in the pressed state 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)) { performClick(); } } } if (mUnsetPressedState == null) { mUnsetPressedState = new UnsetPressedState(); } if (prepressed) { postDelayed(mUnsetPressedState, ViewConfiguration.getPressedStateDuration()); } else if (!post(mUnsetPressedState)) { // If the post failed, unpress right now mUnsetPressedState.run(); } removeTapCallback(); } mIgnoreNextUpEvent = false; break; ... return true; } return false; }
這兩個片段擷取於View的dispatchTouchEvent方法和onTouchEvent方法,我們知道,在View中,事件傳遞總是最先給到dispatchTouchEvent方法,從片段一中可以看到,會先對 mOnTouchListener進行一個判斷,如果符合相關條件,則會響應touchListener事件,如果不符合,則回撥onTouchEvent方法。
在片段二中,我們可以看到,當觸控抬起時,方法會進行一系列的判斷,其中註釋中說道,在事件UP時,如果View處於按壓狀態,那麼就會performClick,及處理相應的點選事件。
所以,結合以上兩個片段,我們可以發現,當給View設定了onTouchListener, onClickListener的時候,View的事件響應順序應該是這樣的:onTouchListener -> onTouchEvent -> onClickListener
寫在最後
這篇文章是對View的事件分發做了一個簡單的介紹,更多詳細的內容還需要各位自己去檢視原始碼以加深印象。以上說的不正確有待糾正的地方歡迎各位指出,共同進步。☺☺☺