【Android基礎】講講Android的事件分發機制

Colin_Mindset發表於2018-07-26

這裡寫圖片描述
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

相關文章