進階必備-Android事件分發機制
一、簡介
或許你會問,“為什麼我一定要知道View的事件分發機制?”。因為我們在實際開發的過程中,經常會遇到多層的View互相巢狀以後,對某一個View進行滑動的時候,特別不靈敏,甚至於沒法滑動。這種滑動衝突的解決需要我們清楚的掌握View的事件分發機制。那下面我們詳細的講解下View的整個事件機制。
Android將View的事件封裝到MotionEvent這個類中,這也是監聽touch事件中回撥給我們的引數public boolean onTouchEvent(MotionEvent event) 。通常事件我們主要關心下面幾種型別:
-
MotionEvent.ACTION_DOWN
當我們手指按下螢幕的第一個事件便是ACTION_DOWN了,也就是意味著事件的開始。 -
MotionEvent.ACTION_MOVE
當我們手指按下屏幕後,在螢幕上滑動的過程,此事件就會不斷的觸發。 -
MotionEvent.ACTION_UP
此事件在我們手指從螢幕擡起的時候會觸發。 -
MotionEvent.ACTION_CANCEL
這個事件說起來稍微複雜一點,舉個栗子:當我們的外層View將事件傳遞給內層View去處理時,外層View的攔截方法一般會返回false,但是當某個條件觸發後,外層View想自己處理接下來的事件,就攔截了事件分發,此時內層View就會收到ACTION_CANCEL的事件。 -
MotionEvent.ACTION_OUTSIDE
這個事件我們不常用到,考慮這種場景。我們又一個Diallog彈出,當我們按Dialog以外的螢幕將Dialog消失掉。這個時候可以考慮監聽這個事件,要想使用這個事件我們必須對當前的Window設定一個Flag:FLAG_WATCH_OUTSIDE_TOUCH。
下面我們介紹和事件分發相關的幾個方法:
-
dispatchTouchEvent(MotionEvent event)
這個方法是用來處理向下分發事件邏輯的,我們通過觀察ViewGrope原始碼中的程式碼知道,這個方法細節較多,檢出我們比較關心的邏輯就是這個方法會先判斷子View是否有呼叫disallowIntercept父View去攔截事件,如果沒有,父View自己會呼叫onInterceptTouchEvent判斷自己是否有攔截,如果攔截事件,將呼叫父View自己的onTouchEvent方法去處理事件,如果沒有攔截事件,事件將繼續分發到子View中處理。 -
onInterceptTouchEvent(MotionEvent event)
用來申明是否攔截事件繼續向下分發,如果返回true,事件將不會繼續向下分發,而是交由自己的onTouchEvent方法處理。 -
onTouchEvent(MotionEvent event)
顯然,這個就是事件處理的方法了。 -
onTouch(MotionEvent event)
這個方法是在我們對某一個setOnTouchListener時回撥,也就是在傳遞事件的時候,在交給View本身的onTouchEvent處理之前判斷是否有監聽的TouchListener,如果有優先呼叫TouchListener的onTouch方法處理。
二、詳細分析View的分發事件
我們都知道,Android的View是樹形結構的,所以當一個事件來臨的時候一般是從根部分發下來的。為了方便我們接下來的理解,我們構建一個這樣的例子:
假設我們有這樣一個頁面,最外層是一個ViewGroup A,裡面巢狀著一個ViewGroup B,B裡面有一個ViewGroup C。
情景1:
假設我們對事件不做任何攔截,也不做任何處理。當我們點選View C,這個時候我們看到的Log顯示呼叫順序為:
A -> dispatchTouchEvent
A -> onInterceptTouchEvent
B -> dispatchTouchEvent
B -> onInterceptTouchEvent
C -> dispatchTouchEvent
C -> onInterceptTouchEvent
C -> onTouchEvent ACTION_DOWN
B -> onTouchEvent ACTION_DOWN
A -> onTouchEvent ACTION_DOWN
由於沒有任何View處理事件,最終會回撥到Activity的onTouchEvent中去處理。從這個情景中我們可以知道,事件向下傳遞的過程以及處理事件的向上傳遞的過程。
情景2:
假設我們在View B的onTouchEvent中返回true,再次點選事件並滑動,我們得到的Log如下:
A -> dispatchTouchEvent
A -> onInterceptTouchEvent
B -> dispatchTouchEvent
B -> onInterceptTouchEvent
C -> dispatchTouchEvent
C -> onInterceptTouchEvent
C -> onTouchEvent ACTION_DOWN
B -> onTouchEvent ACTION_DOWN
A -> dispatchTouchEvent
A -> onInterceptTouchEvent
B -> dispatchTouchEvent
B -> onTouchEvent ACTION_MOVE
A -> dispatchTouchEvent
A -> onInterceptTouchEvent
B -> dispatchTouchEvent
B -> onTouchEvent ACTION_UP
我們發現,除了ACTION_DOWN事件會下發到C,後續的事件不會再下發這是因為,當我們發現某一層View的onTouchEvent返回true以後,會有一個標誌位表示後續的事件都由此View處理,後續事件不再下發到子View,直到ACTION UP事件後將標誌位重置。
情景3:
假設我們在View B的onInterceptTouchEvent中返回true,再次點選C會怎麼樣呢?我們得到如下的Log記錄:
A -> dispatchTouchEvent
A -> onInterceptTouchEvent
B -> dispatchTouchEvent
B -> onInterceptTouchEvent
B -> onTouchEvent ACTION_DOWN
A -> onTouchEvent ACTION_DOWN
相比較於情景2,ACTION_DOWN事件不會下發到C,由於沒有View表示能處理,所以後續的事件均被取消。
三、總結
通過我們實際執行和分析原始碼發現,我們ViewGroup事件的分發流程如下所示:
對著上圖大家不妨嘗試分析下:
- 如果B的onInterceptTouchEvent中返回true並且onTouchEvent中返回true,那麼Log又將是怎樣的呢?
可以關注微信公眾號:南京Android部落,回覆“666”獲取答案哦~