您的位置:首页 > 移动开发 > Android开发

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

2017-06-07 08:28 531 查看
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

内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: