一、事件分發概述
1.1 事件分發的關鍵方法
對於ViewGroup
來說,與事件分發相關的方法包括:
public boolean dispatchTouchEvent(MotionEvent event)
public boolean onInterceptTouchEvent(MotionEvent event)
public boolean onTouchEvent(MotionEvent event)
複製程式碼
對於View
來說,與事件分發相關的方法包括:
public boolean dispatchTouchEvent(MotionEvent event)
public boolean onTouchEvent(MotionEvent event)
複製程式碼
1.2 Down
分發的理解
對於Down
分發的過程,網上看了很多的例子和圖,但是都沒能好好理解,最後還是自己總結了一種方案,這個方案的核心就是染色:
- 初始的時候
View
樹上每個節點都是白色的。 - 從子節點的角度來看,當它被父節點呼叫
dispatchTouchEvent
時,在返回之前都會有一個返回值,如果這個返回值為真,那麼它自己就會變成紅色。 - 從父節點的角度來看,當它給子節點呼叫
dispatchTouchEvent
時,它既是在嘗試給子節點進行染色,也是在嘗試給自己著色,當某個子節點的dispatchTouchEvent
方法返回時,取該子節點的顏色對自己進行著色;如果遍歷完它所有的子節點,它仍然沒有變成紅色,那麼呼叫它自己的onTouchEvent
,如果返回true
,那麼把自己染成紅色。 - 對於一個
ViewGroup/View
來說,有可能染色成功只包括兩種途徑:子節點的dispatchTouchEvent
返回時,取子節點的顏色對自己著色;通過自己的onTouchEvent
方法來著色。並且,只有前一種途徑不成功時才會用後一種途徑。 - 只要節點變成了紅色,那麼就不需要再嘗試對自己進行染色了,也就是上面那條說的:利用子節點的
dispatchTouchEvent
來染色或者通過自己的onTouch
來染色,它會直接返回。
從虛擬碼來看:
public Color color = white;
public boolean dispatchTouchEvent(MotionEvent event) {
int childCount = getChildCount;
for (i = childCount - 1; i >= 0; i--) {
View child = getChildAt(i);
boolean result = child.dispatchTouchEvent(event);
if (result) {
color = RED;
return true;
}
}
boolean touchRes = onTouchEvent(event);
if (touchRes) {
color = RED;
}
return touchRes;
}
複製程式碼
在Down
事件分發完後,我們可以發現這麼個現象。
- 假如一個節點是紅色的,那麼它最多隻可能有一個紅色的子節點。
- 假如一個節點是紅色的,那麼必然會有一條唯一的路徑,該路徑會通過該紅色節點連線到根節點。
- 假如一個節點是白色的,那麼它所有的後代節點都一定是白色的。
1.3 Down
分發的特點
對於一個沒有重寫以上關鍵方法並且位於View
樹上的ViewGroup/View
來說,它Down
事件的分發具有以下幾個特點:
1.3.1 dispatchTouchEvent
dispatchTouchEvent
是否被回撥,由它的父容器決定的。- 假如是它不是葉節點,當它的
dispatchTouchEvent
被呼叫時,它會先逆序依次呼叫下一級子節點的dispatchTouchEvent
方法。 - 如果在以上遍歷中間有任何一個子節點的
dispatchTouchEvent
返回了true
,那麼不會繼續呼叫剩餘未遍歷子節點的dispatchTouchEvent
,並且它自身的onTouchEvent
不會被回撥。
1.3.2 onInterceptTouchEvent
只有ViewGroup
才有,它是否被回撥取決於dispatchTouchEvent
是否被回撥。
1.3.3onTouchEvent
onTouchEvent
是由自己回撥的,是否被回撥,必須同時滿足以下兩個條件:
dispatchTouchEvent
被回撥。- 滿足以下兩種情況之一:
- 它是整個
View
樹的葉節點 - 它擁有子節點,但是它所有子節點的
dispatchTouchEvent
都返回false
。
1.4 Move/Up
分發
在Move/Up
事件分發的時候,其實就是根據之前著色的結果來往下傳遞事件,它的傳遞只需要遵循下面的原則:只會分發給紅色的節點,遇到白色的節點就停止往下分發。
1.5 簡單舉例
我們舉一個簡單的例子:
- 前提條件:
ViewGroup1
返回true
。 - 過程:
Root
呼叫ViewGroup1
的dispatchTouchEvent
,而ViewGroup1
此時是白色,因此它繼續呼叫它的子節點,也就是View21
的dispatchTouchEvent
,但是View21
沒有子節點,因此它呼叫自己的onTouchEvent
,它的dispatchTouchEvent
方法返回,而此時,ViewGroup2
所有的子節點都遍歷完了,它依然沒有變成紅色,因此它呼叫自己的onTouchEvent
,由於該方法返回false
,因此它也返回了,並且在返回時依然是白色。接下來Root
取ViewGroup2
的顏色對自己著色,著色完成之後發現自己仍然是白色,那麼它就繼續呼叫有可能使自己染色成功的方法,ViewGroup1
的dispatchTouchEvent
,由於它的dispatchTouchEvent
返回true
,因此它會把自己染成紅色,由於它已經變成紅色了,那麼它也沒有權利對子節點進行染色,因此它的dispatchTouchEvent
返回,Root
收到返回值時,取ViewGroup1
的顏色對自己進行著色,結果它發現自己是紅色了,那麼Root
也不會呼叫任何可能對自己染色的方法,而是直接返回了。 - 結果:
ROOT
和ViewGroup1
變為紅色節點。
二、示例
只要理解了上面的思想,其實各種情況都可以對應到,下面的例子只是為了讓大家能夠證明自己的想法是否正確罷了,所以就不過多解釋了:
<?xml version="1.0" encoding="utf-8"?>
<com.example.lizejun.repoviews.LogFrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:tag="ViewGroup$Root"
android:orientation="vertical"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context="com.example.lizejun.repoviews.MainActivity">
<com.example.lizejun.repoviews.LogFrameLayout
android:tag="ViewGroup1"
android:layout_width="match_parent"
android:layout_height="match_parent">
<com.example.lizejun.repoviews.LogTextView
android:tag="ViewGroup1$View1"
android:layout_width="match_parent"
android:layout_height="match_parent" />
<com.example.lizejun.repoviews.LogTextView
android:tag="ViewGroup1$View2"
android:layout_width="match_parent"
android:layout_height="match_parent" />
</com.example.lizejun.repoviews.LogFrameLayout>
<com.example.lizejun.repoviews.LogFrameLayout
android:tag="ViewGroup2"
android:layout_width="match_parent"
android:layout_height="match_parent">
<com.example.lizejun.repoviews.LogTextView
android:tag="ViewGroup2$View"
android:layout_width="match_parent"
android:layout_height="match_parent"/>
</com.example.lizejun.repoviews.LogFrameLayout>
</com.example.lizejun.repoviews.LogFrameLayout>
複製程式碼