天天看點

Android事件分發機制

Activity 中的事件分發機制

Activity 中包含兩個事件分發與處理的方法:

//事件分發

public boolean dispatchTouchEvent(MotionEvent ev);

//事件消費

public boolean onTouchEvent(MotionEvent event);

1

2

3

4

我們知道,事件最先是傳到 Activity 中,然後在其内部分發之後再傳遞給ViewGroup 。也就是說 ViewGroup 中的事件上遊其實是 Activity,但實際上,ViewGroup 事件的上遊不僅僅可以是 Activity,還可以是 Dialog,更進一步的說:ViewGroup 事件的上遊是一個 Window。

我這裡隻介紹 Activity 中的事件分發機制,其它的有興趣可以自行閱讀源碼。

首先,Activity 中的事件同樣起源于 dispatchTouchEvent 方法:

public boolean dispatchTouchEvent(MotionEvent ev) {

if (ev.getAction() == MotionEvent.ACTION_DOWN) {

onUserInteraction();

}

if (getWindow().superDispatchTouchEvent(ev)) {

return true;

return onTouchEvent(ev);

}

5

6

7

8

9

第一步是判斷事件如果是點選事件,則調用 onUserInteraction 方法,該方法的方法體為空,我們可以重寫這個方法,用于處理一些互動問題。

第二步是調用了 Window 的 superDispatchTouchEvent 方法,如果傳回 true,則表示此事件已被消費,結束此次分發流程,false 則繼續調用該 Activity 的 onTouchEvent 方法處理該事件。

再來說 superDispatchTouchEvent 這個方法是什麼,我們知道 Window 是個抽象類,具體實作為 PhoneWindow,我們看看 PhoneWindow 中的這個方法的實作:

@Override

public boolean superDispatchTouchEvent(MotionEvent event) {

return mDecor.superDispatchTouchEvent(event);

其中的 MDecor 指的是 DecorView, DecorView 是整個視圖樹的根節點視圖,也就是說 Activity 的事件被傳到了視圖樹的根節點,我們來看看 DecorView 中的 superDispatchTouchEvent 方法:

return super.dispatchTouchEvent(event);

隻是調用了父類的 dispatchTouchEvent 方法,它的父類是 FrameLayout,此時的事件已經傳遞到了視圖樹,開始通過 ViewGroup 的邏輯進行分發,事已至此,一切都很明确了,Activity 已經将事件傳遞給了 ViewGroup。

總結一下,事件進入 Activity#dispatchTouchEvent 開始分發,首先會将該事件傳遞給該頁面的 ViewGroup,ViewGroup 如果消費了該事件,則分發結束,未消費則繼續調用 Activity#onTouchEvent 方法處理事件,流程如下圖:

現在再來看 ViewGroup 的事件分發機制。

ViewGroup 中的事件分發機制

ViewGroup 中包含三個事件分發相關的方法:

//事件攔截

public boolean onInterceptTouchEvent(MotionEvent ev);

事件首先會進入 dispatchTouchEvent 方法開始分發,該方法通過調用 onInterceptTouchEvent 方法判斷是否需要攔截事件。

如果需要攔截(方法傳回 true),則表示目前 ViewGroup 希望處理該事件,或者不希望子 View 處理該事件,此時将直接調用 onTouchEvent 方法。

如果不需要攔截,則将該事件傳遞給子 View 的 onTouchEvent 方法,或者子 ViewGroup 的 dispatchTouchEvent 方法。

現在再來說 onTouchEvent 方法, 該方法用于消費事件,傳回值表示是否已消費,在兩種情況下會被調用:onInterceptTouchEvent 方法确認需要攔截該事件以及子 View/ViewGroup 未消費該事件。

如果該方法傳回 true,則表示事件已消費,此次事件分發就成結束;

如果傳回 false,則表示未消費該事件,會繼續調用父 ViewGroup 或 Activity 的 onTouchEvent 方法。

對于 ViewGroup 來說,事件攔截與事件消費是兩個概念。

事件攔截對應 onInterceptTouchEvent 方法,傳回 true 表示攔截該事件。

事件消費對應 onTouchEvent 方法,傳回 true 表示消費此事件。

攔截事件表示該事件将不會向下傳遞,也就是說不會傳遞給子 View。

消費事件表示該事件将不會向上傳遞,也就是說不會傳遞給父 ViewGroup 的 onTouchEvent 方法。

我們可以攔截但不消費某個事件,同樣,可以也消費但不攔截某個事件。

其分發流程如下圖所示:

View 中的事件分發機制

View 中包含如下兩個用于處理事件分發相關的方法:

public boolean dispatchTouchEvent(MotionEvent event);

ViewGroup 會将事件傳遞給 dispatchTouchEvent 方法,由于不是 ViewGroup,不需要考慮向下傳遞事件的邏輯,是以 View 中的事件處理流程很簡單。

抛開 dispatchTouchEvent 中其它的代碼來看,其中主要是用來調用 onTouchEvent 方法,并傳回 onTouchEvent 的傳回值,流程如下:

上述便是 Android 事件機制的完整流程,實際上事件分發機制還有很多要處理的東西,我這裡隻是介紹了一些重點及關鍵點。

下面我來用一張圖總結一下:

其他

另外還有一些需要注意點的點。

除了上面說的這幾個事件分發相關的方法外,我們經常會調用 View#setOnTouchListener 來處理事件,例如下面這樣:

view.setOnTouchListener(new View.OnTouchListener() {

@Override

public boolean onTouch(View v, MotionEvent event) {

return false;

});

當我們調用了這個方法後,dispatchTouchEvent 會優先調用 OnTouchListener#onTouch 方法判斷是否消費該事件,不消費才會繼續調用 onTouchEvent 方法。

還有一點,我們設定 OnTouchListener 時 IDE 會提示一個警告,或者我們直接重寫 View#OnTouchEvent 方法時同樣會提示這個警告: