View 事件傳遞體系知識梳理(1) 事件分發機制

澤毛發表於2017-12-21

一、事件分發概述

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 簡單舉例

我們舉一個簡單的例子:

View 事件傳遞體系知識梳理(1)   事件分發機制

  • 前提條件:ViewGroup1返回true
  • 過程:Root呼叫ViewGroup1dispatchTouchEvent,而ViewGroup1此時是白色,因此它繼續呼叫它的子節點,也就是View21dispatchTouchEvent,但是View21沒有子節點,因此它呼叫自己的onTouchEvent,它的dispatchTouchEvent方法返回,而此時,ViewGroup2所有的子節點都遍歷完了,它依然沒有變成紅色,因此它呼叫自己的onTouchEvent,由於該方法返回false,因此它也返回了,並且在返回時依然是白色。接下來RootViewGroup2的顏色對自己著色,著色完成之後發現自己仍然是白色,那麼它就繼續呼叫有可能使自己染色成功的方法,ViewGroup1dispatchTouchEvent,由於它的dispatchTouchEvent返回true,因此它會把自己染成紅色,由於它已經變成紅色了,那麼它也沒有權利對子節點進行染色,因此它的dispatchTouchEvent返回,Root收到返回值時,取ViewGroup1的顏色對自己進行著色,結果它發現自己是紅色了,那麼Root也不會呼叫任何可能對自己染色的方法,而是直接返回了。
  • 結果:ROOTViewGroup1變為紅色節點。

二、示例

只要理解了上面的思想,其實各種情況都可以對應到,下面的例子只是為了讓大家能夠證明自己的想法是否正確罷了,所以就不過多解釋了:

<?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>
複製程式碼

2.1 不改變任何函式的返回值

View 事件傳遞體系知識梳理(1)   事件分發機制

2.2 ViewGroup2dispatchTouchEvent返回false

View 事件傳遞體系知識梳理(1)   事件分發機制

2.3 ViewGroup2dispatchTouchEvent返回true

View 事件傳遞體系知識梳理(1)   事件分發機制

2.4 ViewGroup1下的孩子節點View2返回了false

View 事件傳遞體系知識梳理(1)   事件分發機制

2.5 ViewGroup1下的孩子節點View2返回了true

View 事件傳遞體系知識梳理(1)   事件分發機制

2.6 ViewGroup2onTouchEvent返回了false

View 事件傳遞體系知識梳理(1)   事件分發機制

2.7 ViewGroup2onTouchEvent返回了true

View 事件傳遞體系知識梳理(1)   事件分發機制

2.8 ViewGroup1下的孩子節點View2返回了false

View 事件傳遞體系知識梳理(1)   事件分發機制

2.9 ViewGroup1下的孩子節點View2返回了true

View 事件傳遞體系知識梳理(1)   事件分發機制

相關文章