阿里针对布局方案和布局复用的开源框架(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阅读 202,905评论 5 476
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,140评论 2 379
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 149,791评论 0 335
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,483评论 1 273
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,476评论 5 364
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,516评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,905评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,560评论 0 256
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,778评论 1 296
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,557评论 2 319
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,635评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,338评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,925评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,898评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,142评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,818评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,347评论 2 342

推荐阅读更多精彩内容