一點見解: Android事件分發機制(一) - 基本概念解釋
一點見解: Android事件分發機制(二) - 分析ViewGroup
一點見解: Android事件分發機制(三) - 分析View
本文主要分析事件分發機制傳遞到View
后的處理邏輯.
上一篇文章的最后我們得到結論, 在事件傳遞到ViewGroup#dispatchTouchEvent
后, 經過一系列的判斷最終總會把事件傳遞給View#dispatchTouchEvent
, 所以我們從View#dispatchTouchEvent
開始.
在View#dispatchTouchEvent
中包含很多滾動的判斷, 對于事件分發機制我們只需要關注返回值的賦值, 知道什么時候子控件會消費事件就可以了. 相關代碼如下
// View#dispatchTouchEvent
public boolean dispatchTouchEvent(MotionEvent event) {
// ...
boolean result = false;
if (onFilterTouchEventForSecurity(event)) {
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;
}
}
// ...
return result;
}
整個方法內對返回值得賦值僅有2個地方, 都是在條件判斷內條件成立的時候就代表子控件消費該事件.第一個條件判斷關鍵就是當控件為enable
時執行OnTouchListener#onTouch
方法(如果存在該接口方法), 從這里可以得到結論
OnTouchListener#onTouch
是在View#dispatchTouchEvent
被調用的, 如果返回true
則不會調用View#onTouchEvent
, 且表示控件消費該事件.
第二個條件的判斷關鍵則是View#onTouchEvent
, 當其返回true
的時候控件消費該事件, 所以接著分析View#onTouchEvent
方法, 關鍵代碼如下
// View#onTouchEventpublic
boolean onTouchEvent(MotionEvent event) {
// ...
if ((viewFlags & ENABLED_MASK) == DISABLED) {
// ...
// disable但是clickable則消費該事件
return (((viewFlags & CLICKABLE) == CLICKABLE
|| (viewFlags & LONG_CLICKABLE) == LONG_CLICKABLE)
|| (viewFlags & CONTEXT_CLICKABLE) == CONTEXT_CLICKABLE);
}
if (mTouchDelegate != null) {
if (mTouchDelegate.onTouchEvent(event)) {
return true;
}
}
if (((viewFlags & CLICKABLE) == CLICKABLE
|| (viewFlags & LONG_CLICKABLE) == LONG_CLICKABLE)
|| (viewFlags & CONTEXT_CLICKABLE) == CONTEXT_CLICKABLE) {
switch (action) {
case MotionEvent.ACTION_UP:
// ...
if (!mHasPerformedLongPress && !mIgnoreNextUpEvent) {
// 一系列狀態判斷
removeLongPressCallback();
if (!focusTaken) {
// 利用Handler調用OnClickListener#onClick
if (!post(mPerformClick)) {
performClick();
}
}
}
// ...
break;
case MotionEvent.ACTION_DOWN:
// ...
break;
case MotionEvent.ACTION_CANCEL:
// ...
break;
case MotionEvent.ACTION_MOVE:
// ...
break;
}
return true;
}
return false;
}
事件經過一系列的dispatch之后被傳遞到View#onTouchEvent
中, 這個方法的返回值直接決定控件是否消費事件, 所以同樣忽略跟返回值無關的代碼, 看以上代碼, 決定返回值的地方有4處.
- 處理
disable
的情況, 因為在dispatchTouchEvent
中, 當控件為disable
時會直接調用onTouchEvent
方法, 從這里可以看出, 如果控件是disable
同時是clickable
則會消費事件, 但是不會調用任何clickable
的相關接口. - 讓
mTouchDelegate
優先處理事件, 通常情況下不會使用, 我們可以通過設置代理來覆蓋控件默認的處理事件邏輯. - 只要是控件是可點擊的, 就會消費事件, 下面進一步分析.
- 其他情況則不消費事件.
經過上面返回值的分析可以知道
控件是否消費事件的關鍵是
CLICKABLE
,LONG_CLICKABLE
和CONTEXT_CLICKABLE
, 而這3個參數都有對應的get/set
方法, 通常情況下只要設置了對應的接口就會賦值對應的參數為真.
當參數是可點擊的時候會進入一個switch
塊內對不同的事件進行不同的操作, 值得注意的是這里只對4個事件作出處理, 而且大部分是修改控件狀態, 值得關注的是ACTION_UP
事件, 因為ACTION_UP
代表一個操作的結束, 所以在這里應該可以判斷出這個操作具體是什么操作了, 在View
這里區分了長點擊和點擊兩種情況, 稍作分析就可以知道performClick()
執行了OnClickListener#onClick
方法, 代碼就不貼了, 所以這里可以得知
OnClickListener#onClick
和OnLongClickListener#onLongClick
是在View#onTouchEvent
內被回調的, 且同時只會回調其中一個, 另外只要有回調, 就代表控件消費了ACTION_UP
事件, 即消費了整個操作.
源碼bonus
- 改變控件處理事件的邏輯可以設置
OnTouchListener#onTouch
并返回true
或者通過View#setTouchDelegate
設置事件代理. -
OnTouchListener#onTouch
是在View#dispatchTouchEvent
中調用, 而OnClickListener#onClick
是在View#onTouchEvent
中調用, 所以OnTouchListener#onTouch
先于OnClickListener#onClick
被調用. - 即使控件是
disable
, 事件仍然會傳遞到onTouchEvent
, 而且只要控件是clickable
, 雖然接口不會回調, 但是控件仍會消費這事件.
總結
關于事件傳遞機制的路徑網上已經有很多文章了, 這個系列主要關注的是源碼中的一些細節, 所以不再花篇幅細說了.
概括來說, 傳遞路徑的邏輯主要是ViewGroup#dispatchTouchEvent
決定的, 事件進來后先決定是否攔截事件, 如果不攔截事件則會根據一定的順序(默認是在布局中的前后順序)逐個詢問在觸摸坐標下的子控件是否消費事件, 如果所有子控件都不消費事件那么就會調用父控件自己的onTouchEvent
.
更具體的說明可以參考圖解 Android 事件分發機制中的圖解, 畫得相當清晰.
如果關于這系列的文章有任何意見或者表意模糊的地方歡迎回復討論 :D