講講Android的事件分發機制

這裏寫圖片描述
Android的事件分發機制涉及的知識點很多,爲了方便記憶和知識總結,小編決定專門開一篇文章,記錄相關知識。
話不多說,我們直入主題。

面試場景

今天找到了幾個自己之前校招時記錄的關於事件分發的幾個面試題。

講講Android的事件分發機制

基本會遵從Activity => ViewGroup => View的順序進行事件分發,然後通過調用onTouchEvent()方法進行事件的處理。我們在項目中一般會對
MotionEvent.ACTION_DOWN,MotionEvent.ACTION_UP, MotionEvent.ACTION_MOVE分情況進行操作。

有去查看源碼中的事件攔截方法嗎?瞭解過相關源碼嗎?

事件攔截分爲內部攔截法外部攔截法,對於外部攔截法,我們可以重寫ViewGroup的onInterceptEvent();對於內部攔截法,我們可以通過
requestDisallowInterceptTouchEvent()來控制父容器是否攔截。

在一個列表中,同時對父View和子View設置點擊事件,優先響應哪個?爲什麼會這樣?

優先響應子View,因爲當父View決定不攔截子View後,就會調dispatchTouchEvent,ViewGroup的這個方法會先去遍歷調用子View的dispatchTouchEvent,如果都返回false,纔會走到父View的onTouchEvent

這三個問題只是簡單的熱身,要想了解Android的事件分發機制,還是得熟讀源碼。關於事件分發,應該分Activity、ViewGroup、View這三種情況來講。

而且,說到事件分發,有三個非常重要的方法也不得不提。

  • dispatchTouchEvent()
  • onTouchEvent()
  • onInterceptTouchEvent()

Activity的事件分發機制

顧名思義,dispatchTouchEvent()是負責事件分發的。當點擊事件產生後,事件會傳遞給當前Activity,這時會調用Activity的dispatchTouchEvent(),我們來看源碼。
這裏寫圖片描述
在上面這段代碼中,我們來看下getWindow().superDispatchTouchEvent()getWindow()明顯是獲取Window,這個就是我們很熟的PhoneWindow了。我們直接看看PhoneWindowsuperDispatchTouchEvent()到底做了什麼操作。
這裏寫圖片描述
直接調用了DecorViewsuperDispatchTouchEvent()方法。DecorView繼承於FrameLayout,作爲頂層View,是所有界面的父類。而FrameLayout作爲ViewGroup的子類,所以直接調用了ViewGroupdispatchTouchEvent()

ViewGroup的事件分發機制

我們通過查看ViewGroupdispatchTouchEvent()可以發現。
我們分段來看dispatchTouchEvent()。
這裏寫圖片描述
注:本文中源碼都經過了小編刪減,只展示與事件分發相關邏輯。
我們來看如上代碼中的紅框部分:ACTION_DOWN事件一定會交由ViewGroup處理,子View沒辦法攔截,因爲resetTouchState()會把requestDisallowInterceptTouchEvent()所置的標誌位重置

我們再來看紅框外的部分:首先定義了一個變量intercept來表示是否攔截事件。

其中採用了onInterceptTouchEvent()intercept進行賦值。大多數情況下,onInterceptTouchEvent()返回值爲false,但我們可以重寫onInterceptTouchEvent()來改變它的返回值,我們往下看後面這個intercept是如何被用到的。
這裏寫圖片描述
可以看到,當intercept是false時,會通過for循環去遍歷ViewGroupchild,然後調用dispatchTransformedTouchEvent(),如果dispatchTransformedTouchEvent()返回值是true,就去調用addTouchTarget()
而當intercept是true時,就不會去遍歷ViewGroupchild,也更不會調用child的
dispatchTransformedTouchEvent()了。
這裏我們還需要看下dispatchTransformedTouchEvent()addTouchTarget()的源碼是如何實現的。
這裏寫圖片描述
for循環裏,傳入給dispatchTransformedTouchEvent()child不爲null,所以調用的是子View的dispatchTouchEvent()
如果子View的dispatchTouchEvent()返回true,則調用addTouchTarget()
這裏寫圖片描述
這個方法裏,我們只需要注意一點:給mFirstTouchTarget賦值。

我們來整理一下思路:
當事件傳遞給ViewGroup,先去遍歷調用child的dispatchTouchEvent(),如果有child的dispatchTouchEvent()返回了truemFirstTouchTarget就被賦值,否則mFirstTouchTarget就爲null

至此,dispatchTouchEvent()就快看完了,我們來看下mFirstTouchTarget是如何被使用到的。
這裏寫圖片描述
如上,如果mFirstTouchTargetnull,就去調用super.dispatchTouchEvent()

我們知道ViewGroupsuperView,所以,當ViewGroup的所有child的dispatchTouchEvent()都返回false,就調用父容器的dispatchTouchEvent()

總結一下,ViewGroup的事件傳遞過程可以通過如下僞代碼表示:

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

View的事件分發機制

上節我們講到,當ViewGroup的所有child都沒有消費事件,就調用super.dispatchTouchEvent(),而ViewGroupsuperView,所以我們來看下ViewdispatchTouchEvent()
這裏寫圖片描述
ViewdispatchTouchEvent()已經說的很清楚了:onTouchListener的優先級大於onTouchEvent。如果onTouchListener返回true了,就不調用onTouchEvent了。
我們下面再看看onTouchEvent()的源碼。
這裏寫圖片描述
在手指擡起的時候都會調用performClick。如果設置了onClickListener,就會調用onClickListener.onClick

總結

1、Android事件分發總是遵循Activity => ViewGroup => View的傳遞順序
2、onTouchListener的優先級大於onTouchEvent。

參考

https://blog.csdn.net/jiangwei0910410003/article/details/17504315
https://www.cnblogs.com/qlky/p/6675882.html
https://blog.csdn.net/jaysong2012/article/details/45535521
https://www.jianshu.com/p/d3758eef1f72

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