你们好,今天又带来了项目中具体遇到的需求。作一个首界面,该首界面有不少功能块,同时这些功能块是动态的,由于登陆的人的权限的不一样,会显示不一样的功能块,由于功能模块的数量不必定,因此当功能块多的时候,整个界面是能够上下滑动的。其实相似有点像淘宝的首界面。以下图所示。javascript
1.首先由于功能块多的时候,须要界面可以滚动,因此我想到最外面用的ScrollView,而后ScrollView中包含了一个竖向排布的LienarLayout。
而后在放入一个ImageView显示这个顶部图片:
java
而后须要二个横向的LinearLayout,用来显示这个大的分类标题:android
而后再放入二个GridView显示功能模块:git
OK。我发现个人首界面写好了以后:github
<ScrollView>
<LinearLayout>
<ImageView> <顶部图片>
<LinearLayout><View/><TextView/><LinearLayout> <个人服务标题栏>
<GridView /> <个人服务功能块>
<LinearLayout><View/><TextView/><LinearLayout> <个人功能标题栏>
<GridView /> <个人功能功能块>
<LinearLayout>
<ScrollView/>复制代码
1.布局的内容很是之多。维护很不方便
2.定制化功能差了不少,若是我下次想在《个人服务》和《个人功能》大功能分类中,再多加一个《个人售后》,又的去布局中查找相应的位置,而后去去添加新的布局代码,或者是我想删除模块功能了,我还得去布局中找出来,而后去删除它。反正就是很麻烦。
3.当前这个界面还算简单的,毕竟功能块都是以相似九宫格的形式呈现,若是哪天多了个《个人售后》,而后这个《个人售后》不是以这种九宫格的形式呈现,整个界面中有各类各样的布句呈现,管理会变的十分麻烦。算法
上面说到过。咱们的界面有没有像淘宝的首界面,各类布句杂糅在一块儿,而后又能够上下滚动,没错,那我就模仿淘宝的首页同样写个不就好了么。框架
而后里面的不一样布局方式使用不一样的LayoutManager不就能够了么。固然由于前面讲了咱们能够模仿淘宝的首页来写,那咱们固然是使用阿里巴巴开源的vlayout。ide
这时候介绍一下咱们的主角:vlayout函数
vlayout is a powerfull LayoutManager extension for RecyclerView, it provides a group of layouts for RecyclerView. Make it able to handle a complicate situation when grid, list and other layouts in the same recyclerview.布局
咱们能够看到,vlayout是一个强大的RecycleView的LayoutManager,它能够帮我在RecycleView中呈现多种布局方式。
首先,vlayout的基本使用方法,其余大神写的不少也很好。我也不会浪费时间再写一遍:
请看这篇,基本就可以对Vlayout有所了解及使用了:
Android开源库V - Layout:淘宝、天猫都在用的UI框架,赶忙用起来吧!
Android开源库V - Layout:淘宝、天猫都在用的UI框架,赶忙用起来吧!
Android开源库V - Layout:淘宝、天猫都在用的UI框架,赶忙用起来吧!
咱们回头再来看咱们上面的具体的项目需求:
咱们首先整个activity的布局变为了:
<?xml version="1.0" encoding="utf-8"?>
<android.support.v7.widget.RecyclerView xmlns:android="http://schemas.android.com/apk/res/android" android:id="@+id/work_recycleview" android:background="@android:color/white" android:layout_width="match_parent" android:layout_height="match_parent" android:overScrollMode="never" > </android.support.v7.widget.RecyclerView>复制代码
是否是变的干净简洁了!!!
而后咱们要使用Vlayout来设置咱们RecycleView中的各类布局。
RecycleView workRecycleview = (RecycleView)findViewById(R.id.work_recycleview);
//创建咱们的委托LayoutManger
VirtualLayoutManager layoutManager = new VirtualLayoutManager(this);
workRecycleview.setLayoutManager(layoutManager);
//经过这个layoutManager来管理一系列的LayoutHelper
//因此咱们先创建一个List来存放等会要用到的LayoutHelper.
List<LayoutHelper> helperList = new LinkedList<>();
//1.
//由于第一个底部图片就这一项,因此咱们就直接使用SingleLayoutHelper
SingleLayoutHelper bannerLayoutHelper = new SingleLayoutHelper();
bannerLayoutHelper.setItemCount(1);
helperList.add(bannerLayoutHelper);
//2.
//由于大标题栏是一个横向的LinearLayout,因此使用LinearLayoutHelper
LinearLayoutHelper personTitleHelper = new LinearLayoutHelper();
personTitleHelper.setItemCount(1);
helperList.add(personTitleHelper);
//3.
//由于功能块目前是九宫格,因此使用的是GridLayoutHelper
GridLayoutHelper personGridHelper = new GridLayoutHelper(3);
personGridHelper.setAutoExpand(false);
personGridHelper.setWeights(new float[]{33, 33, 33});
//设置登陆时候获取到的该用户权限下显示的功能数量。
personGridHelper.setItemCount(mPersonFunctions.size());
helperList.add(personGridHelper);
//4.
//同2界面
LinearLayoutHelper companyTitleHelper = new LinearLayoutHelper();
companyTitleHelper.setItemCount(1);
helperList.add(companyTitleHelper);
//5.
//同3界面
GridLayoutHelper companyGridHelper = new GridLayoutHelper(3);
companyGridHelper.setWeights(new float[]{33, 33, 33});
companyGridHelper.setAutoExpand(false);
//设置登陆时候获取到的该用户权限下显示的功能数量。
companyGridHelper.setItemCount(mCompanyFunctions.size());
helperList.add(companyGridHelper);复制代码
若是咱们须要增长新的布局控制。咱们只须要添加新的LayoutHelper,按顺序添加到咱们的helperList中便可。
目前的LayoutHelper有如下几种:
- LinearLayoutHelper: 线性布局
- GridLayoutHelper: Grid布局, 支持横向的colspan
- FixLayoutHelper: 固定布局,始终在屏幕固定位置显示
- ScrollFixLayoutHelper: 固定布局,但以后当页面滑动到该图片区域才显示, 能够用来作返回顶部或其余书签等
- FloatLayoutHelper: 浮动布局,能够固定显示在屏幕上,但用户能够拖拽其位置
- ColumnLayoutHelper: 栏格布局,都布局在一排,能够配置不一样列之间的宽度比值
- SingleLayoutHelper: 通栏布局,只会显示一个组件View
- OnePlusNLayoutHelper: 一拖N布局,能够配置1-5个子元素
- StickyLayoutHelper: stikcy布局, 能够配置吸顶或者吸底
- StaggeredGridLayoutHelper: 瀑布流布局,可配置间隔高度/宽度
既然用到RecycleView ,那怎么能够没有Adapter呢,上面的准备工做作了一部分后,咱们开始写咱们的Adapter。
咱们这里选择继承了VirtualLayoutAdapter:
咱们在构造函数中传入咱们二个九宫格功能块对应的List进来。
public class WorkAdapter extends VirtualLayoutAdapter {
int oneFuncs, twoFuncs;//
public List<FunctionBean> oneFunctions;
public List<FunctionBean> twoFunctions;
public static final int BANNER_VIEW_TYPE = 0;
public static final int DIVIDER_VIEW_TYPE = 1;
public static final int FUN_VIEW_TYPE = 2;
public WorkAdapter(@NonNull VirtualLayoutManager layoutManager, List<FunctionBean> oneFunctions, List<FunctionBean> twoFunctions,funcItemOnClickListener listener) {
super(layoutManager);
this.oneFunctions = oneFunctions;
this.twoFunctions = twoFunctions;
this.listener = listener;
oneFuncs = oneFunctions.size();
twoFuncs = twoFunctions.size();
}
}复制代码
咱们来分别看Adapter中每一个方法具体的复写:
1.
@Override
public int getItemCount() {
int totalCount = 0;
List<LayoutHelper> helpers = getLayoutHelpers();
if (helpers == null) {
return 0;
}
for (int i = 0; i < helpers.size(); i++) {
totalCount += helpers.get(i).getItemCount();
}
return totalCount;
}复制代码
咱们能够看到在getItemCount()
方法中,咱们经过遍历了LayoutHelper,分别取每一个LayoutHelper中咱们刚设置的个数。而后加起来,做为整个RecycleView 的个数。
2.
@Override
public int getItemViewType(int position) {
if (position == 0) {
return BANNER_VIEW_TYPE;
} else if (position == 1 || position == (2 + oneFuncs)) {
return DIVIDER_VIEW_TYPE;
} else {
return FUN_VIEW_TYPE;
}
}复制代码
咱们能够看到。咱们在getItemViewType
方法中确定position的值,返回不一样的type,这样等会在onCreateViewHolder
方法中就能够返回不一样的ViewHolder了。
3.
@Override
public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
switch (viewType) {
case BANNER_VIEW_TYPE:
return new BannerViewHolder(LayoutInflater.from(parent.getContext()).inflate(R.layout.item_work_banner, parent, false));
case DIVIDER_VIEW_TYPE:
return new DividerViewHolder(LayoutInflater.from(parent.getContext()).inflate(R.layout.item_work_divider, parent, false));
case FUN_VIEW_TYPE:
return new FuncViewHolder(LayoutInflater.from(parent.getContext()).inflate(R.layout.item_work_func, parent, false));
default:
return null;
}
}复制代码
4.
@Override
public void onBindViewHolder(RecyclerView.ViewHolder holder, int position) {
if (holder instanceof DividerViewHolder) {
if (position == 1) {
((DividerViewHolder) holder).dividerTitle.setText("个人服务");
((DividerViewHolder) holder).colorView.setBackgroundColor(Color.parseColor("#F9C025"));
} else {
((DividerViewHolder) holder).dividerTitle.setText("个人功能");
((DividerViewHolder) holder).colorView.setBackgroundColor(Color.parseColor("#35A7FF"));
}
} else if (holder instanceof FuncViewHolder) {
if(position > 1 && position < 2+ oneFuncs){
...
...
...
}else if(position > 2+ oneFuncs){
...
...
...
}
}
}复制代码
就这样咱们就具体的实现了多个布局的设置,并且当你要再加一个新的也很方便。可是也许你这时候会发现,若是咱们的布局很长,有不少九宫格,或者真的像淘宝同样,这个界面有各类功能块。那咱们刚写的
@Override
public int getItemViewType(int position) {
if (position == 0) {
return BANNER_VIEW_TYPE;
} else if (position == 1 || position == (2 + oneFuncs)) {
return DIVIDER_VIEW_TYPE;
} else {
return FUN_VIEW_TYPE;
}
}复制代码
这里你判断的position就会不少。你可能就要有不少的if-else 来控制返回不一样的type.
@Override
public void onBindViewHolder(RecyclerView.ViewHolder holder, int position) {
if (holder instanceof DividerViewHolder) {
if (position == 1) {
((DividerViewHolder) holder).dividerTitle.setText("个人服务");
((DividerViewHolder) holder).colorView.setBackgroundColor(Color.parseColor("#F9C025"));
} else {
((DividerViewHolder) holder).dividerTitle.setText("个人功能");
((DividerViewHolder) holder).colorView.setBackgroundColor(Color.parseColor("#35A7FF"));
}
} else if (holder instanceof FuncViewHolder) {
if(position > 1 && position < 2+ oneFuncs){
...
...
...
}else if(position > 2+ oneFuncs){
...
...
...
}
}
}复制代码
而后在onBindViewHolder
方法里面也要有不少的if-else,到后面维护又会变的很麻烦,因此这样写相对简单,适合模块很少的状况。
咱们前面是把不少LayoutHelper加入到了Adapter中,而后RecycleView直接设置该Adapter,
咱们此次就不这么作了。并且中间多设置一步,就是先每一个LayoutHelper设置一个Adapter,成为<子的Adapter>,而后把这些Adapter再统一放入到一个<总的Adapter>中,再把这个<总的Adapter>赋值给RecycleView便可。
由于这样在<总的Adapter>中,对于每一个Helper就能够知道个数,而且ViewHolder等赋值都被分配到了那些<子的Adapter>中处理了。咱们不用再if-else的写不少状况了。
(不过我不会彻底很仔细的讲解,代码我也不会贴所有,就贴一些主要的地方,讲主要的部分。)
1.
public class DelegateAdapter extends VirtualLayoutAdapter<RecyclerView.ViewHolder> {
private int mTotal = 0;
private int mIndex = 0;
private SparseArray<Adapter> mItemTypeAry = new SparseArray<>();
@NonNull
private final List<Pair<AdapterDataObserver, Adapter>> mAdapters = new ArrayList<>();
private final SparseArray<Pair<AdapterDataObserver, Adapter>> mIndexAry = new SparseArray<>();
}复制代码
咱们先定义了几个参数,
mIndex
用来等于标记各个加入的<子的Adapter>的序号
一个用来存<子的Adapter>的Map(别问我为何Map类型里面只填了一个Adapter,不是Key-Value? 能够补下SparseArray和ArrayMap的知识了,Android中用来替换HashMap的类。)
一个存放了Pair<AdapterDataObserver, Adapter>
的List集合(Pair若是也不知道,也能够去补充下,就简单理解为一个有二个属性的对象,第一个属性是AdapterDataObserver,第二个是Adapter)
一个存放了Pair<AdapterDataObserver, Adapter>
的Map集合。
2.
那咱们知道确定要有个方法把这些子的Adapter给加进来:
public void setAdapters(@Nullable List<Adapter> adapters) {
clear();//把相关的参数都从新置空,这里不写出来了。
if (adapters == null) {
adapters = Collections.emptyList();
}
List<LayoutHelper> helpers = new LinkedList<>();
mTotal = 0;
Pair<AdapterDataObserver, Adapter> pair;
for (Adapter adapter : adapters) {
// every adapter has an unique index id
//自定义类AdapterDataObserver ,继承于RecyclerView.AdapterDataObserver
AdapterDataObserver observer = new AdapterDataObserver(mTotal, mIndex++);
adapter.registerAdapterDataObserver(observer);
//子的Adapter中自定义的方法:onCreateLayoutHelper(),用来返回子的Adapter中的LayoutHelper
LayoutHelper helper = adapter.onCreateLayoutHelper();
//并且这些子的Adapter中的LayoutHelper的个数,就是这些子的Adapter的个数
helper.setItemCount(adapter.getItemCount());
//总数为每一个LayoutHelper的个数之和,也就是每一个子的Adapter的个数之和
mTotal += helper.getItemCount();
helpers.add(helper);
pair = Pair.create(observer, adapter);
//这里的mIndexAry存放了以加入的顺序mIndex为Key的Pair<AdapterObserver,Adapter>
mIndexAry.put(observer.mIndex, pair);
//同时mAdapters的List集合中也存放了Pair<AdapterObserver,Adapter>
mAdapters.add(pair);
}
super.setLayoutHelpers(helpers);
}复制代码
咱们看下AdapterDataObser的部分代码:
protected class AdapterDataObserver extends RecyclerView.AdapterDataObserver {
int mStartPosition;
int mIndex = -1;
public AdapterDataObserver(int startPosition, int index) {
this.mStartPosition = startPosition;
this.mIndex = index;
}
}复制代码
咱们能够看到咱们刚在new每一个AdapterDataObser的时候传入的构造函数参数是
mTotal
,mIndex++
,这样是否是正好每一个Adapter中的AdapterDataObserver中的mStartPosition
参数就是你的这个Adapter在全部整个RecycleView中的开始的position值。而mIndex
又说明了这个AdapterDataObserver是第几个,也就是这个Adapter是全部的<子的Adapter>中的第几个。
3.
@Override
public int getItemCount() {
return mTotal;
}复制代码
总数就是返回上面咱们的mTotal参数。
4.
@Override
public int getItemViewType(int position) {
Pair<AdapterDataObserver, Adapter> p = findAdapterByPosition(position);
if (p == null) {
return RecyclerView.INVALID_TYPE;
}
int subItemType = p.second.getItemViewType(position - p.first.mStartPosition);
if (subItemType < 0) {
// negative integer, invalid, just return
return subItemType;
}
if (mHasConsistItemType) {
mItemTypeAry.put(subItemType, p.second);
return subItemType;
}
int index = p.first.mIndex;
return (int) getCantor(subItemType, index);
}复制代码
咱们一步步来看这个比较关键的地方,咱们之因此不用咱们最刚开始第一次讲的Vlayout使用的方法,就是由于咱们的LayoutHelper多了以后,在getItemViewType()
方法中返回不一样的ViewType须要不少if-else来处理。因此这里咱们看他是如何自动处理的。
第一步:Pair<AdapterDataObserver, Adapter> p = findAdapterByPosition(position);
咱们看findAdapterByPosition方法的具体实现:
@Nullable
public Pair<AdapterDataObserver, Adapter> findAdapterByPosition(int position) {
//获取咱们上面的mAdapter集合,里面存的是Pair<AdapterObserver,Adapter>
final int count = mAdapters.size();
if (count == 0) {
return null;
}
int s = 0, e = count - 1, m;
Pair<AdapterDataObserver, Adapter> rs = null;
// binary search range
while (s <= e) {
m = (s + e) / 2;
rs = mAdapters.get(m);
int endPosition = rs.first.mStartPosition + rs.second.getItemCount() - 1;
if (rs.first.mStartPosition > position) {
e = m - 1;
} else if (endPosition < position) {
s = m + 1;
} else if (rs.first.mStartPosition <= position && endPosition >= position) {
break;
}
rs = null;
}
return rs;
}复制代码
经过这个方法的字面意思咱们不难理解:经过这个<总的Adapter>返回的item的position,来知道这个position是属于咱们存了Adapter集合中的哪一个Adapter的。
先获取咱们上面已经保存了各个Pair<AdapterObserver,Adapter>
的mAdapters集合,而后判断个数,为0就直接返回了。不为0,咱们就经过二分法查找的方式来进行查找。咱们前面已经在每一个AdapterDataObserver中存了相对于的Adapter的起始的Position,咱们只须要不停的判断如今传给这个方法的position是在(子的Adapter 的起始position) 与 (子的Adapter 的起始position + 子的Adapter的个数)之间,若是是,就说明是属于这个Adapter,咱们就在mAdapters集合中取出相应的Pair<AdapterObserver,Adapter>
。
第二步:
int subItemType = p.second.getItemViewType(position - p.first.mStartPosition);
if (subItemType < 0) {
// negative integer, invalid, just return
return subItemType;
}
if (mHasConsistItemType) {
mItemTypeAry.put(subItemType, p.second);
return subItemType;
}
int index = p.first.mIndex;
return (int) getCantor(subItemType, index);复制代码
这里的
(position - p.first.mStartPosition)
其实就是这个<总的Adapter>的处于position的这一项,在这个<子的Adapter>里面的具体的position值。最后经过这个<子的Adapter>的getItemViewType
来获得<子的Adapter>的ViewType。这样就自动帮咱们判断了在<总的Adapter>中的某个position值的Item的所属的<子的Adapter>的ViewType了。而不用写不少if-else来判断了。
mHasConsistItemType
来控制:在这个<总的Adapter>构造函数中传入,它的做用是whether sub adapters itemTypes are consistent
,就是咱们的全部的<子的Adapter>的itemType都是同样的。由于若是你在<子的Adapter>中没有覆写getItemViewType
方法的话,默认都是返回0,即:
public int getItemViewType(int position) {
return 0;
}复制代码
咱们也知道,RecycleView在运行的时候,执行顺序是:
getItemViewType ->onCreateViewHolder ->
getItemViewType ->onCreateViewHolder ->
getItemViewType ->onCreateViewHolder ->...复制代码
若是咱们的mHasConsistItemType设置为true的话:
因此咱们若是全部的<子的Adapter>中的要用同一个viewType的话,好比这里是0,咱们就在getItemViewType
方法中执行mItemTypeAry.put(subItemType, p.second);
,这样当前的这个<子的Apdater>就存在了key为0的集合中了,而后咱们在onCreateViewHolder
方法中经过Adapter adapter = mItemTypeAry.get(viewType);
取出来就好了,这时候由于viewType为0,就正好取出来咱们刚存的Adapter,而后再进入下一次的getItemViewType
的时候,就用新的adapter覆盖了key为0的value值,而后再拿到onCreateViewHolder
方法里面使用。
若是咱们的mHasConsistItemType设置为false的话:
那这时候就用了另一种方法,首先,由于子的Adapter默认拿到的ViewType都是0,因此咱们用了要设置一个可逆算法,好比A方法和还原的B方法,A方法中咱们每次传入viewType和另一个值(这里选定了上面咱们拿到的Pair<AdapterDataObserver, Adapter>
中的AdapterDataObserver的index值),由于每一个<子的Adapter>的index值不一样,因此生成的ViewType也不一样,而后咱们在onCreateViewHolder
方法里面,用还原的B方法,获取到index值,而后经过这个index再找回<子的Adapter>,这时候咱们就能够调用<子的Adapter>的onCreateViewHolder
方法了。
A方法:
private static long getCantor(long k1, long k2) {
return (k1 + k2) * (k1 + k2 + 1) / 2 + k2;
}复制代码
B方法:(具体看onCreateViewHolder方法中)
// reverse Cantor Function
int w = (int) (Math.floor(Math.sqrt(8 * viewType + 1) - 1) / 2);
int t = (w * w + w) / 2;
int index = viewType - t;
int subItemType = w - index;复制代码
5.
@Override
public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
// reverse Cantor Function
int w = (int) (Math.floor(Math.sqrt(8 * viewType + 1) - 1) / 2);
int t = (w * w + w) / 2;
int index = viewType - t;
int subItemType = w - index;
Adapter adapter = findAdapterByIndex(index);
if (adapter == null) {
return null;
}
return adapter.onCreateViewHolder(parent, subItemType);
}复制代码
6.
@SuppressWarnings("unchecked")
@Override
public void onBindViewHolder(RecyclerView.ViewHolder holder, int position) {
Pair<AdapterDataObserver, Adapter> pair = findAdapterByPosition(position);
if (pair == null) {
return;
}
pair.second.onBindViewHolder(holder, position - pair.first.mStartPosition);
pair.second.onBindViewHolderWithOffset(holder, position - pair.first.mStartPosition, position);
}复制代码
findAdapterByPosition:(也是二分法查找,和上面的findAdapterByIndex方法同样,不介绍了。)
@Nullable
public Pair<AdapterDataObserver, Adapter> findAdapterByPosition(int position) {
final int count = mAdapters.size();
if (count == 0) {
return null;
}
int s = 0, e = count - 1, m;
Pair<AdapterDataObserver, Adapter> rs = null;
// binary search range
while (s <= e) {
m = (s + e) / 2;
rs = mAdapters.get(m);
int endPosition = rs.first.mStartPosition + rs.second.getItemCount() - 1;
if (rs.first.mStartPosition > position) {
e = m - 1;
} else if (endPosition < position) {
s = m + 1;
} else if (rs.first.mStartPosition <= position && endPosition >= position) {
break;
}
rs = null;
}
return rs;
}复制代码
看了上面咱们发现了,最后咱们虽然给RecycleView赋值了一个<总的Adapter>,可是实际上的onCreateViewHolder
方法和onBindViewHolder
方法都是调用了每一个具体的<子的Adapter>的。
因此咱们最终在咱们的Activity中的使用
final DelegateAdapter delegateAdapter = new DelegateAdapter(layoutManager, true);
recyclerView.setAdapter(delegateAdapter);
List<DelegateAdapter.Adapter> adapters = new LinkedList<>();
adapters.add(XXXXX);//添加不一样的子Adapter.
...
//好比这样:
GridLayoutHelper layoutHelper;
layoutHelper = new GridLayoutHelper(4);
layoutHelper.setMargin(0, 10, 0, 10);
layoutHelper.setHGap(3);
layoutHelper.setAspectRatio(4f);
adapters.add(new ASubAdapter(this, layoutHelper, 8));
...
...
delegateAdapter.setAdapters(adapters);复制代码
若是我想新加一个功能块,只要新建一个针对这个功能块的Adapter,而后添加到adapters集合中就能够了。彻底不用修改原来的代码。只须要在这个新加的功能块的Adapter中处理便可。