为什么要使用vlayout
[vlayout]是对RecyclerView的LayoutManage的扩展,所以它接管了RecyclerView的整个布局方式,可以通过它实现各种各样的布局方式,非常的灵活。它提供的默认布局方式解耦所有的View和布局之间的关系: Linear, Grid, 吸顶, 浮动, 固定位置等,大致分为两类:一是非fix类型布局,像线性、Grid、栏格等,它们的特点是布局在整个页面流里,随页面滚动而滚动;另一类就是fix类型的布局,它们的子节点往往不随页面滚动而滚动。除了这个,它用来管理大的模块布局组合,使得同一RecyclerView内的组件可以复用,减少View的创建和销毁过程
通过demo学习
上面这张图是通过运行vlayout提供的demo得到的效果图,看起来挺多view,也挺复杂的,很难相信这只是一个RecyclerView,是不是也间接证明了vLayout的强大。
下面来拆解下这么复杂的布局是如何在一个RecyclerView里实现的。
开始使用
基本用法
final VirtualLayoutManager layoutManager = new VirtualLayoutManager(this);
recyclerView.setLayoutManager(layoutManager);
final DelegateAdapter delegateAdapter = new DelegateAdapter(layoutManager, true);
recyclerView.setAdapter(delegateAdapter);
final List<DelegateAdapter.Adapter> adapters = new LinkedList<>();
//需要添加新的布局,就定义一个新的adapter
delegateAdapter.setAdapters(adapters);
需要定义DelegateAdapter.Adapter的子类,如下
static class SubAdapter extends DelegateAdapter.Adapter<MainViewHolder> {
private Context mContext;
private LayoutHelper mLayoutHelper;
private LayoutParams mLayoutParams;
private int mCount = 0;
public SubAdapter(Context context, LayoutHelper layoutHelper, int count) {
this(context, layoutHelper, count, new LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, 300));
}
public SubAdapter(Context context, LayoutHelper layoutHelper, int count, @NonNull LayoutParams layoutParams) {
this.mContext = context;
this.mLayoutHelper = layoutHelper;
this.mCount = count;
this.mLayoutParams = layoutParams;
}
@Override
public LayoutHelper onCreateLayoutHelper() {
return mLayoutHelper;
}
@Override
public MainViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
return new MainViewHolder(LayoutInflater.from(mContext).inflate(R.layout.item, parent, false));
}
@Override
public void onBindViewHolder(MainViewHolder holder, int position) {
// only vertical
holder.itemView.setLayoutParams(
new LayoutParams(mLayoutParams));
}
@Override
protected void onBindViewHolderWithOffset(MainViewHolder holder, int position, int offsetTotal) {
((TextView) holder.itemView.findViewById(R.id.title)).setText(Integer.toString(offsetTotal));
}
@Override
public int getItemCount() {
return mCount;
}
}
添加一个普通的布局:adapter
adapters.add(new SubAdapter(this, new LinearLayoutHelper(), 1) {
@Override
public void onViewRecycled(MainViewHolder holder) {
if (holder.itemView instanceof ViewPager) {
((ViewPager) holder.itemView).setAdapter(null);
}
}
@Override
public MainViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
if (viewType == 1)
return new MainViewHolder(
LayoutInflater.from(VLayoutActivity.this).inflate(R.layout.view_pager, parent, false));
return super.onCreateViewHolder(parent, viewType);
}
@Override
public int getItemViewType(int position) {
return 1;
}
@Override
protected void onBindViewHolderWithOffset(MainViewHolder holder, int position, int offsetTotal) {
}
@Override
public void onBindViewHolder(MainViewHolder holder, int position) {
if (holder.itemView instanceof ViewPager) {
ViewPager viewPager = (ViewPager) holder.itemView;
viewPager.setLayoutParams(new LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, 200));
// from position to get adapter
viewPager.setAdapter(new PagerAdapter(this, viewPool));
}
}
});
看下效果图
添加一个悬浮的布局:adapter
FloatLayoutHelper layoutHelper = new FloatLayoutHelper();
layoutHelper.setAlignType(FixLayoutHelper.BOTTOM_LEFT);
layoutHelper.setDefaultLocation(100, 400);
LayoutParams layoutParams = new LayoutParams(150, 150);
adapters.add(new SubAdapter(this, layoutHelper, 1, layoutParams));
效果图:
分析:
- FloatLayoutHelper是悬浮布局的实现类
- setAlignType 有四种: TOP_LEFT, TOP_RIGHT, BOTTOM_LEFT, BOTTOM_RIGHT
- setDefaultLocation: x, y 代表与边界的距离,和alignType有关系,比如:TOP_LEFT, x 是与左边的距离,y是与上边的距离
- 可以随手势拖动
添加 宽比高为2的布局
LinearLayoutHelper layoutHelper1 = new LinearLayoutHelper();
layoutHelper1.setAspectRatio(2.0f);
adapters.add(new SubAdapter(this, layoutHelper1, 1));
效果图:
分析:
setAspectRatio(2.0f)
设置宽比高比例为2的布局
添加子列表
LinearLayoutHelper layoutHelper2 = new LinearLayoutHelper();
layoutHelper2.setAspectRatio(4.0f);
layoutHelper2.setDividerHeight(10);
layoutHelper2.setMargin(10, 30, 10, 10);
layoutHelper2.setPadding(10, 30, 10, 10);
layoutHelper2.setBgColor(0xFFF5A623);
adapters.add(new SubAdapter(this, layoutHelper2, 6) {
@Override
public void onBindViewHolder(MainViewHolder holder, int position) {
if (position % 2 == 0) {
LayoutParams layoutParams = new LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, 300);
layoutParams.mAspectRatio = 5;
holder.itemView.setLayoutParams(layoutParams);
}
}
});
分析:
我们看到的是一个垂直方向的列表,如果想要变成一个水平方向上的列表,需要修改VirtualLayoutManager
的orientation
layoutManager.setOrientation(VirtualLayoutManager.HORIZONTAL);
效果图:
添加一个sticky布局,黏住但不固定,手势之后会变化
StickyLayoutHelper layoutHelper = new StickyLayoutHelper();
layoutHelper.setOffset(100);
layoutHelper.setAspectRatio(4);
adapters.add(new SubAdapter(this, layoutHelper, 1, new LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, 100)));
添加只有一个view的布局
SingleLayoutHelper singleLayoutHelper = new SingleLayoutHelper();
singleLayoutHelper.setBgColor(Color.rgb(135, 225, 90));
singleLayoutHelper.setAspectRatio(4);
singleLayoutHelper.setMargin(10, 20, 10, 20);
singleLayoutHelper.setPadding(10, 10, 10, 10);
adapters.add(new SubAdapter(this, singleLayoutHelper, 2, new LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, 100)));
分析:
当你使用SingleLayoutHelper时,如果SubAdapter的数量是多少,界面上只有显示一个view.
添加一个表格布局
ColumnLayoutHelper layoutHelper = new ColumnLayoutHelper();
layoutHelper.setBgColor(0xff00f0f0);
layoutHelper.setWeights(new float[]{40.0f, Float.NaN, 40});
adapters.add(new SubAdapter(this, layoutHelper, 5) {
@Override
public void onBindViewHolder(MainViewHolder holder, int position) {
if (position == 0) {
LayoutParams layoutParams = new LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, 300);
layoutParams.mAspectRatio = 4;
holder.itemView.setLayoutParams(layoutParams);
} else {
LayoutParams layoutParams = new LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, 300);
layoutParams.mAspectRatio = Float.NaN;
holder.itemView.setLayoutParams(layoutParams);
}
}
});
分析:
setWeights(new float[]{40.0f, Float.NaN, 40})
确定了每列的比重权值,默认总权值是100,第一列和第三列是40,其他都是任意。我们看第一行为什么是这样的一个高度,因为下面的代码layoutParams.mAspectRatio = 4
限制了宽高比是4。
添加一个OnePlusNLayoutHelper(数量为2)
需要先了解下它支持的布局方式有下面几种
* 1 + 0
* -------------------------
* | 1 |
* -------------------------
*
* 1 + 1
* -------------------------
* | | |
* | 1 | 2 |
* | | |
* -------------------------
*
* 1 + 2
* -------------------------
* | | 2 |
* | 1 |-----------|
* | | 3 |
* -------------------------
*
* 1 + 3
* -------------------------
* | | 2 |
* | 1 |-----------|
* | | 3 | 4 |
* -------------------------
* 1 + 4
* -------------------------
* | | 2 |
* | 1 |-----------|
* | | 3 | 4 | 5 |
* -------------------------
比如下面
OnePlusNLayoutHelper helper = new OnePlusNLayoutHelper();
helper.setBgColor(0xff876384);
helper.setAspectRatio(4.0f);
helper.setColWeights(new float[]{40f, 45f});
helper.setMargin(10, 20, 10, 20);
helper.setPadding(10, 10, 10, 10);
adapters.add(new SubAdapter(this, helper, 2));
分析:
setAspectRatio(4.0f)
设置整体布局的宽高比,setColWeights(new float[]{40f, 45f})
设置列的权重,总权重是100,这里设置了第一列权重是40,第二列权重是45
添加一个OnePlusNLayoutHelper(数量为4)
OnePlusNLayoutHelper helper = new OnePlusNLayoutHelper();
helper.setBgColor(0xffef8ba3);
helper.setAspectRatio(2.0f);
helper.setColWeights(new float[]{40f});
helper.setRowWeight(30f);
helper.setMargin(10, 20, 10, 20);
helper.setPadding(10, 10, 10, 10);
adapters.add(new SubAdapter(this, helper, 4) {
@Override
public void onBindViewHolder(MainViewHolder holder, int position) {
super.onBindViewHolder(holder, position);
LayoutParams lp = (LayoutParams) holder.itemView.getLayoutParams();
if (position == 0) {
lp.rightMargin = 1;
} else if (position == 1) {
} else if (position == 2) {
lp.topMargin = 1;
lp.rightMargin = 1;
}
}
});
分析:
当数量为4,为什么是这样一个展示页面,参考上面的(1+3)
setAspectRatio(2.0f)
设置整个布局宽高比是2,setColWeights(new float[]{40f})
设置第一列权重是40,
setRowWeight(30f)
设置第一行的权重是30,行总权重是100
添加一个固定布局
FixLayoutHelper layoutHelper = new FixLayoutHelper(FixLayoutHelper.TOP_RIGHT, 20, 20);
adapters.add(new SubAdapter(this, layoutHelper, 1) {//29
@Override
public void onBindViewHolder(MainViewHolder holder, int position) {
super.onBindViewHolder(holder, position);
LayoutParams layoutParams = new LayoutParams(200, 200);
holder.itemView.setLayoutParams(layoutParams);
}
});
分析:
- 固定类型(alignType)有四种:TOP_LEFT(左上角),TOP_RIGHT(右上角),BOTTOM_LEFT(左下角),BOTTOM_RIGHT(右下角)
- 固定的边距,如上x=20, y=20, 所以就得到上面的固定样式
- LayoutParams layoutParams = new LayoutParams(200, 200); 设置固定布局的子view的宽高尺寸
添加一个方格
GridLayoutHelper layoutHelper = new GridLayoutHelper(4);
layoutHelper.setWeights(new float[]{20f, 26.665f});
layoutHelper.setMargin(7, 0, 7, 0);
layoutHelper.setHGap(3);
adapters.add(new SubAdapter(this, layoutHelper, 8));
分析:
- GridLayoutHelper(4)类似于GridView,设置列数量位4
- setWeights(new float[]{20f, 26.665f})设置第一列和第二列的权重分别是20,26.665,其他列均分,列总权重是100,计算一下,第三列和第四列的权重是26.6675
- setHGap(3)类似于GridView,设置水平间距是3像素
- new SubAdapter(this, layoutHelper, 8) 设置方格总数量是8
添加一个Staggered布局
final StaggeredGridLayoutHelper helper = new StaggeredGridLayoutHelper(2, 10);
helper.setMargin(20, 10, 10, 10);
helper.setPadding(10, 10, 20, 10);
helper.setBgColor(0xFF86345A);
adapters.add(new SubAdapter(this, helper, 27) {
@Override
public void onBindViewHolder(MainViewHolder holder, int position) {
super.onBindViewHolder(holder, position);
LayoutParams layoutParams = new LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, 200);
if (position % 2 == 0) {
layoutParams.mAspectRatio = 1.0f;
} else {
layoutParams.height = 340 + position % 7 * 20;
}
holder.itemView.setLayoutParams(layoutParams);
}
});
分析:
- StaggeredGridLayoutHelper(2, 10)设置列数量为2,间距为10像素
- new SubAdapter(this, helper, 27) 设置子view总数量为27
- 下面这段代码是设置每个子view的宽高尺寸,所以才形成了Staggered
LayoutParams layoutParams = new LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, 200);
if (position % 2 == 0) {
layoutParams.mAspectRatio = 1.0f;
} else {
layoutParams.height = 340 + position % 7 * 20;
}
vLayout提供的默认布局
一般情况下,这些都是够用的,如果出现不够的情况,可以参考上面的布局来实现自己的。
源码分析其实现的原理
因为篇幅有限,这里就直接定位到其实现的最底层代码,定位到VirtualLayoutManager
的public void setLayoutHelpers(@Nullable List<LayoutHelper> helpers)
方法,通过这个方法我们注意到上面我们所调用的helper,都已经转换成配置信息了,最后一段代码requestLayout();
重新刷新布局,接下来会调用public void onLayoutChildren(RecyclerView.Recycler recycler, RecyclerView.State state)
,然后就会调用public void layoutChild(View child, int left, int top, int right, int bottom)
对每个子view进行布局
下面是我画的简单的调用示意图
有兴趣交流和讨论的,请在下面发评论。
有兴趣一起学习和讨论kotlin,请加Q群:248445350
[vlayout]:https://github.com/alibaba/vlayout