【自定義控件】android事件分發機制

自定義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返回     正常向下傳遞


說明:關於向下向上傳遞,只是個人理解的不同,我理解的事件分發模型類似一根立起來的管道,事件的傳遞從地面流向管道頂層,再流回到地面.正好符合視圖疊加的流程.並不一定說這種就是對的,方便自己理解的就是好的,看官也不必糾結於這點,關鍵是去理解中間事件分發的流程


掌握了以上的的事件傳遞的基本知識,下次我們碰到事件衝突就可以嘗試自己去解決了!

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章