RecycleView探究:添加头尾布局

Catalogue
  1. 1 具体思路
  2. 2 下面我们就把以上的思路转化成可爱的代码吧

笔者使用RecycleView也有一段时间了,每次遇到需要为RecycleView添加头尾布局的时候,都会是一阵头疼。之前我的做法是直接给依赖的实体集合添加头尾类型的Item Data。打脸的说,这是在赶需求的一种无赖做法。得益于最近项目不是很紧张,所以抽个周末的时间好好研究一下这一块的知识。
言归正传,本篇文章我要实现的效果是简洁高效地为RecycleView无限增加头尾布局。

1 具体思路

1、新建一个HeaderFooterWrapAdapter装饰类,它继承于RecycleView.Adapter,负责拓展普通RecycleView.Adapter对象(被装饰对象)的功能。
2、在onCreateViewHolderonBindViewHoldergetItemViewTypegetItemCount这几个方法区分有无头尾布局。如果没有头尾布局,一律按照被装饰对象的逻辑处理。
3、在onAttachedToRecyclerView方法内,做好GridView的适配。

2 下面我们就把以上的思路转化成可爱的代码吧

一、我们应该让HeaderFooterWrapAdapter继承自RecycleView.Adapter,并且定义好需要的成员对象。

1
2
3
4
5
6
public class HeaderFooterWrapAdapter extends RecyclerView.Adapter {
RecyclerView.Adapter adapter;//被装饰对象
private List<View> headerViews = new ArrayList<>();
private List<View> footerViews = new ArrayList<>();
……
}

二、重写RecyclerView.Adapter中几个重要的方法。
1、判断item个数。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
@Override
public int getItemCount() {
if (headerViews.size() != 0 && footerViews.size() != 0)//同时加了头部和尾部
{
return adapter.getItemCount() + headerViews.size() + footerViews.size();
} else if (headerViews.size() != 0) { //只有头部
return adapter.getItemCount() + headerViews.size();
} else if (footerViews.size() != 0) //只有尾部
{
return adapter.getItemCount() + footerViews.size();
} else {
return adapter.getItemCount();
}
}

2、区分Item类型。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
@Override
public int getItemViewType(int position) {
if (headerViews.size() != 0) {
if (position >= 0 && position < headerViews.size()) {
return headerViews.get(position).hashCode();
}
}
if (footerViews.size() != 0) {
int i = position - headerViews.size() - adapter.getItemCount();
if (i >= 0)
return footerViews.get(i).hashCode();
}
return 0;
}

3、创建Item。

1
2
3
4
5
6
7
8
9
10
11
12
13
public RecyclerView.ViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
for (View headerView : headerViews) {
if (headerView.hashCode() == viewType) {
return new HeaderViewHolder(headerView);
}
}
for (View footerview : footerViews) {
if (footerview.hashCode() == viewType) {
return new FooterViewHolder(footerview);
}
}
return adapter.onCreateViewHolder(parent, viewType);
}

4、绑定数据。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
@Override
public void onBindViewHolder(@NonNull RecyclerView.ViewHolder holder, int position) {
if (headerViews.size() != 0 && footerViews.size() != 0)//同时加了头部和尾部
{
if (position >= headerViews.size() && position < headerViews.size() + adapter.getItemCount()) {
adapter.onBindViewHolder(holder, position - headerViews.size());
}
} else if (headerViews.size() != 0) { //只有头部
if (position >= headerViews.size()) {
adapter.onBindViewHolder(holder, position - headerViews.size());
}
} else if (footerViews.size() != 0) //只有尾部
{
if (position >= 0 && position < adapter.getItemCount()) {
adapter.onBindViewHolder(holder, position);
}
} else {
adapter.onBindViewHolder(holder, position);
}
}

三、接下来适配GridView,这里主要是通过GridLayoutManager的setSpanSizeLookup方法动态处理头尾布局。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
@Override
public void onAttachedToRecyclerView(@NonNull RecyclerView recyclerView) {
super.onAttachedToRecyclerView(recyclerView);
final RecyclerView.LayoutManager layoutManager = recyclerView.getLayoutManager();
if (layoutManager instanceof GridLayoutManager) {
((GridLayoutManager) layoutManager).setSpanSizeLookup(new GridLayoutManager.SpanSizeLookup() {
@Override
public int getSpanSize(int position) {
if (headerViews.size() != 0 && footerViews.size() != 0) {
if (position >= 0 && position < headerViews.size()) {
return ((GridLayoutManager) layoutManager).getSpanCount();
} else if (position >= getItemCount() - footerViews.size() && position < getItemCount()) {
return ((GridLayoutManager) layoutManager).getSpanCount();
} else {
return 1;
}
} else if (headerViews.size() != 0) {
if (position >= 0 && position < headerViews.size()) {
return ((GridLayoutManager) layoutManager).getSpanCount();
}
return 1;
} else if (footerViews.size() != 0) {
if (position >= getItemCount() - footerViews.size() && position < getItemCount()) {
return ((GridLayoutManager) layoutManager).getSpanCount();
}
return 1;
}
return 1;
}
});
}
}

四、定义添加头部和尾部布局的公开方法。

1
2
3
4
5
6
7
8
9
10
//添加头布局
public void addHeaderView(View headerView) {
this.headerViews.add(headerView);
notifyItemInserted(headerViews.size() - 1);
}
//添加尾布局
public void addFooterView(View footerView) {
this.footerViews.add(footerView);
notifyItemInserted(headerViews.size() + adapter.getItemCount() + footerViews.size() - 1);
}

好了,以上就是为RecycleView添加头尾布局的核心代码。调用方式也很简单,伪代码如下。

1
2
3
4
5
6
7
8
9
10
MyAdapter myAdapter=....;
....
HeaderFooterWrapAdapter headerFooterWrapAdapter=new HeaderFooterWrapAdapter(myAdapter);
headerFooterWrapAdapter.addFooterView(footerView01);
headerFooterWrapAdapter.addFooterView(footerView02);
headerFooterWrapAdapter.addFooterView(footerView03);
headerFooterWrapAdapter.addHeaderView(headerView01);
headerFooterWrapAdapter.addHeaderView(headerView02);
headerFooterWrapAdapter.addHeaderView(headerView03);
.......

这里有一点需要注意就是Inflate创建headerView或者footerView时,parent需要传入recyclerView对象才能时布局的顶层属性起效果,例如:

1
2
View footerView = LayoutInflater.from(this).inflate(R.layout.footerview, rcv, false);

关于inflate的使用技巧,可以参考一下这篇文章。最后,再贴一张实现的效果图片吧~


参考资料
学会自己给RecyclerView添加Header、Footer和加载更多回调