您现在的位置是:首页 > 文章详情

Android 解析RecyclerView(2)——带顶部View和底部View的RecyclerView

日期:2017-06-06点击:394

RecyclerView是用来替代ListView的一个控件,比ListView更加的简洁高效,不过也有一些比较不足的地方,比如:无法直接设置点击事件监听; 无法像ListView那样直接添加顶部View和底部View。

设置点击事件监听在前一篇文章已经解决了,这一篇文章要来介绍如何为RecyclerView添加顶部View和底部View。

一、源码分析

先来看下ListView的源码,研究它是如何添加顶部View的

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)) { wrapHeaderListAdapterInternal(); } // 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(); } } } 

可以看到,addHeaderView() 方法首先是将 headerView 保存到 FixedViewInfo 对象中,再将 FixedViewInfo 对象保存到集合中。
FixedViewInfo 类的定义如下所示:

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

mAdapter 即是为ListView设置的Adapter 对象,如果 mAdapter 不是HeaderViewListAdapter 的直接实例,则调用 wrapHeaderListAdapterInternal() 方法,以顶部View、底部View和mAdapter为参数,来将 mAdapter 转为 HeaderViewListAdapter 对象

 protected void wrapHeaderListAdapterInternal() { mAdapter = wrapHeaderListAdapterInternal(mHeaderViewInfos, mFooterViewInfos, mAdapter); } protected HeaderViewListAdapter wrapHeaderListAdapterInternal( ArrayList<ListView.FixedViewInfo> headerViewInfos, ArrayList<ListView.FixedViewInfo> footerViewInfos, ListAdapter adapter) { return new HeaderViewListAdapter(headerViewInfos, footerViewInfos, adapter); } 

查看 HeaderViewListAdapter 类的一些方法,可以看出 HeaderViewListAdapter 在计算子项总数和获取子项实例时,都是将顶部View和底部View包含进来的。

public int getCount() { if (mAdapter != null) { return getFootersCount() + getHeadersCount() + mAdapter.getCount(); } else { return getFootersCount() + getHeadersCount(); } } 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; } 

经过以上的分析,就给我们提供了设计思路。我们同样可以设计一个包裹了 RecyclerView Adapter 和顶部底部View的外部Adapter,再将该Adapter设置给 RecyclerView。此外,由于原生的 RecyclerView 没有对应的 addHeaderView() 和 addFooterView() 方法,所以也需要再来继承 RecyclerView,在子类中定义需要的方法。

二、自定义Adapter

先定义几个需要用到的变量,headerViews 和 footerViews 分别用于存储顶部View和底部View,两个整数值则是会不断自增加一,用来作为SparseArray< View > 的Key值,adapter则是指向为RecyclerView设置的Adapter

 private RecyclerView.Adapter adapter; private SparseArray<View> headerViews; private SparseArray<View> footerViews; //头部类型开始位置,用于viewType private static int BASE_ITEM_TYPE_HEADER = 1000; //底部类型开始位置,用于viewType private static int BASE_ITEM_TYPE_FOOTER = 2000; 

然后声明几个用来添加和移除View的方法

/** * 添加头部View * * @param view 头部View */ public void addHeaderView(View view) { if (headerViews.indexOfValue(view) < 0) { headerViews.put(BASE_ITEM_TYPE_HEADER++, view); notifyDataSetChanged(); } } /** * 添加底部View * * @param view 底部View */ public void addFooterView(View view) { if (footerViews.indexOfValue(view) < 0) { footerViews.put(BASE_ITEM_TYPE_FOOTER++, view); notifyDataSetChanged(); } } /** * 移除头部View * * @param view View */ public void removeHeaderView(View view) { int index = headerViews.indexOfValue(view); if (index > -1) { headerViews.removeAt(index); notifyDataSetChanged(); } } /** * 移除底部View * * @param view View */ public void removeFooterView(View view) { int index = footerViews.indexOfValue(view); if (index > -1) { footerViews.removeAt(index); notifyDataSetChanged(); } } 

重点是 getItemViewType() 方法,如果索引值position指向的是顶部View或者底部View,则返回该View在 SparseArray< View >中的Key值,以该值作为View的 ItemViewType。如果索引指向的是中间的展示数据的子项,则调用adapter本身相同的方法

 /** * 根据索引判断该位置的View类型 * 如果是头部,则返回该View在headerViews中的key * 如果是底部,则返回该View在footerViews中的key * * @param position 索引 * @return View类型 */ @Override public int getItemViewType(int position) { if (isHeaderPosition(position)) { return headerViews.keyAt(position); } if (isFooterPosition(position)) { position = position - headerViews.size() - adapter.getItemCount(); return footerViews.keyAt(position); } position = position - headerViews.size(); return adapter.getItemViewType(position); } 

为不同的View指定了不同的ItemViewType后,则可以在onCreateViewHolder() 方法中返回不同的 ViewHolder 对象了

 @Override public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) { if (isHeaderViewType(viewType)) { return createHeaderFooterViewHolder(headerViews.get(viewType)); } if (isFooterViewType(viewType)) { return createHeaderFooterViewHolder(footerViews.get(viewType)); } return adapter.onCreateViewHolder(parent, viewType); } 

总的方法定义如下所示:

/** * 作者: 叶应是叶 * 时间: 2017/6/4 */ public class WrapRecyclerViewAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolder> { private RecyclerView.Adapter adapter; private SparseArray<View> headerViews; private SparseArray<View> footerViews; //头部类型开始位置,用于viewType private int BASE_ITEM_TYPE_HEADER = 1000; //底部类型开始位置,用于viewType private int BASE_ITEM_TYPE_FOOTER = 2000; public WrapRecyclerViewAdapter(RecyclerView.Adapter adapter) { this.adapter = adapter; headerViews = new SparseArray<>(); footerViews = new SparseArray<>(); this.adapter.registerAdapterDataObserver(new RecyclerView.AdapterDataObserver() { @Override public void onChanged() { super.onChanged(); notifyDataSetChanged(); } }); } @Override public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) { if (isHeaderViewType(viewType)) { return createHeaderFooterViewHolder(headerViews.get(viewType)); } if (isFooterViewType(viewType)) { return createHeaderFooterViewHolder(footerViews.get(viewType)); } return adapter.onCreateViewHolder(parent, viewType); } @Override public void onBindViewHolder(RecyclerView.ViewHolder holder, int position) { if (isHeaderPosition(position) || isFooterPosition(position)) { return; } adapter.onBindViewHolder(holder, position - headerViews.size()); } /** * 获取列表总的条数(头部View个数+列表条数+底部View个数) * * @return 总的条数 */ @Override public int getItemCount() { return adapter.getItemCount() + headerViews.size() + footerViews.size(); } /** * 获取不包含头部和底部View之后列表的条数 * * @return 列表条数 */ public int getDataItemCount() { return adapter.getItemCount(); } /** * 根据索引判断该位置的View类型 * 如果是头部,则返回该View在headerViews中的key * 如果是底部,则返回该View在footerViews中的key * * @param position 索引 * @return View类型 */ @Override public int getItemViewType(int position) { if (isHeaderPosition(position)) { return headerViews.keyAt(position); } if (isFooterPosition(position)) { position = position - headerViews.size() - adapter.getItemCount(); return footerViews.keyAt(position); } position = position - headerViews.size(); return adapter.getItemViewType(position); } /** * 创建头部View或底部View的ViewHolder * * @param view 头部View或底部View * @return ViewHolder */ private RecyclerView.ViewHolder createHeaderFooterViewHolder(View view) { return new RecyclerView.ViewHolder(view) { }; } /** * 判断是否是头部View * * @param key Key * @return 是否是头部View */ private boolean isHeaderViewType(int key) { return headerViews.indexOfKey(key) > -1; } /** * 判断是否是底部View * * @param key Key * @return 是否是底部View */ private boolean isFooterViewType(int key) { return footerViews.indexOfKey(key) > -1; } /** * 根据索引判断该位置的View是否是头部View * * @param position 索引 * @return 是否是头部View */ private boolean isHeaderPosition(int position) { return (position > -1) && (position < headerViews.size()); } /** * 根据索引判断该位置的View是否是底部View * * @param position 索引 * @return 是否是底部View */ private boolean isFooterPosition(int position) { return (position >= (headerViews.size() + adapter.getItemCount())) && (position < (headerViews.size() + adapter.getItemCount() + footerViews.size())); } /** * 添加头部View * * @param view 头部View */ public void addHeaderView(View view) { if (headerViews.indexOfValue(view) < 0) { headerViews.put(BASE_ITEM_TYPE_HEADER++, view); notifyDataSetChanged(); } } /** * 添加底部View * * @param view 底部View */ public void addFooterView(View view) { if (footerViews.indexOfValue(view) < 0) { footerViews.put(BASE_ITEM_TYPE_FOOTER++, view); notifyDataSetChanged(); } } /** * 移除头部View * * @param view View */ public void removeHeaderView(View view) { int index = headerViews.indexOfValue(view); if (index > -1) { headerViews.removeAt(index); notifyDataSetChanged(); } } /** * 移除底部View * * @param view View */ public void removeFooterView(View view) { int index = footerViews.indexOfValue(view); if (index > -1) { footerViews.removeAt(index); notifyDataSetChanged(); } } } 

三、自定义RecyclerView

继承 RecyclerView 实现 WrapRecyclerView 子类
首先需要声明两个变量

 /** * 用来指向传入的 Adapter 或来构造 WrapRecyclerViewAdapter */ private WrapRecyclerViewAdapter wrapRecyclerViewAdapter; /** * 用来指向传入的Adapter */ private Adapter mRecyclerAdapter; 

仿照ListView的思路来重写 setAdapter() 方法,构建一个 WrapRecyclerViewAdapter 对象作为实际的Adapter。
当中,需要注意的是,如果传入的 recyclerAdapter 是直接继承于 WrapViewRecycleAdapter 的话,则直接强转类型就可以了,否则的话需要再来根据 recyclerAdapter 构造一个 WrapViewRecycleAdapter 对象
如果 wrapRecyclerViewAdapter 是通过强转得来的,则当 mRecyclerAdapter 数据刷新时,wrapRecyclerViewAdapter 自然也会做出相应的变化,因为两者指向的是同一个对象。
如果 wrapRecyclerViewAdapter 是用new关键字重新声明的,则需要在为 mRecyclerAdapter 注册一个观察者对象,在 mRecyclerAdapter 数据刷新时同时通知 wrapRecyclerViewAdapter 也进行数据刷新。

 @Override public void setAdapter(Adapter recyclerAdapter) { if (mRecyclerAdapter != null) { mRecyclerAdapter.unregisterAdapterDataObserver(adapterDataObserver); mRecyclerAdapter = null; } mRecyclerAdapter = recyclerAdapter; // 如果传入的 recyclerAdapter 是直接继承于 WrapViewRecycleAdapter 的话,则直接强转类型 // 否则的话再来根据 recyclerAdapter 构造一个 WrapViewRecycleAdapter if (mRecyclerAdapter instanceof WrapRecyclerViewAdapter) { wrapRecyclerViewAdapter = (WrapRecyclerViewAdapter) mRecyclerAdapter; } else { // 注册观察者对象 mRecyclerAdapter.registerAdapterDataObserver(adapterDataObserver); wrapRecyclerViewAdapter = new WrapRecyclerViewAdapter(mRecyclerAdapter); } super.setAdapter(wrapRecyclerViewAdapter); } 

总的代码如下所示:

/** * 作者: 叶应是叶 * 时间: 2017/6/4 * 描述: 可以带头部View与尾部View的RecyclerView */ public class WrapRecyclerView extends RecyclerView { /** * 用来指向传入的 Adapter 或来构造 WrapRecyclerViewAdapter */ private WrapRecyclerViewAdapter wrapRecyclerViewAdapter; /** * 用来指向传入的Adapter */ private Adapter mRecyclerAdapter; public WrapRecyclerView(Context context) { super(context); } public WrapRecyclerView(Context context, AttributeSet attrs) { super(context, attrs); } public WrapRecyclerView(Context context, AttributeSet attrs, int defStyle) { super(context, attrs, defStyle); } @Override public void setAdapter(Adapter recyclerAdapter) { if (mRecyclerAdapter != null) { mRecyclerAdapter.unregisterAdapterDataObserver(adapterDataObserver); mRecyclerAdapter = null; } mRecyclerAdapter = recyclerAdapter; // 如果传入的 recyclerAdapter 是直接继承于 WrapViewRecycleAdapter 的话,则直接强转类型 // 否则的话再来根据 recyclerAdapter 构造一个 WrapViewRecycleAdapter if (mRecyclerAdapter instanceof WrapRecyclerViewAdapter) { wrapRecyclerViewAdapter = (WrapRecyclerViewAdapter) mRecyclerAdapter; } else { // 注册观察者对象 mRecyclerAdapter.registerAdapterDataObserver(adapterDataObserver); wrapRecyclerViewAdapter = new WrapRecyclerViewAdapter(mRecyclerAdapter); } super.setAdapter(wrapRecyclerViewAdapter); } /** * AdapterDataObserver是RecyclerView内部的一个抽象类 * 用来作为观察者监听数据变化 */ private AdapterDataObserver adapterDataObserver = new AdapterDataObserver() { @Override public void onChanged() { if (wrapRecyclerViewAdapter != mRecyclerAdapter) { wrapRecyclerViewAdapter.notifyDataSetChanged(); } } @Override public void onItemRangeRemoved(int positionStart, int itemCount) { if (wrapRecyclerViewAdapter != mRecyclerAdapter) { wrapRecyclerViewAdapter.notifyItemRemoved(positionStart); } } @Override public void onItemRangeMoved(int fromPosition, int toPosition, int itemCount) { if (wrapRecyclerViewAdapter != mRecyclerAdapter) { wrapRecyclerViewAdapter.notifyItemMoved(fromPosition, toPosition); } } @Override public void onItemRangeChanged(int positionStart, int itemCount) { if (wrapRecyclerViewAdapter != mRecyclerAdapter) { wrapRecyclerViewAdapter.notifyItemChanged(positionStart); } } @Override public void onItemRangeChanged(int positionStart, int itemCount, Object payload) { if (wrapRecyclerViewAdapter != mRecyclerAdapter) { wrapRecyclerViewAdapter.notifyItemChanged(positionStart, payload); } } @Override public void onItemRangeInserted(int positionStart, int itemCount) { if (wrapRecyclerViewAdapter != mRecyclerAdapter) { wrapRecyclerViewAdapter.notifyItemInserted(positionStart); } } }; /** * 添加头部View * * @param view View */ public void addHeaderView(View view) { if (wrapRecyclerViewAdapter != null) { wrapRecyclerViewAdapter.addHeaderView(view); } else { throw new RuntimeException("WrapRecyclerViewAdapter == null"); } } /** * 添加底部View * * @param view View */ public void addFooterView(View view) { if (wrapRecyclerViewAdapter != null) { wrapRecyclerViewAdapter.addFooterView(view); } else { throw new RuntimeException("WrapRecyclerViewAdapter == null"); } } /** * 移除头部View * * @param view View */ public void removeHeaderView(View view) { if (wrapRecyclerViewAdapter != null) { wrapRecyclerViewAdapter.removeHeaderView(view); } else { throw new RuntimeException("WrapRecyclerViewAdapter == null"); } } /** * 移除底部View * * @param view View */ public void removeFooterView(View view) { if (wrapRecyclerViewAdapter != null) { wrapRecyclerViewAdapter.removeFooterView(view); } else { throw new RuntimeException("WrapRecyclerViewAdapter == null"); } } } 

四、实际使用

以我上一篇文章:解析RecyclerView(1)——带点击事件监听的通用Adapter 使用到的 MyCommonRecyclerAdapter 类作为最原始的Adapter

在布局文件中声明的RecyclerView就要使用自定义的 WrapRecyclerView 了,再增加几个按钮用于进行RecyclerView展示的数据,顶部View和底部View的增添删除操作。

<?xml version="1.0" encoding="utf-8"?> <RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:tools="http://schemas.android.com/tools" android:layout_width="match_parent" android:layout_height="match_parent" tools:context="com.czy.demo.RecyclerView.Wrap.WrapRecyclerActivity"> <Button android:id="@+id/btn_addData" android:layout_width="match_parent" android:layout_height="wrap_content" android:text="增添数据" /> <Button android:id="@+id/btn_deleteData" android:layout_width="match_parent" android:layout_height="wrap_content" android:layout_below="@id/btn_addData" android:text="删除数据" /> <Button android:id="@+id/btn_addHeaderView" android:layout_width="match_parent" android:layout_height="wrap_content" android:layout_below="@id/btn_deleteData" android:text="增加头部View" /> <Button android:id="@+id/btn_deleteHeaderView" android:layout_width="match_parent" android:layout_height="wrap_content" android:layout_below="@id/btn_addHeaderView" android:text="删除头部View" /> <Button android:id="@+id/btn_addFooterView" android:layout_width="match_parent" android:layout_height="wrap_content" android:layout_below="@id/btn_deleteHeaderView" android:text="增加底部View" /> <Button android:id="@+id/btn_deleteFooterView" android:layout_width="match_parent" android:layout_height="wrap_content" android:layout_below="@id/btn_addFooterView" android:text="删除底部View" /> <com.czy.common.RecyclerView.Wrap.WrapRecyclerView android:id="@+id/wrv_dataList" android:layout_width="match_parent" android:layout_height="match_parent" android:layout_below="@id/btn_deleteFooterView" /> </RelativeLayout> 

Activity总的代码如下所示,加载的顶部View布局文件 R.layout.header_view 只包含含一个 ImageView 控件,底部View布局文件 R.layout.footer _view 只包含一个 TextView 控件

public class WrapRecyclerActivity extends AppCompatActivity implements CommonRecyclerHolder.onClickCommonListener, View.OnClickListener { private List<Data> dataList; private List<View> headerViewList; private List<View> footerViewList; private WrapRecyclerView wrv_dataList; private MyCommonRecyclerAdapter myCommonRecyclerAdapter; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_wrap_recycler); initData(); wrv_dataList = (WrapRecyclerView) findViewById(R.id.wrv_dataList); wrv_dataList.setLayoutManager(new LinearLayoutManager(this)); myCommonRecyclerAdapter = new MyCommonRecyclerAdapter(this, dataList, R.layout.item, this); wrv_dataList.setAdapter(myCommonRecyclerAdapter); View headerView1 = getLayoutInflater().inflate(R.layout.header_view, wrv_dataList, false); View headerView2 = getLayoutInflater().inflate(R.layout.header_view, wrv_dataList, false); View footerView1 = getLayoutInflater().inflate(R.layout.footer_view, wrv_dataList, false); View footerView2 = getLayoutInflater().inflate(R.layout.footer_view, wrv_dataList, false); wrv_dataList.addHeaderView(headerView1); wrv_dataList.addHeaderView(headerView2); wrv_dataList.addFooterView(footerView1); wrv_dataList.addFooterView(footerView2); headerViewList.add(headerView1); headerViewList.add(headerView2); footerViewList.add(footerView1); footerViewList.add(footerView2); findViewById(R.id.btn_addData).setOnClickListener(this); findViewById(R.id.btn_deleteData).setOnClickListener(this); findViewById(R.id.btn_addHeaderView).setOnClickListener(this); findViewById(R.id.btn_deleteHeaderView).setOnClickListener(this); findViewById(R.id.btn_addFooterView).setOnClickListener(this); findViewById(R.id.btn_deleteFooterView).setOnClickListener(this); } @Override public void onClick(View v) { switch (v.getId()) { case R.id.btn_addData: Data data = new Data(R.mipmap.ic_launcher, "Hi"); dataList.add(data); myCommonRecyclerAdapter.notifyDataSetChanged(); break; case R.id.btn_deleteData: if (dataList.size() > 0) { dataList.remove(0); } myCommonRecyclerAdapter.notifyDataSetChanged(); break; case R.id.btn_addHeaderView: View headerView = getLayoutInflater().inflate(R.layout.header_view, wrv_dataList, false); headerViewList.add(headerView); wrv_dataList.addHeaderView(headerView); break; case R.id.btn_deleteHeaderView: if (headerViewList.size() > 0) { wrv_dataList.removeHeaderView(headerViewList.get(0)); headerViewList.remove(0); } break; case R.id.btn_addFooterView: View footerView = getLayoutInflater().inflate(R.layout.footer_view, wrv_dataList, false); footerViewList.add(footerView); wrv_dataList.addFooterView(footerView); break; case R.id.btn_deleteFooterView: if (footerViewList.size() > 0) { wrv_dataList.removeFooterView(footerViewList.get(0)); footerViewList.remove(0); } break; } } private void initData() { dataList = new ArrayList<>(); headerViewList = new ArrayList<>(); footerViewList = new ArrayList<>(); for (int i = 0; i < 50; i++) { Data data = new Data(R.mipmap.ic_launcher_round, "Hi:" + i); dataList.add(data); } } @Override public void onClick(int position) { Toast.makeText(this, "点击:" + position, Toast.LENGTH_SHORT).show(); } @Override public void onLongClick(int position) { Toast.makeText(this, "长按:" + position, Toast.LENGTH_SHORT).show(); } } 

运行效果:

img_ac782a91ca6a3343dd8b2c4a61a5fde2.gif
这里写图片描述

这里提供代码下载:解析RecyclerView(2)——带顶部View和底部View的RecyclerView

原文链接:https://yq.aliyun.com/articles/649713
关注公众号

低调大师中文资讯倾力打造互联网数据资讯、行业资源、电子商务、移动互联网、网络营销平台。

持续更新报道IT业界、互联网、市场资讯、驱动更新,是最及时权威的产业资讯及硬件资讯报道平台。

转载内容版权归作者及来源网站所有,本站原创内容转载请注明来源。

文章评论

共有0条评论来说两句吧...

文章二维码

扫描即可查看该文章

点击排行

推荐阅读

最新文章