Recyclerview head and tail layout

This article has authorized the WeChat public account: Hongyang (hongyangAndroid) to publish the original on the WeChat public account platform.

Please indicate the source for reprinting: 
http://blog.csdn.net/lmj623565791/article/details/51854533
This article comes from: [Zhang Hongyang's blog]

1 Overview

RecyclerView is favored by everyone because of its high degree of customizability, and many users have begun to encapsulate or transform it to meet more and more needs.

If you are no stranger to RecyclerView, you must have encountered such a situation. I want to add a headerView or footerView to RecyclerView. When you type it out .addHeaderView, you will find that there is no API for adding header or bottom View.

Then the main content of this article is obvious, complete the following work:

  • How to add HeaderView to RecyclerView (support multiple)
  • How to add FooterView to RecyclerView (support multiple)
  • How to make HeaderView or FooterView adapt to various LayoutManagers

Well, actually I wanted to be lazy, because Loader wrote a similar article, see the reference link at the end of the article. But I found that it was pushed by other public accounts~~

Then I can only think of another way of thinking to solve this problem and provide as many functions as possible~

This article was first published on my official account, please scan the code to follow (see the left column for the QR code).

2 ideas

(1) Principle

Ideas for adding headerView or footerView

In fact, HeaderView is actually a kind of Item, but it is displayed at the top, so we can do it by setting the ItemType for it.

After we have an idea, we are ready, at least we can realize it in our heart, and then we will consider some details.

(2) Some details

Suppose we have completed the writing of RecyclerView now, and suddenly there is a need to add a HeaderView to the list. What should we do at this time?

Open our Adapter, then add a special ViewType according to our above principle, and then modify the code to complete.

This is a relatively common practice, but there is a problem that if you need to add viewType, then maybe our Adapter needs to be modified a lot, for example getItemType, getItemCount, , onBindViewHolder, onCreateViewHolderetc., almost all methods have to be changed.

In this way, the error rate is very high.

Moreover, there may be multiple RecyclerViews in a project that need to add headerView to their list.

From this point of view, it is very uneconomical to directly change the code of the Adapter. It is best to design a class that can seamlessly add headerView and footerView to the original Adapter.

The idea of ​​​​this article is to design a class through a similar decorator pattern to enhance the function of the original Adapter to support addHeaderViewand addFooterView. In this way, we can flexibly extend the function without changing the code we have completed before.

My desired usage is like this:

mHeaderAndFooterWrapper = new HeaderAndFooterWrapper(mAdapter); t1.setText("Header 1"); TextView t2 = new TextView(this); mHeaderAndFooterWrapper.addHeaderView(t2);

Enhance its function without changing the original Adapter.

3. Initial realization

(1) Basic code

public class HeaderAndFooterWrapper<Textends RecyclerView.Adapter<RecyclerView.ViewHolder> { private static final int BASE_ITEM_TYPE_HEADER = 100000private static final int BASE_ITEM_TYPE_FOOTER = 200000private SparseArrayCompat<View> mHeaderViews = new SparseArrayCompat<>(); private SparseArrayCompat<View> mFootViews = new SparseArrayCompat<>(); private RecyclerView.Adapter mInnerAdapter; public HeaderAndFooterWrapper(RecyclerView.Adapter adapter) { mInnerAdapter = adapter; } private boolean isHeaderViewPos(int position) { return position < getHeadersCount(); } private boolean isFooterViewPos(int position) { return position >= getHeadersCount() + getRealItemCount(); } public void addHeaderView(View view) { mHeaderViews.put(mHeaderViews.size() + BASE_ITEM_TYPE_HEADER, view); } public void addFootView(View view) { mFootViews.put(mFootViews.size() + BASE_ITEM_TYPE_FOOTER, view); } public int getHeadersCount() { return mHeaderViews.size(); } public int getFootersCount() { return mFootViews.size(); } }

First, we write a subclass of Adapter, we call it HeaderAndFooterWrapper, and then add some auxiliary methods such as addHeaderView, addFooterView and so on.

Here you can see that for multiple HeaderViews, the first thing we should think of is to use List<View>, and why should we use SparseArrayCompat<View>it here?

What are the characteristics of SparseArrayCompat? It is similar to Map, except that it has better performance than Map in some cases, and can only store the case where the key is int.

And you can see that we have a specific key corresponding to each HeaderView, the first headerView corresponds to BASE_ITEM_TYPE_HEADER, and the second corresponds to BASE_ITEM_TYPE_HEADER+1;

Why do you do that?

这两个问题都需要到复写onCreateViewHolder的时候来说明。

(2)复写相关方法

public class HeaderAndFooterWrapper<Textends RecyclerView.Adapter<RecyclerView.ViewHolder> { @Override public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) { if (mHeaderViews.get(viewType) != null) { ViewHolder holder = ViewHolder.createViewHolder(parent.getContext(), mHeaderViews.get(viewType)); return holder; } else if (mFootViews.get(viewType) != null) { ViewHolder holder = ViewHolder.createViewHolder(parent.getContext(), mFootViews.get(viewType)); return holder; } return mInnerAdapter.onCreateViewHolder(parent, viewType); } @Override public int getItemViewType(intposition) { if (isHeaderViewPos(position)) { return mHeaderViews.keyAt(position); } else if (isFooterViewPos(position)) { return mFootViews.keyAt(position - getHeadersCount() - getRealItemCount()); } return mInnerAdapter.getItemViewType(position - getHeadersCount()); } private int getRealItemCount() {return mInnerAdapter.getItemCount(); } @Override public void onBindViewHolder(RecyclerView.ViewHolder holder, int position) { if (isHeaderViewPos(position)) { return; } if (isFooterViewPos(position)) { return; } mInnerAdapter.onBindViewHolder(holder, position - getHeadersCount()); } @Override public intgetItemCount() { return getHeadersCount() + getFootersCount() + getRealItemCount(); } }
  • getItemViewType

由于我们增加了headerView和footerView首先需要复写的就是getItemCount和getItemViewType。

getItemCount很好理解;

对于getItemType,可以看到我们的返回值是mHeaderViews.keyAt(position),这个值其实就是我们addHeaderView时的key,footerView是一样的处理方式,这里可以看出我们为每一个headerView创建了一个itemType。

  • onCreateViewHolder

可以看到,我们分别判断viewType,如果是headview或者是footerview,我们则为其单独创建ViewHolder,这里的ViewHolder是我之前写的一个通用的库里面的类,文末有链接。当然,你也可以自己写一个ViewHolder的实现类,只需要将对应的headerView作为itemView传入ViewHolder的构造即可。

这个方法中,我们就可以解答之前的问题了:

  • 为什么我要用SparseArrayCompat而不是List?
  • 为什么我要让每个headerView对应一个itemType,而不是固定的一个?

对于headerView假设我们有多个,那么onCreateViewHolder返回的ViewHolder中的itemView应该对应不同的headerView,如果是List,那么不同的headerView应该对应着:list.get(0),list.get(1)等。

但是问题来了,该方法并没有position参数,只有itemType参数,如果itemType还是固定的一个值,那么你是没有办法根据参数得到不同的headerView的。

所以,我利用SparseArrayCompat,将其key作为itemType,value为我们的headerView,在onCreateViewHolder中,直接通过itemType,即可获得我们的headerView,然后构造ViewHolder对象。而且我们的取值是从100000开始的,正常的itemType是从0开始取值的,所以正常情况下,是不可能发生冲突的。

需要说明的是,这里的意思并非是一定不能用List,通过一些特殊的处理,List也能达到上述我描述的效果。

  • onBindViewHolder

onBindViewHolder比较简单,发现是HeaderView或者FooterView直接return即可,因为对于头部和底部我们仅仅做展示即可,对于事件应该是在addHeaderView等方法前设置。

这样就初步完成了我们的装饰类,我们分别添加两个headerView和footerView:

我们看看运行效果:

感觉还是不错的,再不改动原来的Adapter的基础上,我们完成了初步的实现。

大家都知道RecyclerView比较强大,可以设置不同的LayoutManager,那么我们换成GridLayoutMananger再看看效果。

好像发现了不对的地方,我们的headerView果真被当成普通的Item处理了,不过由于我们的编写方式,出现上述情况是可以理解的。

那么我们该如何处理呢?让每个headerView独立的占据一行?

4、进一步的完善

好在RecyclerView里面为我们提供了一些方法。

(1)针对GridLayoutManager

在我们的HeaderAndFooterWrapper中复写onAttachedToRecyclerView方法,如下:

@Override public void onAttachedToRecyclerView(RecyclerView recyclerView) { innerAdapter.onAttachedToRecyclerView(recyclerView); RecyclerView.LayoutManager layoutManager = recyclerView.getLayoutManager(); if (layoutManager instanceof GridLayoutManager) { final GridLayoutManager gridLayoutManager = (GridLayoutManager) layoutManager; final GridLayoutManager.SpanSizeLookup spanSizeLookup = gridLayoutManager.getSpanSizeLookup(); gridLayoutManager.setSpanSizeLookup(new GridLayoutManager.SpanSizeLookup() { @Override public int getSpanSize(int position) { int viewType = getItemViewType(position); if (mHeaderViews.get(viewType) != null) { return layoutManager.getSpanCount(); } else if (mFootViews.get(viewType) != null) { return layoutManager.getSpanCount(); } if (oldLookup != nullreturn oldLookup.getSpanSize(position); return 1; } }); gridLayoutManager.setSpanCount(gridLayoutManager.getSpanCount()); } }

当发现layoutManager为GridLayoutManager时,通过设置SpanSizeLookup,对其getSpanSize方法,返回值设置为layoutManager.getSpanCount();

现在看一下运行效果:

哈,终于正常了。

(3)对于StaggeredGridLayoutManager

在刚才的代码中我们好像没有发现StaggeredGridLayoutManager的身影,StaggeredGridLayoutManager并没有setSpanSizeLookup这样的方法,那么该如何处理呢?

依然不复杂,重写onViewAttachedToWindow方法,如下:

@Override public void onViewAttachedToWindow(RecyclerView.ViewHolder holder) { mInnerAdapter.onViewAttachedToWindow(holder); int position = holder.getLayoutPosition(); if (isHeaderViewPos(position) || isFooterViewPos(position)) { ViewGroup.LayoutParams lp = holder.itemView.getLayoutParams(); if (lp != null && lp instanceof StaggeredGridLayoutManager.LayoutParams) { StaggeredGridLayoutManager.LayoutParams p = (StaggeredGridLayoutManager.LayoutParams) lp; p.setFullSpan(true); } } }

这样就完成了对StaggeredGridLayoutManager的处理,效果图就不贴了。

到此,我们就完成了整个HeaderAndFooterWrapper的编写,可以在不改变原Adapter代码的情况下,为其添加一个或者多个headerView或者footerView,以及完成了如何让HeaderView或者FooterView适配各种LayoutManager。

源码地址: 
https://github.com/hongyangAndroid/baseAdapter


欢迎关注我的微博: 
http://weibo.com/u/3165018720


群号: 497438697 ,欢迎入群

微信公众号:hongyangAndroid 
(欢迎关注,不要错过每一篇干货,支持投稿) 

参考

Guess you like

Origin http://43.154.161.224:23101/article/api/json?id=325857166&siteId=291194637