1. 程式人生 > >Android---事件分發機制原理講述

Android---事件分發機制原理講述

為什麼要有事件分發機制?
安卓系統中的View是樹形結構的,View可能會重疊在一起,當我們點選的activity有多個View都可以響應的時候,這個點選事件應該給誰呢?為了解決這一個問題,就有了事件分發機制。
如下圖,View是一層一層巢狀的,當手指點選 View1 的時候,下面的ViewGroupA、 RootView 等也是能夠響應的,為了確定到底應該是哪個View處理這次點選事件,就需要事件分發機制來幫忙處理。
這裡寫圖片描述

View的結構簡單描述:
我們的View是樹形結構的,在上述中例項View的介面大致如下:
layout佈局檔案:

<com.gcssloop.touchevent
.test.RootView xmlns:android="http://schemas.android.com/apk/res/android" xmlns:tools="http://schemas.android.com/tools" android:layout_width="match_parent" android:layout_height="300dp" android:background="#4E5268" android:layout_margin="20dp" tools:context="com.gcssloop.touchevent.MainActivity"
> <com.gcssloop.touchevent.test.ViewGroupA android:background="#95C3FA" android:layout_width="200dp" android:layout_height="200dp"> <com.gcssloop.touchevent.test.View1 android:background="#BDDA66" android:layout_width="130dp" android:layout_height="130dp"
/> </com.gcssloop.touchevent.test.ViewGroupA> <com.gcssloop.touchevent.test.View2 android:layout_alignParentRight="true" android:background="#BDDA66" android:layout_width="80dp" android:layout_height="80dp"/></com.gcssloop.touchevent.test.RootView>

View結構簡圖:
這裡寫圖片描述
可以看到在上面的View結構中莫名多出來的兩個東西,PhoneWindow 和 DecorView ,這兩個我們並沒有在Layout檔案中定義過,但是為什麼會存在呢?
仔細觀察上面的 layout 檔案,你會發現一個問題,我在 layout 檔案中的最頂層 View(Group) 的大小並不是填滿父窗體的,留下了大量的空白區域,由於我們的手機螢幕不能透明,所以這些空白區域肯定要顯示一些東西,那麼應該顯示什麼呢?

有過安卓開發經驗的都知道,螢幕上沒有View遮擋的部分會顯示主題的顏色。不僅如此,最上面的一個標題欄也沒有在 layout 檔案中,這個標題欄又是顯示在哪裡的呢?
你沒有猜錯,這個主題顏色和標題欄等內容就是顯示在DecorView中的。
現在知道 DecorView 是幹什麼的了,那麼PhoneWindow 又有什麼作用?

要了解 PhoneWindow 是幹啥的,首先要了解啥是 Window ,看官方原始碼解釋:

Abstract base class for a top-level window look and behavior policy.
An instance of this class should be used as the top-level view added
to the window manager. It provides standard UI policies such as a
background, title area, default key processing, etc.

簡單來說,Window是一個抽象類,是所有檢視的最頂層容器,檢視的外觀和行為都歸他管,不論是背景顯示,標題欄還是事件處理都是他管理的範疇,它其實就像是View界的太上皇(雖然能管的事情看似很多,但是沒實權,因為抽象類不能直接使用)。

而 PhoneWindow 作為 Window 的唯一親兒子(唯一實現類),自然就是 View 界的皇帝了,PhoneWindow 的權利可是非常大大,不過對於我們來說用處並不大,因為皇帝平時都是躲在深宮裡面的,雖然偶爾用特殊方法能見上一面,但想要完全指揮 PhoneWindow 為你工作是很困難的。

而上面說的 DecorView 是 PhoneWindow 的一個內部類,其職位相當於小太監,就是跟在 PhoneWindow 身邊專業為 PhoneWindow 服務的,除了自己要幹活之外,也負責訊息的傳遞,PhoneWindow 的指示通過 DecorView 傳遞給下面的 View,而下面 View 的資訊也通過 DecorView 回傳給 PhoneWindow。
事件分發、攔截與消費列表如下
√ 表示有該方法。
X 表示沒有該方法。
這裡寫圖片描述
這個三個方法均有一個 boolean(布林) 型別的返回值,通過返回 true 和 false 來控制事件傳遞的流程。
注意 :由上表可以看到 Activity 和 View 都是沒有事件攔截的,這是因為:Activity 作為原始的事件分發者,如果 Activity 攔截了事件會導致整個螢幕都無法響應事件,這肯定不是我們想要的效果。
View最為事件傳遞的最末端,要麼消費掉事件,要麼不處理進行回傳,根本沒必要進行事件攔截。

事件分發流程:
前面我們瞭解到View是樹形結構的,基於這樣的結構,我們的事件可以進行有序的分發。

事件收集之後最先傳遞給 Activity, 然後依次向下傳遞,大致如下:

Activity -> PhoneWindow -> DecorView -> ViewGroup -> … -> View

這樣的事件分發機制邏輯非常清晰,可是,你是否注意到一個問題?如果最後分發到View,如果這個View也沒有處理事件怎麼辦,就這樣讓事件浪費掉?

當然不會啦,如果沒有任何View消費掉事件,那麼這個事件會按照反方向回傳,最終傳回給Activity,如果最後 Activity 也沒有處理,本次事件才會被拋棄:

Activity <- PhoneWindow <- DecorView <- ViewGroup <- … <- View

這種設計是非常精巧的,上層View既可以直接攔截該事件,自己處理,也可以先詢問(分發給)子View,如果子View需要就交給子View處理,如果子View不需要還能繼續交給上層View處理。既保證了事件的有序性,又非常的靈活。在我第一次將這個邏輯弄清楚的時候,看著這樣精妙的設計,簡直想歡呼慶賀一下。

其實關於事件傳遞機制,吳小龍的 Android事件傳遞機制分析 一文中的比喻非常有趣,本文也會借鑑一些其中的內容。

先確定幾個角色:

Activity - 公司大老闆

RootView - 專案經理

ViewGroupA - 技術小組長

View1 - 碼農小王(公司裡唯一的碼農)

View2 - 跑龍套的路人甲,無視即可

點選 View1 區域但沒有任何 View 消費事件
這裡寫圖片描述
當手指在 View1 區域點選了一下之後,如果所有View都不消耗事件,你就能看到一個完整的事件分發流程,大致如下:(紅色箭頭方向表示事件分發方向、綠色箭頭方向表示事件回傳方向。)

這裡寫圖片描述
簡單來說如下所述:

1 事件返回時 dispatchTouchEvent 直接指向了父View的 onTouchEvent 這一部分是不合理的,實際上它僅僅是給了父View的dispatchTouchEvent 一個 false 返回值,父View根據返回值來呼叫自身的 onTouchEvent。
2. ViewGroup 是根據 onInterceptTouchEvent 的返回值來確定是呼叫子View的 dispatchTouchEvent 還是自身的 onTouchEvent, 並沒有將呼叫交給 onInterceptTouchEvent。
3. ViewGroup 的事件分發機制虛擬碼如下,可以看出呼叫的順序。

public boolean dispatchTouchEvent(MotionEvent ev) {
    boolean result = false;             // 預設狀態為沒有消費過

    if (!onInterceptTouchEvent(ev)) {   // 如果沒有攔截交給子View
        result = child.dispatchTouchEvent(ev);
    }

    if (!result) {                      // 如果事件沒有被消費,詢問自身onTouchEvent
        result = onTouchEvent(ev);
    }

    return result;}

測試1:

情景:老闆: 我看公司最近業務不咋地,準備發展一下電商業務,下週之前做個淘寶出來試試怎麼樣。事件順序,老闆(MainActivity)要做淘寶,這個事件通過各個部門(ViewGroup)一層一層的往下傳,傳到最底層的時候,碼農小王(View1)發現做不了,於是訊息又一層一層的回傳到老闆那裡。可以看到整個事件傳遞路線非常有序。從Activity開始,最後回傳給Activity結束(由於我們無法操作Phone Window和DecorView,所以沒有它們的資訊)。
這裡寫圖片描述
點選 View1 區域且事件被 View1 消費
如果事件被View1消費掉了則事件會回傳告訴上層View這個事件已經被我解決了,上層View就無需再響應了。
這裡寫圖片描述

測試2:
情景:老闆: 報告一下專案進度。
事件順序,老闆(MainActivity)要知道專案進度,這個事件通過各個部門(ViewGroup)一層一層的往下傳,傳到技術組組長(ViewGroupA)的時候,組長(ViewGroupA)上報任務即可。無需告知碼農小王(View1)。
這裡寫圖片描述

事件分發機制設計到到情形非常多,這裡就不一一列舉了,記住以下幾條原則就行了。
1.如果事件被消費,就意味著事件資訊傳遞終止。
2.如果事件一直沒有被消費,最後會傳給Activity,如果Activity也不需要就被拋棄。
3.判斷事件是否被消費是根據返回值,而不是根據你是否使用了事件。
文中測試用的原始碼下載

#總結
View的事件分發機制實際上就是一個非常經典的責任鏈模式,如果你瞭解責任鏈模式,那麼事件分發對你來說並不是什麼難題,如果你不瞭解責任鏈模式,剛好藉此機會學習一下啦。由於水平有限,如有不當之處還請指出,謝謝!