本篇文章已授權微信公眾號 dasu_Android(大蘇)獨家發布
先看個效果圖:

上一篇中,我們留了問題,在 Tv Home 界面這種很常見聚焦卡位放大動畫效果,我們這一篇就來看看 Leanback 庫是怎么實現的。
如果要我們自己實現的話,思路應該不難,就是寫個放大、縮小動畫,然后在卡位獲得焦點時應用放大動畫,失去焦點時應用縮小動畫,所以關鍵點只是在於如何進行封裝。那下面就來學學 Google Leanback 庫的 ItemView 縮放動畫的實現思路。
源碼分析
看源碼時,我習慣帶着目的性地去閱讀,這樣只要專注於理解跟目的相關的代碼即可,不用每行代碼地去分析,畢竟好多代碼目前能力有限,還啃不透。
那么,我們這次閱讀源碼的目的就是要搞清楚:卡位獲得焦點時放大、縮小動畫是如何實現的?
閱讀源碼時經常會碰到一個問題,那就是該從哪入手,從哪開始看?
這就是為什么我習慣帶着目的去閱讀,因為我們可以從目的分析,猜測我們需要的代碼應該在哪里,然后找到我們該從哪里閱讀,再一步步的去分析。
比如我們這次的任務,我們該從哪里入手閱讀源碼呢?首先,你得先了解一下 Leanback 庫的基本使用,這就是為什么我第一篇博客先簡單介紹了 Leanback 庫的使用。在上一篇博客里,可以看到,我們跟 Leanback 庫打交道的也就是下面這幾個類:
- ArrayObjectAdapter:作用類似於 List,裝填着整個頁面的數據,頁面數據其實是分兩級,以行為單位和以每一行中的 Item 為單位,所以整個頁面有一個 ArrayObjectAdapter(mRowsAdapter) 對象,它由許多行數據 ArrayObjectAdapter(rowAdapter) 對象組成,每行數據 rowAdapter 由許多 Item 組成。初始化一個 ArrayObjectAdapter 對象時,必須提供一個 Presenter 對象與它關聯。
 - ListRowPresenter:Leanback 庫中的 Presenter 作用都有些類似於 RecyclerView.Adapter,用於創建 ItemView 以及將數據綁定到 ItemView 上。
 - ListRow:可以理解成一個 Mode,也就是把每一行抽象封裝成一個 ListRow
 - BrowerFragment:用來展示可左右上下滑動的視頻列表界面,Leanback 已高度封裝,我們只需提供一個頁面的 ArrayObjectAdapter(mRowsAdapter) 對象,通過 
setAdapter()將數據設置進去,Leanback 會自動根據 ArrayObjectAdapter 里的數據以及和它關聯的 Presenter 將界面顯示出來。 
既然我們跟 Leanback 打交道只有這么幾點,那么切入點應該就在這些,畢竟我們對 Leanback 並不熟,那么只能從我們接觸到的地方來着手。
那么,再來想想,既然是要實現卡位獲得焦點和失去焦點時放大和縮小動畫,那么肯定是需要監聽 ItemView 的焦點變化,對吧?那我們通常是怎么做的,無外乎就是在 RecyclerView.Adapter 里的 onCreateViewHolder() 或 onBindViewHolder() 里監聽 ItemView 的焦點變化吧。
既然方向有了,那么就是要尋找 Leanback Home 界面對應的 RecyclerView.Adapter 是由哪個類實現的吧。我們也知道了在 Leanback 中 Presenter 的作用就是類似於 RecyclerView.Adapter,那么我們就先到 Presenter 里看一下。

ListRowPresenter 繼承自 RowPresenter 繼承自 Presenter,那么我們通過 Android Studio 跳到 Presenter 里看看。

Presenter 是個抽象類,有三個抽象方法, onCreateViewHolder()、onBindViewHolder()、onUnbindViewHolder(),跟 RecyclerView.Adapter 很像吧。根據我們之前的分析, ItemView 焦點的監聽通常是在 onCreateViewHolder() 或 onBindViewHolder() 里實現的,那么我們就去它的實現類 ListRowPresenter 里看一看。
ListRowPresenter 里找不到這三個方法的實現,那么就是由它的父類 RowPresenter 實現了,我們繼續通過 AS 跳到 RowPresenter 里看看。

onCreateViewHolder() 里的代碼我們不用去理解,當然你有時間有能力也可以,但現在主要是想搞懂它的卡位縮放動畫實現,所以我們只要看有沒有跟焦點監聽相關的代碼即可。很顯然,這里面並沒有找到,里面調用了幾個方法,有些方法一看就知道作用是創建某個對象的,你們也可以點進去看看,這里我們着重看一下 initializeRowViewHolder() 這個方法。

好像也沒找到跟焦點監聽的相關代碼,但是左邊有個標志,說明子類 ListRowPresenter 有復寫這個方法,那么代碼運行時實際上是調用的之類的方法,那么我們就點進去看看。

代碼很多,截圖也沒截完,但是我們發現了一個關鍵,找到了一個看名字就覺得跟焦點有關的方法 FocusHighlightHelper.setupBrowseItemFocusHighlight(),那么到底是不是呢?我們繼續點擊去看一下。

這個方法的介紹大意就是說設置每行的 ItemView 即卡位獲得焦點時的行為,這不就是指卡位的縮放動畫嘛,看來我們找到了。看代碼,是調用了 ItemBridgeAdapter 的 setFocusHighlight() 方法,繼續跟進看一下。


方法就只是設置了 mFocusHighlight 變量的值,而 ItemBridgeAdapter 是繼承 RecyclerView.Adapter 的,看來卡位的焦點監聽就是在這里實現了。看一下 onCreateViewHolder() 方法就知道是不是了。

presenterView 其實就是在 Presenter 里創建出來的 ItemView,所以這里其實就是對卡位設置焦點的變化監聽,viewHolder.mFocusChangeListener 應該是 View.OnFocusChangeListener 的對象,我們看一下。

mFocusChangeListener 初始化通過實例化一個類對象賦值,那么這個類應該就是實現 View.OnFocusChangeListener 接口的,我們繼續看一下。

這下清楚了吧,焦點發生變化時,會去調用 mFcousHightlight 的 onItemFocused() 方法。而 mFcousHightlight 是之前 FocusHighlightHelper.setupBrowseItemFocusHighlight() 里調用了 ItemBridgeAdapter 的 setFocusHighlight() 方法傳進來的,我們再看看它傳進來的是什么。

看一下 BrowseItemFocusHighlight 這個類做了什么。

所以,在 ItemBridgeAdapter 里注冊了焦點變化監聽,當焦點變化時,通知 mFcousHightlight 執行 onItemFocused() 方法,而 mFcousHightlight 是 BrowseItemFocusHighlight 類的實例,所以實際上是來執行上圖里的邏輯。看代碼也很容易明白,設置 ItemView 的選中狀態,並且去運行一個焦點動畫,那么卡位的縮放動畫應該就是在這里實現了。繼續看一下是不是。

該方法其實就是創建一個動畫對象,如果該對象有緩存的話,那么就從緩存中取出,沒有的話,就 new 一個,這種緩存的思想很值得學習。

該類就是實現了縮放的動畫效果了,通過實現 TimeAnimator.TimeListener 接口來實現的屬性動畫,當然縮放動畫也可以用其他方式實現,無非就是對 View 進行放大、縮小而已,這里就不具體去分析了,感興趣的可以自己來這里看看 Google 是如何實現縮放動畫的,后期有時間的話我可以再來分析一下這個類。
好了,到這里基本就分析完了,Leanback 庫關於卡位的縮放動畫的實現,從我們要從哪里着手開始閱讀源碼到找到焦點監聽實現的相關代碼到動畫實現的代碼整個過程基本就是這樣。以后大家在想看源碼的某個功能是如何實現時,可以參考這種思路來進行分析,一步步的去跟進,只找我們目標相關的代碼,這樣可以不至於被整個源碼的復雜性混亂掉。
最后,我想再總結一下上面的過程。
總結
- 卡位縮放動畫的實現在類 FocusHighlightHelper 的內部類 FocusAnimator 里實現。
 - 縮放動畫跟 ItemView 的綁定過程:
RowPresenter#onCreateViewHoler()
-> RowPresenter#initializeRowViewHolder()
-> ListRowPresenter#initializeRowViewHolder()
-> FocusHighlightHelper#setupBrowseItemFocusHighlight()
-> ItemBridgeAdapter#setFocusHighlight() - 簡單點說就是,當每一行的 View 要創建時,會注冊一個焦點監聽器,該行里的 ItemView 焦點發生變化時會從 ItemViwe 的 Tag 里取出縮放動畫對象,如果沒有則 new 一個,然后應用縮放動畫。
 

 最近剛開通了公眾號,想激勵自己堅持寫作下去,初期主要分享原創的Android或Android-Tv方面的小知識,感興趣的可以點一波關注,謝謝支持~~
