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 参数也是相对位置。