自定義Viewgrou中我們也許會經常碰到這樣的情況,2個子控件的事件衝突導致滑動沒有用了,滑動反應很慢,點擊沒用了,要劃很多次才移動一點點等等。也許我們第一反應就是百度,google去搜索下答案,把代碼直接copy過來。其實也許可以換個解決辦法,自己想想爲什麼會出現這種情況。
以下是博主對android事件分發機制的探索。希望大家看完後能對Android事件分發機制有一個詳細的瞭解,以後不用百度,google也能輕鬆解決由於事件衝突導致各種問題。
首先我們要對Android 事件有初步的瞭解:
1.Android Touch事件相關的函數包括了:
dispatchTouchEvent(MotionEvent ev):負責事件分發的函數,在各個view裏面最先被調用
onInterceptTouchEvent(MotionEvent ev) :事件攔截的函數(viewGroup非常重要函數,下面會有具體說明)
onTouchEvent(MotionEvent ev):事件響應的函數
onTouch(MotionEvent ev):事件響應的函數
onTouchEvent(MotionEvent ev)和onTouch(MotionEvent ev)均是事件響應的函數,2者區別:onTouch會優先於onTouchEvent調用,onTouch只有在listener不爲空與點擊的控件爲enable的情況下會被調用,onTouch能通過控件外部傳入onTouchListener來實現監聽,而onTouchEvent不能通過外部設置。(可能描述過於抽象,簡單點就是有些控件沒有ontouch事件,或者控件不可點擊那麼我們想監聽onTouch事件就必須重寫onTouchEvent來實現監聽)
請看以下view的dispatchTouchEvent源碼中調用onTouch()和onTouchEvent()的區別:
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;
}
}
外層判斷暫時不管(用來判斷view是否位於頂部的,如果view不在頂部,過濾掉用戶點擊事件),請注意內層判斷,當mListenerInfo中的mOnTouchListener不爲空(即我們給view註冊了監聽事件)並且view是可點擊的就把事件交給mListenerInfo的mOnTouchListener.onTouch來處理並且根據onTouchListener的boolean來決定事件是否繼續傳遞,根據result的值來決定是否調用onTouchEvent
返回值說明:當dispatchTouchEvent(MotionEvent ev)返回爲false表示繼續向上傳遞,true表示停止傳遞
以下是事件傳遞的順序:
假定我們有一個LinearLayout, 佈局中有一個Button。那麼touch事件的傳遞如下:
activity的dispatchTouchEvent()------>LinearLayout的dispatchTouchEvent()--------->onInterceptTouchEvent()------->button的dispatchTouchEvent()從根元素向上依次傳遞,如果中間我們重寫了某view的dispatchTouchEvent()並且返回true,那麼事件會停止繼續傳遞並且由當前函數消費。onTouch和onTouchEvent一樣的道理(這兩者區別見上面描述),只是順序正好和dispatchTouchEvent的順序相反,從最外層向根元素傳遞。
至於onInterceptTouchEvent(),首先該函數是ViewGroup的函數,也意味着只有ViewGroup和該類的子類中可以重寫該函數,例如我們自定義的view繼承自LinearLayout(LinearLayout爲ViewGroup的子類),那麼我們就可以重寫該函數來達到事件攔截的目的,該函數緊跟dispatchTouchEvent()後調用(前提是該函數存在,默認返回false),如果onInterceptTouchEvent()返回爲false 事件會繼續傳遞,如果返回爲true,那麼事件將停止繼續向上面的dispatchTouchEvent()並且將事件交給自己的onTouch()和onTouchEvent()來處理。
下面我們來看下實驗的結果
1.沒有改變事件返回的結果
事件最終被customButton消費掉了,從中我們可以得到以下事件傳遞的圖
2.重寫onInterceptTouchEvent,並且返回爲true截斷事件繼續傳遞
這裏需要說明下由於在coustomLinearLayout中事件沒有被消費掉(也就是Touch相關函數全部返回爲false),如果是activity分發下去的事件那麼最終會到由activity onTouchEvent()消費掉,下面是調用的示意圖
3.CustomButton的onTouchEvent()返回false
4.點擊在CustomLinearLayout上,沒有點擊到CustomButton
從上面我們可以得到
1.除了onInterceptTouchEvent()外,其他事件按照1所示依次由根元素傳遞給點擊的view,並且由view消費掉,並且中間環節任意一個函數返回了true(除了onInterceptTouchEvent()外),那麼事件將會由當前返回true的函數消費,停止向後面傳遞,由於函數過多,博主就沒有把每個函數返回true的情況截圖貼出來了。
2.ViewGroup的子類中,重寫onInterceptTouchEvent()函數,返回爲true,那麼該函數將停止向子view的dispatchTouchEvent()傳遞,並把事件交由當前view的onTouch()和onTouchEvent()處理
3.view的onTouchEvent默認會消費掉事件,ViewGroup的0nTouchEvent則不會消費掉事件
4.同級別view,會根據你點擊的控件來進行事件傳遞,傳遞到相應的你點擊的view,如果點擊的是ViewGroup,那麼事件將不會被消費掉,直到傳遞到分發的根元素的OnTouchEvent()纔會被消費掉
後續補充:
偶然回顧很久之前寫的這篇博客,發現有關dispatchTouchEvent()函數的處理有些情況未做說明,容易導致讀者出現誤會,特此補充,
ViewGroup中dispatchTouchEvent()的返回值分爲3種情況:
1.返回false 停止事件向上的傳遞.調用上級傳遞者的onTouchEvent()處理
2.返回true 消費掉該次事件,終止事件傳遞
3.調用super返回 正常向下傳遞
說明:關於向下向上傳遞,只是個人理解的不同,我理解的事件分發模型類似一根立起來的管道,事件的傳遞從地面流向管道頂層,再流回到地面.正好符合視圖疊加的流程.並不一定說這種就是對的,方便自己理解的就是好的,看官也不必糾結於這點,關鍵是去理解中間事件分發的流程
掌握了以上的的事件傳遞的基本知識,下次我們碰到事件衝突就可以嘗試自己去解決了!