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
了。我們直接看看PhoneWindow
的superDispatchTouchEvent()
到底做了什麼操作。
直接調用了DecorView
的superDispatchTouchEvent()
方法。DecorView
繼承於FrameLayout
,作爲頂層View,是所有界面的父類。而FrameLayout
作爲ViewGroup
的子類,所以直接調用了ViewGroup
的dispatchTouchEvent()
。
ViewGroup的事件分發機制
我們通過查看ViewGroup
的dispatchTouchEvent()
可以發現。
我們分段來看dispatchTouchEvent()。
注:本文中源碼都經過了小編刪減,只展示與事件分發相關邏輯。
我們來看如上代碼中的紅框部分:ACTION_DOWN
事件一定會交由ViewGroup處理,子View沒辦法攔截,因爲resetTouchState()
會把requestDisallowInterceptTouchEvent()
所置的標誌位重置
。
我們再來看紅框外的部分:首先定義了一個變量intercept
來表示是否攔截事件。
其中採用了onInterceptTouchEvent()
對intercept
進行賦值。大多數情況下,onInterceptTouchEvent()
返回值爲false,但我們可以重寫onInterceptTouchEvent()
來改變它的返回值,我們往下看後面這個intercept
是如何被用到的。
可以看到,當intercept
是false時,會通過for
循環去遍歷ViewGroup
的child
,然後調用dispatchTransformedTouchEvent()
,如果dispatchTransformedTouchEvent()
返回值是true,就去調用addTouchTarget()
。
而當intercept
是true時,就不會去遍歷ViewGroup
的child
,也更不會調用child的
dispatchTransformedTouchEvent()
了。
這裏我們還需要看下dispatchTransformedTouchEvent()
和addTouchTarget()
的源碼是如何實現的。
在for
循環裏,傳入給dispatchTransformedTouchEvent()
的child
不爲null
,所以調用的是子View的dispatchTouchEvent()
。
如果子View的dispatchTouchEvent()
返回true,則調用addTouchTarget()
。
這個方法裏,我們只需要注意一點:給mFirstTouchTarget
賦值。
我們來整理一下思路:
當事件傳遞給ViewGroup,先去遍歷調用child的dispatchTouchEvent()
,如果有child的dispatchTouchEvent()
返回了true
,mFirstTouchTarget
就被賦值,否則mFirstTouchTarget
就爲null
。
至此,dispatchTouchEvent()就快看完了,我們來看下mFirstTouchTarget
是如何被使用到的。
如上,如果mFirstTouchTarget
爲null
,就去調用super.dispatchTouchEvent()
。
我們知道ViewGroup
的super
是View
,所以,當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()
,而ViewGroup
的super
是View
,所以我們來看下View
的dispatchTouchEvent()
。
View
的dispatchTouchEvent()
已經說的很清楚了: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