深入:Android Touch事件傳遞機制全面解析(從WMS到View樹)
通俗理解Android事件分發與消費機制
說起Android滑動沖突,是個很常見的場景,比如SliddingMenu與ListView的嵌套,要解決滑動沖突,不得不提及到View的事件分發機制。
Touch事件傳遞規則分析
首先,我們要知道Touch事件是包裝在MotionEvent對象中的,在手指與屏幕接觸過程中產生一系列事件,典型的事件有以下三種:
ACTION_DOWN:手指剛接觸屏幕的瞬間
ACTION_MOVE:手指在屏幕上滑動
ACTION_UP:手指剛離開屏幕的瞬間
那么,Android中Touch事件是一個怎樣的傳遞過程呢?
1 , 事件分發:public boolean dispatchTouchEvent(MotionEvent ev)
Touch事件發生時Activity的dispatchTouchEvent(MotionEvent ev)方法會將事件傳遞給最外層View的dispatchTouchEvent(MotionEvent ev)方法,該方法對事件進行分發。分發邏輯如下:
如果return true,事件會由當前View的dispatchTouchEvent方法進行消費,同時事件會停止向下傳遞;
如果return false,事件分發分為兩種情況:
如果當前 View 獲取的事件直接來自 Activity,則會將事件返回給Activity的onTouchEvent進行消費;
如果當前 View 獲取的事件來自外層父控件,則會將事件返回給父View的onTouchEvent進行消費。
如果return super.dispatchTouchEvent(ev),事件分發分為兩種情況:
如果當前View是ViewGroup,則事件會分發給onInterceptTouchEvent方法進行處理;
如果當前View是普通View,則事件直接交給onTouchEvent方法進行處理
2, 事件攔截:public boolean onInterceptTouchEvent(MotionEvent ev)
此方法只有ViewGroup才有, Activity與普通View沒有。上面已經提到,如果當前ViewGroup的dispatchTouchEvent(事件分發)返回super.dispatchTouchEvent(ev), 那么事件會傳遞到傳遞到onInterceptTouchEvent方法, 該方法對事件進行攔截。攔截邏輯如下:
如果return true,則表示攔截該事件,並將事件交給當前View的onTouchEvent方法;
如果return false,則表示不攔截該事件,並將該事件交由子View的dispatchTouchEvent方法進行事件分發,重復上述過程;
如果return super.onInterceptTouchEvent(ev), 事件攔截分兩種情況:
如果該View(ViewGroup)存在子View且點擊到了該子View, 則不攔截, 繼續分發給子View 處理, 此時相當於return false。
如果該View(ViewGroup)沒有子View或者有子View但是沒有點擊中子View(此時ViewGroup相當於普通View), 則交由該View的onTouchEvent響應,此時相當於return true。
一般的LinearLayout、 RelativeLayout、FrameLayout等ViewGroup默認不攔截, 而ScrollView、ListView等ViewGroup則可能攔截,得看具體情況。
3, 事件響應:public boolean onTouchEvent(MotionEvent ev)
上面已經提到,在dispatchTouchEvent(事件分發)返回super.dispatchTouchEvent(ev)並且onInterceptTouchEvent進行攔截(事件攔截返回true)的情況下,那么事件會傳遞到onTouchEvent方法,該方法對事件進行響應。響應邏輯如下:
如果return true,則表示響應並消費該事件;
如果return fasle,則表示不響應事件,那么該事件將會不斷向上層View的onTouchEvent方法傳遞,直到某個View的onTouchEvent方法返回true,如果到了最頂層View還是返回false,那么認為該事件不消耗,則在同一個事件系列中,當前View無法再次接收到事件,該事件會交由Activity的onTouchEvent進行處理;
如果return super.dispatchTouchEvent(ev),事件處理分為兩種情況:
如果該View是clickable或者longclickable的,則會返回true, 表示消費了該事件, 與返回true一樣;
如果該View不是clickable或者longclickable的,則會返回false, 表示不消費該事件,將會向上傳遞,與返回false一樣.
上述三個方法到底有什么區別與聯系呢?我們通過一段偽代碼來表示:
事件分發偽代碼
- public boolean dispatchTouchEvent(MotionEvent ev){
- boolean consume = false;
- if(onInterceptTouchEvent(ev)){ // 如果onInterceptTouchEvent返回true
- consume = onTouchEvent(ev); // 則交由該View的onTouchEvent方法
- } else {
- consume = child. dispatchTouchEvent(ev); // 否則交由子View的dispatchTouchEvent事件進行分發
- }
- return consume; // 如果成功消費該事件,則返回true,然后停止傳遞,否則返回false
- }
-------------------------------------------------------------------分割線------------------------------------------------------------
各組件對應方法的有無情況:
注: Activity的dispatchTouchEvent最終是調用了Window對應DecorView的dispatchTouchEvent, 相當於ViewGroup; onTouchEvent是Activity自帶的方法並不是DecorView的onTouchEvent; 同時,沒有onInterceptTouchEvent方法是因為Window並沒有回調該方法。
返回值作用:true和false標志事件是否被消費。
如果消費了就不再傳遞給其他控件了,如果沒有消費則還會傳遞給父控件或者子控件,觸發相應控件的事件處理函數。
控件默認返回值
1,對於ViewGroup的onInterceptTouchEvent方法:
如果存在子View且點擊到了子View, 則不攔截, 繼續分發給子View 處理, 此時返回super.onInterceptTouchEvent(ev) 就相當於return false。
如果沒有子View或者有子View但是沒有點擊中子View(此時ViewGroup相當於普通View), 則交由當前View的onTouchEvent響應,此時返回super.onInterceptTouchEvent(ev) 相當於return true。
2,對於View的onTouchEvent方法: 如果是clickable或者longClickable的,則返回true消費該事件; 否則返回false不消費該事件,從而往上傳遞.
-------------------------------------------------------------------分割線------------------------------------------------------------
注:同一個事件序列是指從手指接觸屏幕的那一刻開始,到手指離開屏幕那一刻結束,在這過程中所產生的一系列事件,這個事件序列以down事件開始,以up事件結束,中間含有數量不定的move事件.
事件分發與消費的規則總結:
(1)事件的分發是以隧道方式由上到下的, 即事件總是先傳遞給父元素, 然后再由父元素分發給子元素。對於onTouchEvent事件,如果返回false,則會以冒泡方式向上傳遞。 頂級View接收到事件之后,就會按相應規則去分發事件。如果一個View的onTouchEvent方法返回false,那么將會交給父容器的onTouchEvent方法進行處理,以冒泡方式逐級往上,如果所有的View都不處理該事件,則交由Activity的onTouchEvent進行處理。就跟工作中遇到了難題,逐級找領導解決一個道理,領導解決不了,再找上一級領導。
(2)正常情況下,一個事件序列只能被一個View攔截且消耗。某個View一旦進行事件攔截,那么這一個事件序列都只能交由他處理,並且onInterceptTouchEvent也不會被再次調用。因此,正常情況下一個事件是不能交給兩個View來處理的,當然,特殊做法就是在View的onTouchEvent處理完之后再返回false,強行交給其他View處理。
(3)如果某一個View開始處理事件,如果他不消耗ACTION_DOWN事件(也就是onTouchEvent返回false),則同一事件序列比如接下來進行ACTION_MOVE、ACTION_UP,則不會再交給該View處理,並且事件將重新提交給它的父元素處理。就像工作中做一件事情,你要么做完,要么你就不要做這件事了。
(4)ViewGroup的onInterceptTouchEvent方法默認返回false,即不攔截任何事件,而交給子View進行分發處理(前提是有子View)。
(5)普通View(比如TextView、ImageView,非ViewGroup)沒有onInterceptTouchEvent方法, 一旦有事件傳遞給它,它的onTouchEvent方法就會被調用。正常情況下,它們都會消耗事件(返回true),除非它們是不可點擊的(clickable和longClickable都為false),那么就會交由父容器的onTouchEvent處理。View的longClickable默認都是false的,而對於clickable則要分情況,比如Button的clickable默認你是true,而TextView默認是false.
(6)如果View不消耗除down以外的其他事件, 此時父View的onTouchEvent並不會被調用, 並且當前View可以持續收到后續事件,最終這些事件會傳遞給Activity處理.
(7)View的enable屬性不影響onTouchEvent的默認返回值,只要它clickable或者longClickable為true,則onTouchEvent就會返回true。
(8) 如果當前View是可點擊的,並且它收到了down和up事件(以down開始,以up結束),則它的click事件就會觸發;對於onLongClick,則只要當前View是longClickable的並接收到down事件且超過了系統默認的long時間,則就會觸發,只與down事件有關而與up事件無關.
(9)點擊事件分發過程如下 dispatchTouchEvent—->OnTouchListener的onTouch方法—->onTouchEvent-->OnClickListener的onClick方法。也就是說,我們平時調用的setOnClickListener,優先級是最低的,所以,OnTouchListener的onTouch方法如果返回true,則不響應onClick方法...
(10) 子View可以通過requestDisallowInterceptTouchEvent方法請求父控件不要攔截事件,從而干預事件的分發過程,但是down事件除外,無法干預到
(11)如果一個View監聽了onTouch,則在onTouch里面應該返回false,否則onTouchEvent事件及點擊、長按事件就無法監聽到
(12)如果ViewGroup中的子View將傳遞的事件消費掉,ViewGroup的onTouch將無法接收到任何事件, 但onTouchEvent還是能接收到的; 如果是View的onTouchEvent消費,則該View的onTouch仍然能接收到事件,因為此時onTouch的調用在onTouchEvent之前。 總之,對於View, 無論onTouchEvent消費與否,都會觸發View的onTouch事件, 因為onTouch的調用在onTouchEvent之前。
-------------------------------------------------------------------分割線------------------------------------------------------------
【參考資料】
Trinea
郭林
Android事件分發機制完全解析,帶你從源碼的角度徹底理解(上)
Android事件分發機制完全解析,帶你從源碼的角度徹底理解(下)
鴻洋
Android ViewGroup事件分發機制 源碼解析(下)
工匠若水
Android觸摸屏事件派發機制詳解與源碼分析一(View篇)
Android觸摸屏事件派發機制詳解與源碼分析二(ViewGroup篇)
Android觸摸屏事件派發機制詳解與源碼分析三(Activity篇)
AigeStudio
click相關: