原文連接html
好久好久之前,我分享過一篇文章,介紹了團隊推出的一種異構的自定義 LayoutManger
的實現,它是基於 LinearLayoutManager
擴展實現的,這個項目的名字叫 vlayout,也許你之前據說過,或者在 github 上看到過,雖然還存在很多 bug 和不足,但能獲得很多同窗的支持,真是感到欣慰。java
關於它的設計思路,其實在文章《Tangram 的基礎 —— vlayout》裏已經有過一些介紹,還有一些關於它的使用、功能介紹:vlayout使用說明(一)、vlayout使用說明(二)。其實它不少細節能夠展開介紹,其中可能涉及到 RecyclerView
自身的源碼解讀之類的。這裏我想分享 vlayout 裏其中一種 LayoutHelper
(LayoutHelper
負責具體的佈局邏輯,是 vlayout 裏抽象出的一個層次,能夠參考前文連接詳細瞭解)的設計與實現。android
說到這裏,這篇文章的標題其實應該叫作:vlayout 裏一種自定義 LayoutHelper 的設計與實現,考慮到可能有讀者不明白,因此用『自定義 LayoutManager 的一種設計與實現』代替了一下。git
好,下面開始進入主題。github
在 vlayout 裏,提供了多種類型的 LayoutHelper
來負責佈局邏輯,將不一樣類型的 LayoutHelper
組合到一個 RecyclerView
裏,實現了在同一個頁面異構的、扁平化的佈局能力。在考慮到一種佈局結構須要對應實現一個 LayoutHelper
的時候,老是要考慮到將 item 扁平化地佈局,這樣才能最大程度發揮 RecyclerView
的回收複用能力。緩存
如今若是有這樣一種需求場景:在組件 A 以兩列布局模式的數據裏流,以 4 個一組爲單位,插入一塊其餘佈局類型的組件,好比說是 3 列布局的組件 B。按照原先的作法,可能須要按照視覺樣式,將 4 個一組的組件 A,包裝到一個 GridLayoutHelper
裏,而後將中插的每一塊組件 B 區域,包裝到另外一個 GridLayoutHerlper
裏,這兩種 GridLayoutHerlper
的主要區別在於列數不一樣。bash
這樣子作有一個小問題在於,從產生數據列表到 UI 展現列表的鏈路裏,總有一個環節須要按照視覺樣式來對數據進行切割分組操做。將這種數據切割的操做暴露給業務方,老是讓人難受的,並且很容易出錯。在更加複雜的業務場景下,數據來源方多是多種多樣的,它只關心數據的吐出,而不是按照 UI 樣式或者某一特定框架的協議來轉換數據。架構
所以有必要側重在端上進行設計,若是進一步考慮這個需求,能夠將這種結構描述成一種樹狀結構。以上圖爲例,也就說處於根節點的的組件 A 列表,都是用 2 列結構的 GridLayoutHelper
來佈局的,而根節點的組件列表裏某些位置,插入一個組件 B 的列表,它們是用 3 列結構的 GridLayoutHelper
來佈局的。這種描述可能有點抽象,以普通場景下、非 RecyclerView
裏實現場景爲例,也就是說假如要寫一個自定義佈局來繪製上述界面,其實就是寫一個能進行 2 或 3 列布局的 ViewGroup,而後按照想要的結構自由組織就好了,而後最終咱們就能獲得一個 View 的樹。可是這種嵌套的結構 View 在 RecyclerView
只能做爲一個總體來進行回收複用,還不夠扁平化,回收複用的粒度就達不到咱們的要求,因此就提出了上述的邏輯上具有嵌套能力的樹狀結構。有了這樣的邏輯結構來描述,就能夠提供更加普適性的佈局能力。解決這個問題的 LayoutHelper
就是本文要介紹的內容,它能夠接收帶邏輯上帶嵌套結構的數據描述,同時又在最終佈局的時候將每個 item 組件扁平化地、直接地掛載到 RecyclerView
下。app
有了描述佈局的結構,接下來就是要按照設計來實現佈局能力,若是是普通的自定義 ViewGroup
,狀況還比較容易,可是要結合到 RecyclerView
裏,必須時時牢記扁平化實現,在 vlayout 的場景裏,就是要新建一種 LayoutHelper
來實現。 以前有作過幾回這樣的嘗試。第一種思路是像正常 View 層級同樣寫一個大的自定義 ViewGroup
做爲總體的一個 RecyclerView
的組件,內部在作回收複用的分發處理,這樣其實沒有作到真正的扁平化,並且須要維護內部的子 View
佈局高度消耗,以及與 RecyclerView
佈局機制的協同,過程會比較麻煩,稍加嘗試以後放棄。框架
第二種方式是實現一種 LayoutHelper
,讓它像系統 View
同樣具有嵌套描述的能力。一開始將它想象的比較複雜,能夠按照任意層次結構去嵌套、擺放,結果致使設計與實現都很是複雜。
嘗試了前兩種方案,實現成本和結果都不太理想,因而來從新審視最初的目標。並作了如下幾點思考:1. 要在必定領域內解決問題,限定邊界,不能單純追求更大的靈活性而提高複雜度。2. 將問題簡化爲行級佈局,由於自己 vlayout 裏每一種 LayoutHelper
都是按行來佈局的,LayoutHelper
內部每一次佈局都是填滿一整行的空間,而不一樣 LayoutHelper
之間也都是按行劃分的,不會出現同一行內兩個不一樣的 LayoutHelper
混搭。
因而,基於前面第二種方案進行簡化,仍是實現一種自定義 LayoutHelper
,在它引入了一種叫 RangeStyle
的結構來描述每一塊區域的相對父節點起始位置以及它的樣式,RangeStyle
能夠按照設計上的邏輯嵌套結構來嵌套描述。這樣最初設計上的邏輯樹狀結構就有了實體來承載。而在佈局的時候,自定義 LayoutHelper
會獲取到當前將要佈局的 position,經過這個 position 來它所對應的 RangeStyle
節點信息,經過它提供的樣式,好比 margin、padding、spanCount 等來控制當前 LayoutHelper
的行爲。這樣每次佈局的組件就像在其餘 LayoutHelepr
裏的同樣是直接掛載到 RecyclerView
下的,也達到了嵌套的描述、扁平化的實現的預設目標。
基於這樣的思路,思考起來就很是清晰,與總體的 vlayout 設計自己就契合的很是好,實現起來也比較順利。固然實現起來仍是有一些細節要調測,好比計算總體的 margin、padding 須要累加 RangeStyle
樹裏節點下的相同位置的邊距;每一塊區域的背景色也要像真的一層嵌套結構那樣按照預期的層級堆疊排放。
我將它稱之爲 RangeGridLayoutHelper
,主要是目由於前支持用來作這種嵌套的流式佈局的實現。它的詳細源碼能夠參考:RangeGridLayoutHelper。
若是直接使用 vlayout,RangeGridLayoutHelper
的使用代碼看起來多是這樣的:
RangeGridLayoutHelper layoutHelper = new RangeGridLayoutHelper(4);
layoutHelper.setBgColor(Color.GREEN);
layoutHelper.setWeights(new float[]{20f, 26.665f});
layoutHelper.setPadding(15, 15, 15, 15);
layoutHelper.setMargin(15, 15, 15, 15);
layoutHelper.setHGap(10);
layoutHelper.setVGap(10);
GridRangeStyle rangeStyle = new GridRangeStyle();
rangeStyle.setBgColor(Color.RED);
rangeStyle.setSpanCount(2);
rangeStyle.setWeights(new float[]{46.665f});
rangeStyle.setPadding(15, 15, 15, 15);
rangeStyle.setMargin(15, 15, 15, 15);
rangeStyle.setHGap(5);
rangeStyle.setVGap(5);
layoutHelper.addRangeStyle(4, 7, rangeStyle);
GridRangeStyle rangeStyle1 = new GridRangeStyle();
rangeStyle1.setBgColor(Color.YELLOW);
rangeStyle1.setSpanCount(2);
rangeStyle1.setWeights(new float[]{46.665f});
rangeStyle1.setPadding(15, 15, 15, 15);
rangeStyle1.setMargin(15, 15, 15, 15);
rangeStyle1.setHGap(5);
rangeStyle1.setVGap(5);
layoutHelper.addRangeStyle(8, 11, rangeStyle1);
adapters.add(new SubAdapter(this, layoutHelper, 16));
複製代碼
vlayout 雖然提供了異構佈局的能力,可是我也認可,目前是接口(主要是 DelegateAdapter
以及各類 LayoutHelper
提供的接口)並不易用,開發者很難拋開那些具體的細節而後快速寫出頁面,在 Github 上也有同窗反饋過這個問題。之因此這樣實際上是由於:咱們團隊本身也並非直接使用 vlayout 進行開發,而是經過 Tangram 庫來間接使用 vlayout,在 Tangram 主要是經過 JSON 數據來描述總體頁面的結構,並封裝了一個自定義的 Adater,它接收 Tangram 協議 JSON 數據,來自動建立、維護各類 LayoutHelper
的內部信息,這樣就屏蔽了 vlayout 這些複雜的細節,而不是在使用 DelegateAdapter
的時候手動維護各個 LayoutHelper
。建議到 Tangram 工程下進一步瞭解詳細信息,對於原來使用 vlayout 開發的 app 來講,理論上均可以遷移到 Tangram 架構,這樣整個頁面的渲染就能夠由數據來驅動,提高頁面的動態性。
那麼說到動態性,Tangram 解決了頁面結構的問題,至於每個 RecyclerView
裏的 item,也能夠稱之爲組件,它的動態性,咱們有另一個方案—— VirtualView,它是經過自定義 XML 來描述組件的佈局結構,而後由自定義引擎解析 XML 數據並渲染出界面的方案。就比如在 Android 裏寫 XML 佈局文件而後渲染展現,當動態下發 XML 數據的時候,組件樣式也就能動態更新了。有興趣的也能夠進一步瞭解一下:
有了這兩件利器,當下一次 PD 跑過來問你線上 XXX 能不能調整一下樣式結構的時候,你就能夠回答說『能夠』,而不是等到下一次發版。並且咱們的重點功能、平常迭代,也主要是圍繞 Tangram + VirtualView 來進行,這樣能夠更快用上最新特性。
RecyclerView
的資料最後,想說一點的是,整個 RecyclerView
體系的設計雖然很是強大、擴展性更好,但對於使用方來講,想要擴展一個自定義的 LayoutManager
仍是比較麻煩的,這要求開發者深刻理解 RecyclerView
體系的設計及原理,這裏收集了部分以前閱讀過的資料,對於你們深刻理解 RecyclerView
或者 vlayout 都有好處: