RecycleView添加头部和底部的ItemView的功能

xiaoxiao2021-02-28  100

Recycleview的推出是用于替代ListView的,它为我们展示数据提供了非常便捷的方式,RecyclerView与ListView原理是类似的:都是维护少量的View并且可以展示大量的数据集。RecyclerView是高度的解耦,非常灵活的,通过设置它提供的不同LayoutManager,ItemDecoration , ItemAnimator,作为老司机的我们可以任意飙车,体验飞般的感觉。 RecyclerView虽然作为ListView的替代者有着较好的性能提升,但是ListView的一些常用功能却没有提供,比如我们平时会经常用到的addHeaderView,addFooterView等方法并没有提供,虽然它没有提供,但有时候我们又需要这样的功能,这可怎么办?那当然是自己造一个了,怎么造?作为小白,自己想破头也想不出什么思路来,眼见要翻车了,机智的小白想到:既然Recycleview是替代ListView出现的,那他们的原理应该类似,那ListView的addHeaderView,addFooterView又是怎么实现的呢?我们能不能依葫芦画瓢仿着Listview写一个呢?先上效果图,看合不合您胃口: 诺,就这个效果,怎么搞,接下来就跟着小白去查看一下Listview的源码吧,去找找里面有什么蛛丝马迹可供我们参考的,首先我们找到ListView源码里面的addHeaderView方法 源码如下:

public void addHeaderView(View v, Object data, boolean isSelectable) { //FixedViewInfo 是一个数据的封装,用于保存我们添加的头部View和View 相关的数据 final FixedViewInfo info = new FixedViewInfo(); info.view = v; info.data = data; info.isSelectable = isSelectable; //一个ArrayList用于保存添加进来的头部View mHeaderViewInfos.add(info); mAreAllItemsSelectable &= isSelectable; // Wrap the adapter if it wasn't already wrapped. if (mAdapter != null) { if (!(mAdapter instanceof HeaderViewListAdapter)) { //如果mAdapter的类型是通用的Adapter就调用包装方法包装该普通的adapter wrapHeaderListAdapterInternal(); } //否则调用HeaderAdapter的添加View功能,刷新数据 // 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(); } } }

仔细一瞧,小白恍然大悟,长吼一声“原来如此”。FixedViewInfo 是一个数据类的封装,用于保存我们添加进来的头部View和它关联的数据,mHeaderViewInfos是一个ArrayList用于保存添加进来的头部View如果该adapter是HeaderViewListAdapter那么就调wrapHeader ListAdapterInternal();否则就调用普通的adapter也就是没有头部View的adapter,不做处理。 接下来我们看看wrapHeaderListAdapterInternal具体是怎么实现的:

/** @hide */ protected void wrapHeaderListAdapterInternal() { mAdapter = wrapHeaderListAdapterInternal(mHeaderViewInfos, mFooterViewInfos, mAdapter); }

传入保存头部和底部ItemView 的mHeaderViewInfos,mFooterViewInfos集合, 调用该方法会给我返回一个特殊的mAdapter;接着往下看,它有什么特殊之处:

/** @hide */ protected HeaderViewListAdapter wrapHeaderListAdapterInternal( ArrayList<ListView.FixedViewInfo> headerViewInfos, ArrayList<ListView.FixedViewInfo> footerViewInfos, ListAdapter adapter) { return new HeaderViewListAdapter(headerViewInfos, footerViewInfos, adapter); }

这里我们发现它重新new了一个新类型的HeaderViewListAdapter对象,用于展示具有头部效果的Adapter。看一下HeaderViewListAdapter具体实现,HeaderViewListAdapter很简单,只有300多行的代码,你仔细看看肯定都能看懂,下面我就把重点的拿出来遛一遛。

//具有获取头部View数量的方法,以及底部View数量的方法: public int getHeadersCount() { return mHeaderViewInfos.size(); } public int getFootersCount() { return mFooterViewInfos.size(); } //getCount方法 public int getCount() { if (mAdapter != null) { // 如果添加了头部和底部的View的话Count他们的总和加上listView的数据项 return getFootersCount() + getHeadersCount() + mAdapter.getCount(); } else { return getFootersCount() + getHeadersCount(); } } //getItem方法 public Object getItem(int position) { // Header (negative positions will throw an IndexOutOfBoundsException) int numHeaders = getHeadersCount(); if (position < numHeaders) { return mHeaderViewInfos.get(position).data; } // Adapter final int adjPosition = position - numHeaders; int adapterCount = 0; if (mAdapter != null) { adapterCount = mAdapter.getCount(); if (adjPosition < adapterCount) { return mAdapter.getItem(adjPosition); } } // Footer (off-limits positions will throw an IndexOutOfBoundsException) return mFooterViewInfos.get(adjPosition - adapterCount).data; }

仔细看上面的getItem的方法,惊不惊喜,意不意外,看到了它的巧妙之处了吗?它实际就是通过代理设计模式对原本的Adapter进行封装。我们可以看到,首先获取头部View的数量(numHears),如果当前的position小于numHears,就表示当前Item返回的是集合中添加的头部View相关数据,那么直接从mHeaderViewInfos集合中取我们添加的View相关的数据,接着adjPosition等于当前位置减去numHears,那么就表示通用数据项的ItemView的相关数据,对于通用的ItemView它直接交给我们的传过来的adapter去处理,可以看到调用了mAdapter.getItem(adjPosition);返回了我们通用数据项的ItemView的数据。如果还存在底部的View,同理则从底部的View集合中它将取出来返回。 //最后getView方法:

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; }

原理与getItem()的方法类似,这里是返回View 而不是返回对象数据,仔细看不难明白,也是通过代理,包装处理。看到这里终于可以歇一下了,站在Google大神的肩膀上就是不一样,小白的我终于有点思路去实现Recycleview类似的功能了。很简单,直接依葫芦画瓢呗。下面就来看看我们怎么来画这个瓢,不多说了上代码: 1首先我们建一个WrapRecyclerView.class 用于包装原本的Recycleview,自定义添加我们需要的addHeaderView(View view)和ddFooterView(View v):

public class WrapRecycleView extends RecyclerView{ //保存头部view private ArrayList<View> mHeaderViews = new ArrayList<>(); //保存底部的view private ArrayList<View> mFooterViews = new ArrayList<>(); private Adapter mAdapter; public WrapRecycleView(Context context,AttributeSet attrs) { super(context, attrs); } /** * 添加头部的View * @param v */ public void addHeaderView(View v) { mHeaderViews.add(v); // Wrap the adapter if it wasn't already wrapped. if (mAdapter != null) { if (!(mAdapter instanceof HeaderViewRecyclerAdapter)) { mAdapter = new HeaderViewRecyclerAdapter(mHeaderViews, mFooterViews, mAdapter); } } } /** * 添加底部的View * @param v */ public void addFooterView(View v) { mFooterViews.add(v); // Wrap the adapter if it wasn't already wrapped. if (mAdapter != null) { if (!(mAdapter instanceof HeaderViewRecyclerAdapter)) { mAdapter = new HeaderViewRecyclerAdapter(mHeaderViews, mFooterViews, mAdapter); } } } @Override public void setAdapter(Adapter adapter) { if (mHeaderViews.size()>0||mFooterViews.size()>0){ mAdapter=new HeaderViewRecyclerAdapter(mHeaderViews,mFooterViews,adapter); }else { mAdapter=adapter; } super.setAdapter(mAdapter); } }

代码很简单,就是从仿着ListView的核心代码改造过来的,只是我做了简化。接来下就是包装的HeaderViewRecyclerAdapter,它继承Adapter ,用于判断当前条目的类型以及返回对应的Item注意这个Adapter是android.support.v7.widget.RecyclerView.Adapter下面的Adapter,继续看代码:

public class HeaderViewRecyclerAdapter extends Adapter { private Adapter mAdapter; private ArrayList<View> mHeaderViews; private ArrayList<View> mFooterViews; private static final int STATE_HEADER=1; private static final int STATE_FOOTER=-1; private int headerIndex=0; private int footerIndex=0; public HeaderViewRecyclerAdapter(ArrayList<View> headerViews, ArrayList<View> footerViews, Adapter adapter) { mAdapter=adapter; if (headerViews==null){ mHeaderViews=new ArrayList<>(); }else { mHeaderViews=headerViews; } if (footerViews==null){ mFooterViews=new ArrayList<>(); }else { mFooterViews=footerViews; } } @Override public int getItemViewType(int position) { //判断当前条目是什么类型的---决定渲染什么视图给什么数据 int numHeaders = getHeadersCount(); if (position < numHeaders) {//表示头部 return STATE_HEADER; } //数据项Item部分 // Adapter final int adjPosition = position - numHeaders; int adapterCount = 0; if (mAdapter != null) { adapterCount = mAdapter.getItemCount(); if (adjPosition < adapterCount) { return mAdapter.getItemViewType(adjPosition); } } //footer部分 return STATE_FOOTER; } @Override public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) { if(viewType==STATE_HEADER){//header return new HeaderViewHolder(mHeaderViews.get(headerIndex++)); }else if(viewType==STATE_FOOTER){//footer return new HeaderViewHolder(mFooterViews.get(footerIndex++)); } // Footer (off-limits positions will throw an IndexOutOfBoundsException) return mAdapter.onCreateViewHolder(parent, viewType); } @Override public void onBindViewHolder(RecyclerView.ViewHolder holder, int position) { //需要划分三个区域 int numHeaders = getHeadersCount(); if (position < numHeaders) {//表示头部 return ; } //adapter body final int adjPosition = position - numHeaders; int adapterCount = 0; if (mAdapter != null) { adapterCount = mAdapter.getItemCount(); if (adjPosition < adapterCount) { mAdapter.onBindViewHolder(holder, adjPosition); return ; } } //表示底部 } @Override public int getItemCount() { if (mAdapter != null) { return getFootersCount() + getHeadersCount() + mAdapter.getItemCount(); } else { return getFootersCount() + getHeadersCount(); } } public int getHeadersCount() { return mHeaderViews.size(); } public int getFootersCount() { return mFooterViews.size(); } private static class HeaderViewHolder extends RecyclerView.ViewHolder { public HeaderViewHolder(View view) { super(view); } } }

由于在onCreateViewHolder没有Postion的参数,不同于ListView,所以我们首先应该重写getItemViewType()方法在里面判断当前Item是什么类型,然后在onCreateViewHolder(ViewGroup parent, int viewType)中我们通过viewType就可以知道当前的Item是什么类型,接着返回相应的View,如果是正常的数据项我们就交给我们传过来的Apdater处理,直接调用 onBindViewHolder(holder, adjPosition)。我们不用去干预了,代理的巧妙就展现出来了。以上就是核心代码的解析了,我们可以看到所有的步骤以及思路跟ListView源码的思路完全一样的。 当然,你可以通过看ListView的源码将这个Adapter封装的更好,恩… . . .就这样,欢迎指正。

源码地址:https://github.com/ashijiaming/RecycleviewHeaderAndFooter/tree/master

转载请注明原文地址: https://www.6miu.com/read-29961.html

最新回复(0)