阿里針對布局方案和布局復用的開源框架(VirtualLayout)

VirtualLayout下載

Github:https://github.com/alibaba/vlayout

VirtualLayout簡介

VirtualLayout是一個針對RecyclerView的LayoutManager擴展, 主要提供一整套布局方案和布局間的組件復用的問題。

VirtualLayout設計思路

通過定制化的LayoutManager,接管整個RecyclerView的布局邏輯;LayoutManager管理了一系列LayoutHelper,LayoutHelper負責具體布局邏輯實現的地方;每一個LayoutHelper負責頁面某一個范圍內的組件布局;不同的LayoutHelper可以做不同的布局邏輯,因此可以在一個RecyclerView頁面里提供異構的布局結構,這就能比系統自帶的LinearLayoutManager、GridLayoutManager等提供更加豐富的能力。同時支持擴展LayoutHelper來提供更多的布局能力。

VirtualLayout主要功能

默認通用布局實現,解耦所有的View和布局之間的關系: Linear, Grid, 吸頂, 浮動, 固定位置等。

① LinearLayoutHelper: 線性布局

②GridLayoutHelper: Grid布局, 支持橫向的colspan

③FixLayoutHelper: 固定布局,始終在屏幕固定位置顯示

④ScrollFixLayoutHelper: 固定布局,但之后當頁面滑動到該圖片區域才 顯示, 可以用來做返回頂部或其他書簽等

⑤FloatLayoutHelper:浮動布局,可以固定顯示在屏幕上,但用戶可以拖拽其位置

⑥ColumnLayoutHelper: 欄格布局,都布局在一排,可以配置不同列之間的寬度比值

⑦SingleLayoutHelper: 通欄布局,只會顯示一個組件View

⑧OnePlusNLayoutHelper: 一拖N布局,可以配置1-5個子元素

⑨StickyLayoutHelper: stikcy布局, 可以配置吸頂或者吸底

⑩StaggeredGridLayoutHelper:瀑布流布局,可配置間隔高度/寬度

上述默認實現里可以大致分為兩類:一是非fix類型布局,像線性、Grid、欄格等,它們的特點是布局在整個頁面流里,隨頁面滾動而滾動;另一類就是fix類型的布局,它們的子節點往往不隨頁面滾動而滾動。

所有除布局外的組件復用,VirtualLayout將用來管理大的模塊布局組合,擴展了RecyclerView,使得同一RecyclerView內的組件可以復用,減少View的創建和銷毀過程。

VLayout使用

// gradle

compile ('com.alibaba.android:vlayout:1.0.4@aar') { ? ?transitive =true}


①初始化LayoutManager

final RecyclerView recyclerView = (RecyclerView) findViewById(R.id.recycler_view);

final VirtualLayoutManager layoutManager =newVirtualLayoutManager(this);

recyclerView.setLayoutManager(layoutManager);

②設置回收復用池大小,(如果一屏內相同類型的 View 個數比較多,需要設置一個合適的大小,防止來回滾動時重新創建 View):

RecyclerView.RecycledViewPool ?viewPool = new RecyclerView.RecycledViewPool();

recyclerView.setRecycledViewPool(viewPool);

viewPool.setMaxRecycledViews(0,10);

③加載數據時有兩種方式:

第 一種:使用 DelegateAdapter, 可以像平常一樣寫繼承自DelegateAdapter.Adapter的Adapter, 只比之前的Adapter需要多重載onCreateLayoutHelper方法。 其他的和默認Adapter一樣。

DelegateAdapter delegateAdapter=newDelegateAdapter(layoutManager, hasStableItemType);

recycler.setAdapter(delegateAdapter);// 之后可以通過 setAdapters 或 addAdapter方法添加

DelegateAdapter.AdapterdelegateAdapter.setAdapters(adapters);

//OR

CustomAdapter adapter=newCustomAdapter(data,newGridLayoutHelper());

delegateAdapter.addAdapter(adapter);// 如果數據有變化,調用自定義

adapter 的 notifyDataSetChanged()adapter.notifyDataSetChanged();


第二種:當業務有自定義的復雜需求的時候, 可以繼承自VirtualLayoutAdapter, 實現自己的Adapter

public class ? ?MyAdapter extends ?VirtualLayoutAdapter{

......

}

MyAdapter myAdapter =newMyAdapter(layoutManager);//構造 layoutHelper 列表List helpers =newLinkedList<>();

GridLayoutHelper gridLayoutHelper =newGridLayoutHelper(4);

gridLayoutHelper.setItemCount(25);

helpers.add(gridLayoutHelper);

GridLayoutHelper gridLayoutHelper2 =newGridLayoutHelper(2);

gridLayoutHelper2.setItemCount(25);

helpers.add(gridLayoutHelper2);//將 layoutHelper 列表傳遞給 adapter

myAdapter.setLayoutHelpers(helpers);//將 adapter 設置給 recyclerViewrecycler.setAdapter(myAdapter);

在這種情況下,需要使用者注意在當LayoutHelpers的結構或者數據數量等會影響到布局的元素變化時,需要主動調用setLayoutHepers去更新布局模式。

? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ?VLayout運行效果

VirtualLayout組件復用的問題

比如碰到卡頓、類型轉換異常等等,都有可能是復用的問題引起的。

在使用 DelegateAdapter 的時候,每一個 LayoutHelper 都對應于一個 DelegateAdapter.Adapter,一般情況下使用方只需要提供自定義的 DelegateAdapter.Adapter,然后按照正常的使用方式使用。

但這里有個問題,不同的 DelegateAdapter.Adapter 之間,他們的 itemType 是不是一樣的?這里有一個選擇:在 DelegateAdapter 的構造函數里有個 hasConsistItemType 參數(默認是 false ):

當hasConsistItemType=false的時候,即使不同 DelegateAdapter.Adapter 里返回的相同的 itemType,對于 DelegateAdapter 也會將它轉換成不同的值,對于 RecyclerView 來說它們是不同的類型。

當hasConsistItemType=true 的時候,不同的 DelegateAdapter.Adapter 之間返回相同的 itemType 的時候,他們之間是可以復用的。

因此如果沒有處理好這一點,會導致 ViewHolder 的類型轉換異常等 bug。有一篇更加詳細的資料可參考:PairFunction

補充:后來發現一個 bug,當 hasConsistItemType=true,在同一位置數據變化,前后構造了不一樣的 Adapter,它們返回的 itemType 一樣,也會導致類型轉換出錯,詳見:#182,目前采用人工保證返回不同的 itemType 來規避。

VirtualLayout設置每種類型回收復用池的大小

在 README 里寫了這么一段 demo:viewPool.setMaxRecycledViews(0, 10);,很多人誤以為只要這么設置就可以了,實際上有多少種類型的 itemType,就得為它們分別設置復用池大小。比如:

viewPool = new RecyclerView.RecycledViewPool();

recyclerView.setRecycledViewPool(viewPool);

viewPool.setMaxRecycledViews(0,5);

viewPool.setMaxRecycledViews(1,5);

viewPool.setMaxRecycledViews(2,5);

viewPool.setMaxRecycledViews(3,10);

viewPool.setMaxRecycledViews(4,10);

viewPool.setMaxRecycledViews(5,10);

...

VirtualLayout混淆問題

如果碰到release包(混淆過)無法正常運行,debug包(一般未混淆)可正常運行,檢查一下混淆配置是否完整:

-keepattributesInnerClasses-keepclasscom.alibaba.android.vlayout.ExposeLinearLayoutManagerEx{ *;}-keepclassandroid.support.v7.widget.RecyclerView$LayoutParams{ *;}-keepclassandroid.support.v7.widget.RecyclerView$ViewHolder{ *;}-keepclassandroid.support.v7.widget.ChildHelper{ *;}-keepclassandroid.support.v7.widget.ChildHelper$Bucket{ *;}-keepclassandroid.support.v7.widget.RecyclerView$LayoutManager{ *;}

VirtualLayout下拉刷新和加載更多

VLayout 只負責布局,下拉刷新和加載更多需要業務方自己處理,當然可能存在和一些下拉刷新控件不兼容的 bug。

個人推薦一個刷新控件:SmartRefreshLayout

下拉刷新,有很多框架是通過判斷 RecyclerView 的第一個 view 的 top 是否為 0 來觸發下拉動作。VLayout 里在處理背景、懸浮態的時候加入了一些對 LayoutManager 不可見的 View,但又真實存在與 RecyclerView 的視圖樹里,建議使用 layoutManager.getChildAt(0) 來獲取第一個 view。

加載更多,可以通過 recyclerView 的滾動狀態來觸發 load-more 事件,需要使用方注冊一個 OnScrollListener:

RecyclerView.OnScrollListener onScrollListener =newRecyclerView.OnScrollListener() {

public void onScrollStateChanged(RecyclerView recyclerView,intnewState) {}

public void onScrolled(RecyclerView recyclerView,intdx,intdy) {

//hasMore: status of current page, means if there's more data, you have to maintain this status

if(hasMore) { ? ? ? ? ?

?VirtualLayoutManager lm = (VirtualLayoutManager)recyclerView.getLayoutManager();

intfirst=0, last=0, total=0;? ? ? ? ? ? ? ??

first = ((LinearLayoutManager)lm).findFirstVisibleItemPosition(); ? ? ? ??

last = ((LinearLayoutManager)lm).findLastVisibleItemPosition(); ? ??

total = recyclerView.getAdapter().getItemCount();if(last >0&& last >= total? - earlyCountForAutoLoad) {

//earlyCountForAutoLoad: help to trigger load more listener earlier

//TODO trigger loadmore listener

? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? }? ? ? ? ? ? ? ?

? ? ? ? ? ? ? ? ? ? }? ? ? ? ?

? ? ?}? ? ??

? }

VirtualLayout橫向滑動

沒有實現橫向滾動的 LayoutHelper,因為 LayoutHelper 目前只能做靜態的布局,對于跟數據綁定的動態橫向滾動布局,比如 ViewPager 或者 RecyclerView ,建議使用組件的形式提供。也就是一個 LinearLayoutHelper 包一個 Item,這個 Item 是 ViewPager 或者橫向滾動的 RecyclerView,且它們是可以和整個頁面的 RecyclerView 共用一個回收復用池的。

VirtualLayout設置背景圖后觸發循環布局

給 LayoutHelper 設置背景圖的時候,由于這個過程是在布局 view 的階段,設置了圖片會觸發一次新的 layout,從而又導致觸發一次背景圖設置,最終進入死循環,因此需要使用方在設置背景圖的時候判斷當前圖片是否已經加載過一次并且成功,如果綁定過一次就不需要再設置圖片了,阻斷死循環的路徑。 具體做法是:在 BaseLayoutHelper.LayoutViewBindListener 的 onBind() 方法里判斷是否成功綁定過該背景圖。在BaseLayoutHelper.LayoutViewUnBindListener 的 onUnbind() 方法里清楚綁定成功與否的狀態。 在使用方的圖片加載成功回調函數里設置一下圖片加載成功的狀態,可以自行維護一個 map 或者給 View 設置一個 tag 標記。

VirtualLayout在可滾動區域里嵌套使用 vlayout 的 RecyclerView

不太建議嵌套滾動,除非手勢不沖突;如果要完全展開 vlayout 里的內容,犧牲滾動復用,可以調用 VirtualLayoutManager 的 setNoScrolling(true); 方法設置一下。

VirtualLayout為 GridLayoutHelper 的設置自定義 SpanSizeLookup

在 SpanSizeLookup 中,public int getSpanSize(int position) 方法參數的 position 是整個頁面的 position 信息,需要獲取當前 layoutHelper 內的相對位置,需要減去一個偏移量,即 position - getStartPosition()。

VirtualLayout獲取 DelegateAdapter 里數據的相對位置

在 DelegateAdapter 里有 findOffsetPosition(int absolutePosition) 方法,傳入整個頁面的絕對位置,獲取相對位置。 或者用

public static abstract class Adapter<VH extends RecyclerView.ViewHolder>extendsRecyclerView.Adapter{

public abstract LayoutHelper onCreateLayoutHelper();

protectedvoidonBindViewHolderWithOffset(VH holder,intposition,intoffsetTotal) {? ? ?

?}

??}

中的 onBindViewHolderWithOffset() 方法代替傳統的 onBindViewHolder() 方法,其中的 position 參數也是相對位置。

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 230,563評論 6 544
  • 序言:濱河連續發生了三起死亡事件,死亡現場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發現死者居然都...
    沈念sama閱讀 99,694評論 3 429
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 178,672評論 0 383
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 63,965評論 1 318
  • 正文 為了忘掉前任,我火速辦了婚禮,結果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 72,690評論 6 413
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發上,一...
    開封第一講書人閱讀 56,019評論 1 329
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 44,013評論 3 449
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 43,188評論 0 290
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當地人在樹林里發現了一具尸體,經...
    沈念sama閱讀 49,718評論 1 336
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 41,438評論 3 360
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發現自己被綠了。 大學時的朋友給我發了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 43,667評論 1 374
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 39,149評論 5 365
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質發生泄漏。R本人自食惡果不足惜,卻給世界環境...
    茶點故事閱讀 44,845評論 3 351
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 35,252評論 0 28
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 36,590評論 1 295
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 52,384評論 3 400
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 48,635評論 2 380

推薦閱讀更多精彩內容