盒子
盒子
文章目录
  1. 前言
  2. ListView源码
  3. EnhanceRecyclerView
    1. FixedViewInfo
    2. addHeaderView与addFooterView
    3. setAdapter
  4. WrapperRecyclerViewAdapter
    1. getItemCount
    2. getItemViewType
    3. onCreateViewHolder
    4. onBindViewHolder
  5. 调整
    1. adjustSpanSize
    2. setLayoutManager
  6. 总结

RecyclerView添加header与footer

前言

这次主要关于RecyclerView添加headerfooter的实现方法,我们都知道,在使用ListView的时候我们能自由的给自己的ListView添加头部与尾部。使用addHeaderView()addFooterView()方法实现。只要自己自定义布局文件,添加进去即可。但当我们使用RecyclerView的时候就会发现这两个方便的方法没有了。那么如果此时我们要实现这些功能时要怎么办呢?不急,下面我们先来熟悉下ListView的添加headerfooter的实现原理。

ListView源码

既然ListView实现了添加头部与尾部的原理,所以我们可以先进入ListView的源码,查看其中的addHeaderView()方法与addFooterView()方法。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
public void addHeaderView(View v, Object data, boolean isSelectable) {
final FixedViewInfo info = new FixedViewInfo();
info.view = v;
info.data = data;
info.isSelectable = isSelectable;
mHeaderViewInfos.add(info);
mAreAllItemsSelectable &= isSelectable;
// Wrap the adapter if it wasn't already wrapped.
if (mAdapter != null) {
if (!(mAdapter instanceof HeaderViewListAdapter)) {
mAdapter = new HeaderViewListAdapter(mHeaderViewInfos, mFooterViewInfos, mAdapter);
}
// In the case of re-adding a header view, or adding one later on,
// we need to notify the observer.
if (mDataSetObserver != null) {
mDataSetObserver.onChanged();
}
}
}

这里省略addFooterView()源码,其实跟addHeaderView()基本一致

通过addHeaderView()的源码我们可以发现,它是使用一个FixedViewInfo类来存储数据。

1
2
3
4
5
6
7
8
public class FixedViewInfo {
/** The view to add to the list */
public View view;
/** The data backing the view. This is returned from {@link ListAdapter#getItem(int)}. */
public Object data;
/** <code>true</code> if the fixed view should be selectable in the list */
public boolean isSelectable;
}

其中最主要的代码就是

1
2
3
if (!(mAdapter instanceof HeaderViewListAdapter)) {
mAdapter = new HeaderViewListAdapter(mHeaderViewInfos, mFooterViewInfos, mAdapter);
}

我们会发现它new了一个HeaderViewListAdapter类,那么我们再进入HeaderViewListAdapter源码中,发现他就相对于一个我们自定义的Adapterheaderfooter的封装。

代码都比较简单,主要是思想,其中主要的是有

1
2
3
4
5
6
7
public int getCount() {
if (mAdapter != null) {
return getFootersCount() + getHeadersCount() + mAdapter.getCount();
} else {
return getFootersCount() + getHeadersCount();
}
}

根据position来设置ViewType的值

1
2
3
4
5
6
7
8
9
10
11
12
public int getItemViewType(int position) {
int numHeaders = getHeadersCount();
if (mAdapter != null && position >= numHeaders) {
int adjPosition = position - numHeaders;
int adapterCount = mAdapter.getCount();
if (adjPosition < adapterCount) {
return mAdapter.getItemViewType(adjPosition);
}
}
return AdapterView.ITEM_VIEW_TYPE_HEADER_OR_FOOTER;
}

再看getView()方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
public View getView(int position, View convertView, ViewGroup parent) {
// Header (negative positions will throw an IndexOutOfBoundsException)
int numHeaders = getHeadersCount();
if (position < numHeaders) {
return mHeaderViewInfos.get(position).view;
}
// Adapter
final int adjPosition = position - numHeaders;
int adapterCount = 0;
if (mAdapter != null) {
adapterCount = mAdapter.getCount();
if (adjPosition < adapterCount) {
return mAdapter.getView(adjPosition, convertView, parent);
}
}
// Footer (off-limits positions will throw an IndexOutOfBoundsException)
return mFooterViewInfos.get(adjPosition - adapterCount).view;
}

getItemViewType类似,当position小于headercount时,此时返回头部View,当position减去headeercount时依然小于正常的adaptercount,此时返回正常的View,如果还有,自然剩下的就是footer了。

所以根据ListView的添加头部与尾部的实现原理,我们可以模仿实现RecyclerView的相同的功能。

EnhanceRecyclerView

那么我们根据ListView来实现一个能够添加headerfooterRecyclerView,我这里定义为EnhanceRecyclerView

FixedViewInfo

在上面我们看到ListView中使用到了FixedViewInfo,就是一个自定义的类,用来存储数据,我们做适当的修改如下,View还是不变,依然是添加的视图,将其余的删掉,换成viewType

1
2
3
4
public class FixedViewInfo {
public View view;
public int viewType;
}

addHeaderView与addFooterView

做相应的修改,其中BASE_HEADER_VIEW_TYPEBASE_FOOTER_VIEW_TYPE是一个finalint数据

1
2
3
4
5
6
7
8
9
10
public void addHeaderView(View view) {
FixedViewInfo info = new FixedViewInfo();
info.view = view;
info.viewType = BASE_HEADER_VIEW_TYPE + mHeaderViewInfos.size();
mHeaderViewInfos.add(info);
if (mAdapter != null) {
mAdapter.notifyDataSetChanged();
}
}
1
2
3
4
5
6
7
8
9
10
public void addFooterView(View view) {
FixedViewInfo info = new FixedViewInfo();
info.view = view;
info.viewType = BASE_FOOTER_VIEW_TYPE + mFooterViewInfos.size();
mFooterViewInfos.add(info);
if (mAdapter != null) {
mAdapter.notifyDataSetChanged();
}
}

setAdapter

下面是setAdapter(),使用后面封装的WrapperRecyclerViewAdapter

1
2
3
4
5
6
7
8
9
10
@Override
public void setAdapter(Adapter adapter) {
if (!(adapter instanceof WrapperRecyclerViewAdapter))
mAdapter = new WrapperRecyclerViewAdapter(mHeaderViewInfos, mFooterViewInfos, adapter);
super.setAdapter(mAdapter);
// if (isShouldSpan) {
// ((WrapperRecyclerViewAdapter) mAdapter).adjustSpanSize(this);
// }
}

WrapperRecyclerViewAdapter

我们也可以根据ListViewHeaderViewListAdapter来模仿实现一个封装头部与尾部的adapter。我这里定义为WrapperRecyclerViewAdapter继承RecyclerView.Adapter根据情况适当修改。

getItemCount

1
2
3
4
5
6
7
8
@Override
public int getItemCount() {
if (mAdapter != null) {
return getHeadersCount() + getFootersCount() + mAdapter.getItemCount();
} else {
return getHeadersCount() + getFootersCount();
}
}

getItemViewType

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
@Override
public int getItemViewType(int position) {
int numHeaders = getHeadersCount();
if (position < numHeaders) {
return mHeaderViewInfos.get(position).viewType;
}
int adjPosition = position - numHeaders;
int adapterPosition = 0;
if (mAdapter != null) {
adapterPosition = mAdapter.getItemCount();
if (adjPosition < adapterPosition) {
return mAdapter.getItemViewType(adjPosition);
}
}
return mFooterViewInfos.get(position - adapterPosition - getHeadersCount()).viewType;
}

这里逻辑与ListView的基本一致。

onCreateViewHolder

这里需将HeaderViewListAdapter中的getView()方法分成两部分来实现。

1
2
3
4
5
6
7
8
9
10
11
@Override
public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
if (viewType >= EnhanceRecyclerView.BASE_HEADER_VIEW_TYPE && viewType < EnhanceRecyclerView.BASE_HEADER_VIEW_TYPE + getHeadersCount()) {
View view = mHeaderViewInfos.get(viewType - EnhanceRecyclerView.BASE_HEADER_VIEW_TYPE).view;
return viewHolder(view);
} else if (viewType >= EnhanceRecyclerView.BASE_FOOTER_VIEW_TYPE && viewType < EnhanceRecyclerView.BASE_FOOTER_VIEW_TYPE + getFootersCount()) {
View view = mFooterViewInfos.get(viewType - EnhanceRecyclerView.BASE_FOOTER_VIEW_TYPE).view;
return viewHolder(view);
}
return mAdapter.onCreateViewHolder(parent, viewType);
}

根据不同的viewType来实现不同的布局文件.不是headerfooter布局时则直接回调原始adapteronCreateViewHolder()方法。实现正常的布局。

onBindViewHolder

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
@Override
public void onBindViewHolder(RecyclerView.ViewHolder holder, int position) {
int numHeaders = getHeadersCount();
if (position < numHeaders) {
return;
}
int adjPosition = position - numHeaders;
int adapterCount = 0;
if (mAdapter != null) {
adapterCount = mAdapter.getItemCount();
if (adjPosition < adapterCount) {
mAdapter.onBindViewHolder(holder, adjPosition);
return;
}
}
}

根据position的位置来选择,当position属于正常的位置范围之内时,则回调原始的adapteronBindViewHolder()方法,实现数据的绑定;对于其它的情况,无需做处理。

主要的模仿变动就是这些,然后自己在根据情况进行适当的修改,最后在布局文件中使用自定义的EnhanceRecyclerView替代RecyclerView

调整

我们都知道在使用RecyclerView的时候要设置LayoutManager,如果按照上面的实现,当LayoutManager设置为LinearLayout时,自然没上面问题,头部与尾部能正常添加;但当我们的LayoutManager设置为GridLayoutManagerStaggeredGridLayoutManager时,我们会发现头部与尾部的添加出现异常,就是他们不能独自占一行,所以这里我们要做相应的调整,使他们在添加头部与尾部的时候独自占一行。

adjustSpanSize

在上面我们自定义的WrapperRecyclerViewAdapter中添加adjustSpanSize()方法,用来实现调整。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
public void adjustSpanSize(RecyclerView recyclerView) {
if (recyclerView.getLayoutManager() instanceof GridLayoutManager) {
final GridLayoutManager manager = (GridLayoutManager) recyclerView.getLayoutManager();
manager.setSpanSizeLookup(new GridLayoutManager.SpanSizeLookup() {
@Override
public int getSpanSize(int position) {
int numHeaders = getHeadersCount();
int adjPosition = position - numHeaders;
if (position < numHeaders || adjPosition >= mAdapter.getItemCount())
return manager.getSpanCount();
return 1;
}
});
}
if (recyclerView.getLayoutManager() instanceof StaggeredGridLayoutManager) {
isStaggered = true;
}
}

可以看出当LayoutManagerGridLayoutManager时,我们通过manager来设置setSpanSizeLookup()getSpanSize()方法中根据具体判断来调用manager.getSpanCount()来实现头部与尾部的占一行的效果;对于StaggeredGridLayoutManager因为其没有setSpanSizeLookup()方法,所以我们先做标记,在ViewHolder中为每一个需要的ItemView来设置paramssetFullSpa(true)方法来填充一行。

1
2
3
4
5
6
7
8
9
10
11
private RecyclerView.ViewHolder viewHolder(View itemView) {
if (isStaggered) {
StaggeredGridLayoutManager.LayoutParams params = new StaggeredGridLayoutManager.LayoutParams(StaggeredGridLayoutManager.LayoutParams.MATCH_PARENT,
StaggeredGridLayoutManager.LayoutParams.WRAP_CONTENT);
params.setFullSpan(true);
itemView.setLayoutParams(params);
}
return new RecyclerView.ViewHolder(itemView) {
};
}

setLayoutManager

EnhanceRecyclerView中重写setLayoutManager()方法,添加标记。

1
2
3
4
5
6
@Override
public void setLayoutManager(LayoutManager layout) {
if (layout instanceof GridLayoutManager || layout instanceof StaggeredGridLayoutManager)
isShouldSpan = true;
super.setLayoutManager(layout);
}

最后再 在EnhanceRecyclerViewsetAdapter()方法中根据标记来判断是否调用adjustSpanSize()方法来进行调整headerfooter.

1
2
3
if (isShouldSpan) {
((WrapperRecyclerViewAdapter) mAdapter).adjustSpanSize(this);
}

其实就是前面setAdapter()中所注释的代码。

这样整个调整部分就基本完成。headerfooter的添加也就基本完成了。

总结

通过RecyclerView的头部与尾部的添加,我们应该能学习如何运用原有的资源来实现我们所需要的功能。虽然方法不一定是最好的,因为使用这种方法实现也存在一些问题,例如不能在初始化(onCreate)中同时添加headerfooter,否则会出现布局混乱的现象,现在还不知道为何有这种特例,希望知道的可以告知解决下,但这种模仿的思想还是很值得推荐的。

转载请指明出处 idisfkj博客:https://idisfkj.github.io

支持一下
赞赏是一门艺术