RecycleView多种布局显示
1.前言
- 我们知道ListView多种布局显示用到两个方法一个getItemViewType和getViewTypeCount方法。
- getitemViewType方法告诉ListView我在第几个position展示哪种布局
- getViewTypeCount方法告诉ListView我有多少种布局
- 那么RecycleView如何实现多布局显示呢?其实这个和ListView原理一样,我们待会说,先来了解下实现RecycleView.Adapter会有哪几个方法要重写
- getItemCount()告诉RecycleView我有多少个item数量
- onCreateViewHolder()创建自己的holder并返回
- onBindViewHolder()绑定holder
- 创建自己的holder只需继承成RecycleView.ViewHolder即可(这一句不是重写adapter的方法,而是在继承RecycleView.Adapter的时候需要指定泛型,而这个泛型就是你的ViewHolder)。
2.多布局显示
RecycleView.Adapter也提供了getItemViewType方法,此方法和ListView加载多布局一样。我们只要在该方法中判断某个位置下返回某一种类型的布局即可。比如这样给RecycleView加头布局和脚布局(以下代码是伪代码):
-
你得告诉RecycleView你要加载的item数量
public int getItemCount() { return mDatas.size()+2; }
在getItemViewType中根据不同位置的position返回不同布局类型
if(position==0){
return 头布局类型;
}else if(position==getitemCount()-1){
return 脚布局类型;
}else {
return 默认布局类型;
}
-
在onCreateViewHolder(ViewGroup parent, intviewType)根据不同类型的type来返回不同的view给自己的ViewHolder。FooterView和SwitchView如何来的,其实是自己的adapter提供两个方法setHeaderView()和setFooterView通过外界传递进来的。
public ListHolder onCreateViewHolder(ViewGroup parent, intviewType) { View root =null; if(viewType ==头布局类型) { root =mHeaderView; }else if(viewType==脚布局类型){ root=mFooterView; }else{ root = View.inflate(context,R.layout.list_item,null); } return newListHolder(root,viewType); }
-
在自己的ViewHolder中进行处理如果是头布局或者脚布局直接返回
public static class ListHolderextends RecyclerView.ViewHolder{ TextView tv; publicListHolder(View root, intviewType) { super(root); if(viewType==头布局类型){ return ; } if(viewType==脚布局类型){ return ; } tv= (TextView)root.findViewById(R.id.item); }
-
在onBindViewHolder(ListHolder holder, intposition)方法中绑定数据
- 绑定View,这里是根据返回的这个position的类型,从而进行绑定的,HeaderView和FooterView就不绑定了
public void onBindViewHolder(ListHolder holder, intposition) {
int itemViewType = getItemViewType(position);
if(itemViewType ==头布局类型) {
return;
}else if(itemViewType ==脚布局类型) {
return;
}else{
//这里注意因为加了一个头布局position-1才是正确的数据
holder.tv.setTag(position-1);
holder.tv.setText(mDatas.get(position -1));
}
}
原理讲完了,那么接下来就应该讲讲实际的东西了。
3.需求
- 一般情况下,RecycleView加载多布局就是头部一个轮播图脚部一个加载更多,那么今天带给大家是RecycleView四种布局加载,为什么是四种布局呢,其实和以上三种布局原理是一样的,只是为了多说一下,recycleView切换视图列数。
请自动忽略图丑
请自动忽略图丑
-
我们分析下上面给出的两张图
- 蓝色背景是headerView
- 红色背景用来切换列表我们这里就叫做swichView
- 绿色部分就是我们的数据布局了
- 蓝色部分为FooterView
要达到这种布局,那么首先你得在你的adapter中getitemCount中返回数据长度+3
在getItemViewType方法中根据position返回不同类型布局 ......和上面的三种布局一致这里就不多说了。
主要讲解如何监听RecycleView滑动到底部自定加载数据呢?如何切换item的列数呢?
4.滑动监听
- 如何监听RecycleView滑动到底部自定加载数据?
- RecycleView有个addOnScrollListener方法,此方法接受一个OnScrollListener的子类并重写onScrolled,当RecycleView滑动的时 候会回调onScrolled方法
- onScrolled(RecyclerView recyclerView, int dx, int dy) 参数二:水平滚动距离,参数三:竖直滚动距离
- 那我们如何实现呢?
- 自定义一个类EndLessOnScrollListener 继承RecyclerView.OnScrollListener重写onScrolled方法。完整代码如下:
public abstract class EndLessOnScrollListener extends RecyclerView.OnScrollListener{
public static final String TAG =EndLessOnScrollListener.class.getName();
private GridLayoutManager gridLayoutManager;
//已经加载出来的Item的数量
private int totalItemCount;
//主要用来存储上一个totalItemCount
private int previousTotal = 0;
//在屏幕上可见的item数量
private int visibleItemCount;
//在屏幕可见的Item中的第一个
private int firstVisibleItem;
//是否正在上拉数据
private boolean loading = true;
//当前页,从1开始
private int currentPage =1;
public EndLessOnScrollListener(GridLayoutManager gridLayoutManager) {
this.gridLayoutManager = gridLayoutManager;
}
@Override
public void onScrolled(RecyclerView recyclerView, int dx, int dy) {
super.onScrolled(recyclerView, dx, dy);
visibleItemCount = recyclerView.getChildCount();
totalItemCount = gridLayoutManager.getItemCount();
firstVisibleItem = gridLayoutManager.findFirstVisibleItemPosition();
if(loading){
if(totalItemCount > previousTotal){
//说明数据已经加载结束
loading = false;
previousTotal = totalItemCount;
}
}
/**
* 当不是正在加载时并且
* totalItemCount-visibleItemCount得到的值表示已经滚出和未滑入的总数,
* firstVisibleItem 也可以表示已经滚出屏幕的item个数
* 其实也可以写成这样
* int i = totalItemCount - visibleItemCount; i就是滚出和未滚入的和
* i-firstVisibleItem得到的是未滚入的 如果未滚入=0或者小于0说明已经滚动到底部了
* if(!loading&&i-firstVisibleItem<=0){
* currentPage ++;
* onLoadMore(currentPage);
* loading = true;
* }
*/
if (!loading && totalItemCount-visibleItemCount <= firstVisibleItem){
currentPage ++;
onLoadMore(currentPage);
loading = true;
}
}
/**
* 提供一个抽象方法,在Activity中监听到这个EndLessOnScrollListener
* 并且实现这个方法
*/
public abstract void onLoadMore(int currentPage);
}
}
-
如何使用呢?
rec.addOnScrollListener(new EndLessOnScrollListener(gridLayoutManager) { @Override public void onLoadMore(int currentPage) { footerViewInTextView.setText("正在加载请稍后..."); getData(false); } });
因为要模拟网络请求,这里用的是rxjava timer操作符来模拟耗时,count用来模拟数据加载完毕
private void getData(final boolean falg) {
if(refreshSubscribe!=null&&!refreshSubscribe.isUnsubscribed()){
refreshSubscribe.unsubscribe();
}
if(count==3){
footerViewInTextView.setText("没有更多数据了");
return ;
}
if(falg){
swRefresh.setRefreshing(true);
}
refreshSubscribe = Observable.timer(3, TimeUnit.SECONDS).observeOn(AndroidSchedulers.mainThread()).subscribe(new Subscriber<Long>() {
@Override
public void onCompleted() {
for (int x = 30*count; x < 30*(1+count); x++) {
list.add("item" + x);
}
if(falg){
swRefresh.setRefreshing(false);
}
count++;
notifyData();
}
@Override
public void onError(Throwable e) {
}
@Override
public void onNext(Long aLong) {
}
});
}
5.更改布局列数
写之前先来看一下动图:
实现原理很简单:
-
adapter定义两种类型
- 一列类型
- 三列类型
默认加载三列类型那么可以提供一个变量来默认加载三列类型,提供外界一个可以设置类型的方法。用来更改列数
-
在getItemViewType中在返回默认布局的时候再次判断下是三列还是一列
public int getItemViewType(int position) { if (position == 0){ //第一个item应该加载Header return TYPE_HEADER; //第二个选择切换布局 }else if(position==1){ return TYPE_SWITCHER; }if (position == getItemCount()-1){ //最后一个,应该加载Footer return TYPE_FOOTER; }else { //如果是三列 if(showOneOrThree==TYPE_SHOW_THREE){ return TYPE_SHOW_THREE; }else { //一列 return TYPE_SHOW_ONE; } } }
-
同样在onBindViewHolder中判断类型,测试点击的是哪一种列数
public void onBindViewHolder(ListHolder holder, int position) { int itemViewType = getItemViewType(position); if (itemViewType == TYPE_HEADER) { return; } else if (itemViewType == TYPE_SWITCHER) { return; } else if (itemViewType == TYPE_FOOTER) { return; } else { holder.tv.setTag(position-2); holder.tv.setText(mDatas.get(position - 2)); if(itemViewType==TYPE_SHOW_ONE){ holder.tv.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { Integer tag = (Integer) v.getTag(); Toast.makeText(context,"一行单列:"+mDatas.get(tag),Toast.LENGTH_SHORT).show(); } }); }else { holder.tv.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { Integer tag = (Integer) v.getTag(); Toast.makeText(context,"一行三列:"+mDatas.get(tag),Toast.LENGTH_SHORT).show(); } }); } } }
外界更改并刷新
//三列
to_gridview.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
if (myAdapter != null) {
int spanSize = myAdapter.getShowOneOrThree();
//一列变三列,三列就不管
if (spanSize == RECYCLEVIEW_SHOW_ONE) {
gridLayoutManager.setSpanSizeLookup(new GridLayoutManager.SpanSizeLookup() {
@Override
public int getSpanSize(int position) {
if (position == 0) {
return 3;
} else if (position == 1) {
return 3;
} else if (myAdapter.getItemCount() - 1 == position) {
return 3;
} else {
return 1;
}
}
});
//更改状态
myAdapter.setShowOneOrThree(RECYCLEVIEW_SHOW_THREE);
//刷新视图
myAdapter.notifyItemRangeChanged(3, myAdapter.getItemCount());
}
}
}
});
//一列
to_listview.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
if (myAdapter != null) {
int spanSize = myAdapter.getShowOneOrThree();
//三列变一列,一列就不管
if (spanSize == RECYCLEVIEW_SHOW_THREE) {
gridLayoutManager.setSpanSizeLookup(new GridLayoutManager.SpanSizeLookup() {
@Override
public int getSpanSize(int position) {
if (position == 0) {
return 3;
} else if (position == 1) {
return 3;
} else if (myAdapter.getItemCount() - 1 == position) {
return 3;
} else {
return 3;
}
}
});
//更改状态
myAdapter.setShowOneOrThree(RECYCLEVIEW_SHOW_ONE);
//刷新视图
myAdapter.notifyItemRangeChanged(3, myAdapter.getItemCount());
}
}
}
});
这里要说下gridLayoutManager.setSpanSizeLookup方法,此方法的作用是用来确定一个item占用几列。因为我们默认用的是gridViewManager
来加载三列的,所以原始视图是三列。
- 当position==0时 是头布局,头布局宽度是和屏幕一样宽的,占用三列
- position==1时,是切换列数布局。同样占三列
- position==myAdapter.getItemCount() - 1 脚布局,占三列
- 其余根据所要切换的列数来返回。