Android 解析RecyclerView(2)——带顶部View和底部View的RecyclerView
来源:互联网 发布:淘宝发货地址在哪里填 编辑:程序博客网 时间:2024/06/03 12:29
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(); }}
运行效果:
这里提供代码下载:解析RecyclerView(2)——带顶部View和底部View的RecyclerView
- Android 解析RecyclerView(2)——带顶部View和底部View的RecyclerView
- Android 解析RecyclerView(3)——以更简单的方法实现带顶部View和底部View的RecyclerView
- Android-View-RecyclerView
- android上使用RecyclerView实现顶部悬浮标题效果的Sticky Title View
- 自定义控件——带底部监听的RecyclerView
- ViewPager和RecyclerView缓存View的问题
- Android 解析RecyclerView(1)——带点击事件监听的通用Adapter
- Android从零开搞系列:自定义View(2)继承RecyclerView实现下拉刷新和加载更多
- RecyclerView 嵌套RecyclerView 或者 ScrollView当中嵌套RecyclerView ,子View会自动滚动到顶部
- 可以添加头部和尾部View并且支持setOnItemClickListener和滑到底部自动加载更多的RecyclerView
- Android Recyclerview判断是否已经到底部或者顶部
- android 删除ScrollView/RecyclerView等拉到尽头(顶部、底部),然后继续拉出现的阴影效果
- 去除listView和recyclerview滑动到顶部和底部边界的阴影
- android Recyclerview实现类似朋友圈点击添加图片的view
- RecyclerView检测滑动到顶部或底部的代码示例
- Android—RecyclerView带你飞
- Android 添加数据到RecyclerView的顶部
- 安卓RecyclerView视图滑动对齐不到顶部(底部)的校准
- packet tracer 的switch交换机的常用命令
- javaWeb的session和cookie实现记住密码自动登录功能简单实例
- 【操作系统】磁盘臂调度算法
- thefuck的安装和使用
- Node.js学习笔记
- Android 解析RecyclerView(2)——带顶部View和底部View的RecyclerView
- 音质检测的算法创新与实现
- Java 多模块项目创建
- iptables详解
- 线性筛素数
- python模块之搜索路径和路径搜索
- JavaScript的部分基本循环
- 找工作笔试面试那些事儿(4)---C++函数高级特征
- SpringMVC入门项目 注解版