Android自定義View之事件分發機制總結

xxq2dream發表於2018-08-11

Android自定義View系列

事件序列

(1)手指接觸螢幕後會產生一系列事件,事件分為3種:ACTION_DOWN(手指剛剛接觸螢幕)、ACTION_MOVE(手指在螢幕移動)、ACTION_UP(手指從螢幕鬆開)

(2)一個事件序列為ACTION_DOWN-->ACTION_MOVE-->...-->ACTION_UP

事件傳遞的順序

Activity-->Window-->decor view-->我們的layout,ViewGroup-->我們佈局中被點選的子View

如果我們的子View沒有處理事件,那事件就會反向向上傳遞回來:

我們佈局中被點選的子View-->上層的ViewGroup-->decor view-->Window-->Activity

如果所有的View都沒有消耗事件,那最後事件會傳回到Activity,由Activity處理(Activity的onTouchEvent()方法被呼叫)

三大方法

ViewGroup中有3個跟事件分發有關的方法,分別是 dispatchTouchEvent、 onInterceptTouchEvent、onTouchEvent。

(1)dispatchTouchEvent方法

dispatchTouchEvent方法用來進行事件的分發。事件傳遞到當前View時,這個方法就會被呼叫。dispatchTouchEvent方法裡面包含了具體的事件分發邏輯,返回結果受當前View的onTouchEvent方法和下級View的dispatchTouchEvent方法的影響。

(2)onInterceptTouchEvent方法

onInterceptTouchEvent方法在dispatchTouchEvent方法內部被呼叫,用來判斷是否攔截某個事件。如果當前View攔截了某個事件,那麼在同一個事件序列當中,此方法不會被再次呼叫,返回結果表示是否攔截當前事件。這個方法只有VewGroup中有,View中沒有。

(3)onTouchEvent方法

在dispatchTouchEvent方法中呼叫,用來處理點選事件,返回結果表示是否消耗當前事件,如果不消耗,則在同一個事件序列中,當前View無法再次接收到事件。

onTouchListener、onTouchEvent、onClickListener的優先順序

(1)onTouchListener和onTouchEvent都在dispatchTouchEvent方法中被呼叫,onClickListener在onTouchEvent方法中被呼叫

(2)onTouchListener的優先順序高於onTouchEvent方法,如果onTouchListener的onTouch方法返回true,則onTouchEvent方法不會被呼叫,當然onClickListener就更不會被呼叫了

(3)在onTouchEvent方法中,如果當前View設定了onClickListener,那麼onClickListener的onClick方法會被呼叫

(4)只要View的CLICKABLE和LONKG_CLICKABLE有一個為true,View就會消耗當前事件,也就是說onTouchEvent方法最後會返回true。

(5)View的LONG_CLICKABLE屬性預設為false,而CLICKABLE屬性和具體的View有關,可點選的View的CLICKABLE屬性為true,不可點選的View的CLICKABLE屬性為false。

ViewGroup中的事件分發邏輯

ViewGroup中的事件分發邏輯可以用一段虛擬碼來表述

public boolean dispatchTouchEvent(MotionEvent ev) {
    boolean consume = false;
    if (onInterceptTouchEvent(ev)) {
        consume = onTouchEvent();
    }else {
        consume = child.dispatchTouchEvent(ev);
    }
    
    return consume;
}
複製程式碼

從上述的虛擬碼中我們可以總結出ViewGroup中的事件分發流程:

(1)事件傳遞到ViewGroup時,dispatchTouchEvent方法會被呼叫。如果這個ViewGroup的onInterceptTouchEvent方法返回true,則表示它要攔截事件,事件就會交給當前ViewGroup的onTouchEvent方法處理。

(2)如果當前ViewGroup的onInterceptTouchEvent返回false,即不攔截事件,則會呼叫子元素的dispatchTouchEvent方法,這樣就把事件傳遞給了子元素。

(3)如果子元素沒有消耗事件,也就是子元素的dispatchTouchEvent方法返回false,那事件會由當前ViewGroup自己處理,當前ViewGroup的onTouchEvent會被呼叫。如果當前ViewGroup的dispatchTouchEvent方法也返回false,最後就會一層層往上,如果事件一直沒有被消耗,那麼最後Activity的onTouchEvent方法會被呼叫

(4)這裡需要理解一下的是ViewGroup繼承自View,ViewGroup中並沒有onTouchEvent方法。在所有子元素沒有消耗事件時,ViewGroup會呼叫父類,也就是View的dispatchTouchEvent方法,從而呼叫到onTouchEvent方法來自己處理事件,如果自己沒有消耗事件,dispatchTouchEvent方法就會返回false,從而將事件反向往上層傳遞。

(5)如果ACTION_DOWN事件子元素沒處理(onTouchEvent返回false),那這個事件序列的其他事件(MOVE和UP事件)都不會再分派給子元素處理。

(6)ViewGroup預設不攔截任何事件

(7)對於ACTION_DOWN事件,ViewGroup每次都會呼叫onInterceptTouchEvent方法來判斷是否需要攔截事件,一旦確定要攔截事件,後續的ACTION_MOVE和ACTION_UP事件都ViewGroup自己處理,不會傳遞給子View,也不會再呼叫onInterceptTouchEvent方法。所以onInterceptTouchEvent方法不是每次事件都會被呼叫的。

(8)子View可以通過requestDisallowInterceptTouchEvent方法來干預父元素的除了ACTION_DOWN意外的事件分發過程

View中的事件分發邏輯

requestDisallowInterceptTouchEvent方法

requestDisallowInterceptTouchEvent方法用於影響父元素的事件攔截策略,requestDisallowInterceptTouchEvent(true),表示不允許父元素攔截事件,這樣事件就會傳遞給子View。一般這個方法子View用的多,可以用來處理滑動衝突問題。

事件分發邏輯

(1)View中沒有onInterceptTouchEvent方法,所以一旦事件傳遞到View,那麼View的dispatchTouchEvent方法就會被呼叫。

(2)dispatchTouchEvent方法中處理事件的邏輯順序是onTouchListener-->onTouchEvent-->onClickListener。

(3)也就是說如果View設定了onTouchListener,那onTouchListener的onTouch方法會被呼叫,如果onTouch方法返回true,那事件就被消耗了,事件分發結束,onTouchEvent不會被呼叫。

(4)如果onTouch方法返回false,那麼onTouchEvent就會被呼叫。如果View設定了onClickListener,當ACTION_UP事件到來時,onTouchEvent中的onClickListener的onClick方法也會被呼叫。

(5)View一般都會消耗事件,如果View沒有消耗ACTION_DOWN事件,那後面ACTION_MOVE和ACTION_UP就都不會傳遞給View。

常用的滑動衝突處理邏輯

(1)利用父佈局的onInterceptTouchEvent方法

這個思路就是在父佈局需要處理事件時攔截下來,其他時候不攔截。有幾個注意點:

  • 對於ACTION_DOWN事件,onInterceptTouchEvent方法必須返回false,因為一旦返回true,子元素永遠也接收不到事件了,那還解決個毛線衝突。
  • 主要的邏輯就在ACTION_MOVE的處理上,需不需要攔截的邏輯在這裡根據需要來實現
  • 對於ACTION_UP事件返回false,因為一旦父元素返回true,那子View就接受不到ACTION_UP事件了,也就無法觸發onClick事件。

(2)利用子View的requestDisallowInterceptTouchEvent方法

這個思路就是父佈局預設攔截除了ACTION_DOWN的所有事件,子View中在dispatchTouchEvent方法中根據需要來干預父佈局的攔截策略。預設不允許父佈局攔截事件,在需要父佈局處理事件時,通過requestDisallowInterceptTouchEvent(false)方法讓父佈局處理事件,其他時候都由子View處理。

注意點:

  • 同樣的對於ACTION_DOWN事件,onInterceptTouchEvent方法必須返回false,其他事件預設返回true
  • 在子View的dispatchTouchEvent方法中,對於ACTION_DOWN事件,通過呼叫requestDisallowInterceptTouchEvent(true)預設不允許父佈局攔截事件,這樣後續事件都交給子View處理
  • 在子View的dispatchTouchEvent方法中,對於ACTION_MOVE事件,預設是子View處理,在需要父佈局處理時,呼叫requestDisallowInterceptTouchEvent(false)方法來讓父佈局攔截事件,交給父佈局處理。

                    歡迎關注我的微信公眾號,和我一起每天進步一點點!
複製程式碼

AntDream

相關文章