繼上篇內容,本文介紹 ViewTreeObserver 的使用,以及體會其所涉及的觀察者模式,期間會附帶回顧一些基礎知識。最後,咱們簡單聊一下 Android 的消息傳遞,附高清示意圖,輕鬆捋清整個傳遞過程!
在開始下篇以前,有必要回顧一下上篇《解析 ViewTreeObserver 源碼,體會觀察者模式、Android消息傳遞(上)》說起的 ViewTreeObserver 的概念:
ViewTreeObserver 是被用來註冊監聽視圖樹的觀察者,在視圖樹發生全局改變時將收到通知。這種全局事件包括但不限於:整個視圖樹的佈局發生改變、在視圖開始繪製以前、視圖觸摸模式改變時…
尚未看上篇,或者對上篇已經沒印象的,建議先去看一下。
本篇內容較多,爲節省篇幅,直接接着上篇繼續講。
#1. 一覽 ViewTreeObserver 的大綱
先經過這部分來對類的構成進行粗略的認知,這樣才能自如的應對後面的內容。本部分建議你們參考源碼去看,這樣會更直觀、更容易理解,我參考的源碼是 Android 6.0 的 SDK(api 23)。
查看類的大綱發現,該類看着挺複雜,但歸納起來看就很簡單了,下面咱們按類別來一個個拿下。(windows 下 AS 查看類大綱的默認快捷鍵是 Ctrl + F12,大綱模式下還支持搜索以快速定位)
1.1 類的接口
ViewTreeObserver 經過接口回調的方式實現觀察者模式,當接收到通知後,經過接口的回調方法告知程序相應的事件發生了。在 ViewTreeObserver 中,包含了 11 個接口,對應着11中觀察事件,以下圖:java
這裏寫圖片描述
1.2 類的方法
介紹完接口,下面總結一下 ViewTreeObserver 類的方法,大概分爲如下四種類型。
添加監聽:addOnXxxListener(OnXxxListener)
移除監聽:removeOnXxxListener(OnXxxListener)
分發事件:dispatchOnXxx()
其餘方法:checkIsAlive()、isAlive()方法等
「其餘方法」在上篇差很少提過了,如今咱們着重看前三類方法,下面簡稱 add、remove 和 dispatch 方法。
查看類可知,對於前面那張圖所展現的每個接口,都有與其對應的 add、remove、dispatch 方法。舉個例子吧,以 OnGlobalLayoutListener(全局佈局監聽) 爲例,那麼與其對應的三類方法就是:
addOnGlobalLayoutListener(OnGlobalLayoutListener listener);
removeOnGlobalLayoutListener(OnGlobalLayoutListener victim);
dispatchOnGlobalLayout();
這麼說,一共有11個接口,那麼與之對應的 add、remove、dispatch 方法也就分別有11個,沒錯,咱們經過大綱查看時就是這樣。這個你們自行去類中查看,或者根據上面舉的例子類推一下,我就再也不貼代碼了。
下面補充一點與方法的使用相關的內容:
雖然說 ViewTreeObserver 包含這麼多方法,可是系統並無對咱們開放全部的API。咱們能夠驗證一下,在程序代碼中先經過 getViewTreeObserver() 獲取 View 的 ViewTreeObserver 對象,而後使用該對象分別調用這幾類方法,分別模糊匹配 add、remove 和 dispatch,而後查看IDE的智能提示。
先看看調用 add 和 remove 方法:android
如圖所示,add 和 remove 方法只分別隻有8個,並無11個。其中remove中最後一個方法removeGloableOnLayoutListener已通過時了,在 API 16 取代它的方法是removeOnGloableLayoutListener。查看removeGloableOnLayoutListener方法可知,其直接調用了removeOnGloableLayoutListener方法,功能上沒區別。區別在於名字,確定是初期方法命名不合理,後來想改,但又不能直接修改或刪除。因此,在一開始就設計好一些規範,並在開發過程當中按照代碼規範開發,是有多重要…
既然都是8個,那各自少掉的3個呢?進 ViewTreeObserver類一看,發現不讓外部調用的是與OnWindowShownListener、OnComputeInternalInsetsListener、OnEnterAnimationCompleteListener接口對應的add、remove方法,這幾個方法之因此在程序中沒法訪問,是由於被添加了 @hide標籤,這是什麼?
@hide 意味着被其標記的方法、類或者變量,在自動生成文檔時,將不會出如今API文檔中對開發者開放,可是系統能夠調用,這就解釋了爲何咱們只能訪問其中8個方法了。其中有些要求對版本有要求,例如添加或移除 OnWindowAttachListener,須要 API 18 以上,而咱們一版在開發時會選擇最低適配 Android 4.0,也便是 API 爲 14,這樣一來就沒法使用。
其實,能夠經過反射訪問被 @hide 標記的域。可是不建議這麼作,由於 Google 在添加該標記時解釋道:
We are not yet ready to commit to this API and support it,so @hide。
既然沒有準備好提交這個API並支持他,也就意味着 Google 可能會隨時修改這些方法(雖然可能性很小),因此出於保險仍是不要經過反射使用的好(我的觀點)。
再來看看 dispatch 方法可用的有哪些:
喔,竟然只有3個!查看 ViewTreeObserver 類,發現其他8個不可訪問的方法沒有聲明修飾符,那就是默認的 default 類型。咱們知道,default 修飾的方法只能在同一包內可見,ViewTreeObserver.java 在 android.view 包下,咱們在程序中顯然沒法訪問。
#2. 接口和方法的做用
爲了保持內容的連貫和思路的清晰,在上一節只是介紹了 ViewTreeObserver 類的構成,並無解釋具體的做用。下面趁熱打鐵,看一下各自的做用。此處仍以 OnGlobalLayoutListener(全局佈局監聽) 接口對應的三個方法爲例,其餘接口的原理都同樣,再也不贅述。
2.1 OnGlobalLayoutListener 接口:windows
註釋很精確的歸納了其做用:當全局佈局狀態,或者視圖樹的子view可見性發生改變時,將調用該回調接口。
該接口包含了一個回調方法 onGlobalLayout(),咱們在程序中就是經過覆寫該方法,實現本身的邏輯,具體使用將在實戰部分介紹。
##2.2 addOnGlobalLayoutListener 和 removeOnGlobalLayoutListener 方法
仍是將這倆好基友放在一塊介紹,我直接簡稱 add 和 remove 了。
在程序中,經過 add 方法添加一個對 view 佈局發生改變的監聽,傳入 OnLayoutGlobalListener 接口對象,覆寫接口的 onGlobalLayout() 方法,系統會將咱們傳入的 OnLayoutGlobalListener 存在集合中。
當經過 add 監聽以後,咱們須要在適當的時候經過 remove 方法移除該監聽,防止屢次調用。一般在覆寫的 onGlobalLayout() 時方法中調用 remove 方法移除監聽。
##2.3 dispatchOnGlobalLayout 方法
dispatch 方法通常都由系統調用,咱們不須要去關心。在 dispatchOnGlobalLayout 方法中,會遍歷存放 OnLayoutGlobalListener 對象的集合,而後調用 OnLayoutGlobalListener 對象的 onGlobalLayout() 方法,通知程序該事件發生了。
[注:上述代碼中存放 OnGlobalLayoutListener 的集合 CopyOnWriteArray,值得了解一下,會讓你受益不淺。本打算講的,但限於篇幅只好做罷,感興趣的能夠上網瞭解一下]
3.使用姿式(實戰)
到目前爲止,咱們對 ViewTreeObserver 的認識仍停留在概念級別,終於等到了實戰環節,驗收本身學習成果的時刻到了。
##3.1 使用流程
咱們仍是先以 OnGlobalLayoutListener 爲例介紹一下標準使用流程,這裏須要結合上篇所學內容。
經過 View 對象的 getViewTreeObserver() 獲取 ViewTreeObserver 對象。
檢測 observer 是否可用。不可用的話再獲取一次
定義 OnGlobalLayoutListener 接口對象 listener,並覆寫 onGlobalLayout() 回調方法。若是隻監聽一次,記得在方法最後調用 observer.removeOnGlobalLayoutListener() 移除監聽,避免重複調用。
observer.addOnGlobalLayoutListener(listener) ,至此完成對該 View 對象的全局佈局監聽。
附上一張不完整的流程圖,使用在線工具 ProcessOn 畫的,挺好用的,推薦給你們:
##3.2 實際使用
上面只是標準使用流程,實際開發中咱們不會這麼多約束,下面看兩個實際的例子。值得注意的是,咱們一直所說的 View,實際上指的是 View 及其子類,好比 LinearLayout、ImageView、TextView等。
① 在 onCreate() 中獲取 View 的高度
在開發中,咱們有時須要在 onCreate() 方法中拿到一個view(任何View的子類)的寬高,這時候咱們直接經過 getWidth() 和 getHeight() 方法獲取的值均爲 0,由於真正的值要在 view 完成 onLayout() 以後才能夠返回。這時,咱們就能夠藉助 OnGlobalLayoutListener 監聽 view 的佈局改變,當 view 佈局發生改變且完成 onLayout() 後,就會調用 dispatchOnGlobal() 通知咱們,接下來就會走到回調方法 onGlobalLayout() 中去。
view.getViewTreeObserver().addOnGlobalLayoutListener(
new ViewTreeObserver.OnGlobalLayoutListener() {
@Override
public void onGlobalLayout() {
//1. do sth you want
width = view.getWidth();
height = view.getHeight;
Log.d("OnGlobalLayoutListener", "width:" + width + ",height:" + height);
//2. remove listener
// api 小於 16
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.JELLY_BEAN){
//使用過期方法
view.getViewTreeObserver().removeGlobalOnLayoutListener(this);
}
// api >= 16
else {
//使用替換方法
view.getViewTreeObserver().removeOnGlobalLayoutListener(this);
}
}
});
代碼已經寫得很清楚了,下面再補充兩點:
由於每次都是經過 getViewTreeObserver() 直接獲取 View 當前的observer,因此就沒再使用 isAlive() 判斷。
在介紹 remove 方法時,提到 removeGlobalOnLayoutListener() 方法已通過時,取而代之的是 removeOnGlobalLayoutListener() 方法。後者是在 JELLY_BEAN 版本才引入的,對應的 api 是 16。因爲我當前程序的 minSdkVersion 爲 14,因此須要根據實際版本號分開處理。其實,在本例中,是不須要分開處理的,咱們直接調用已過期的 removeGlobalOnLayoutListener() 方法便可,由於在前面分析過,兩者僅僅是名字上的差異。但我之因此這麼作,就是爲了演示如何判斷版本號並據此選擇對應的方案。畢竟有些方法系統只提供了高版本的實現,以前的版本就沒有對應的方法,此時咱們就必須本身實如今低版本上的功能了。
除了 OnGlobalLayoutListener,咱們還能夠藉助 OnPreDrawListener 實現上述功能。同時,OnPreDrawListener 還能夠幫助咱們實現 View 初始化加載時的動畫效果。下面再舉個例子,供你們參考以熟悉api,實際的開發中須要靈活運用。
② View 的初始化加載動畫
直接上代碼,在 onCreate() 方法中:
添加屬性動畫:
最終效果:
---------------------
https://blog.csdn.net/my_truelove/article/details/52653072
api