转载请注明出处:http://blog.youkuaiyun.com/anyfive/article/details/53022682
前言
在上一篇文章中,我们介绍了下拉刷新上拉加载RecyclerView的使用,从这篇开始,我将对这个项目的具体实现详细介绍,这篇首先介绍添加删除头尾部的实现。
大家都知道recyclerview并不能像listview一样,可以直接使用addHeaderView和addFooterView添加头部和尾部,而在实际的项目中,我们常常需要实现这些功能。既然recyclerview不提供,那我们就自己写一个呗。
关于给recyclerview添加头部和尾部,网上已有非常多的介绍,比如我们的鸿洋大神就写过一篇 《 Android 优雅的为RecyclerView添加HeaderView和FooterView》。
但是啊,添加有了,删除头尾部的介绍网上比较少。看过鸿洋大神这篇文章的同学想必都知道,给recyclerview添加头尾部,其实就是 在adapter中使用键值数组对存储头部和尾部,addHeaderView其实就是往头部的键值对数组中添加view。那么removeHeaderView呢?当然就是从头部的键值对数组中删除view啦!!没错,就是这么简单。
这时候,有同学把网上的代码拷下来,开始在adapter中添加removeHeaderView方法,恩,完美。添加View0,添加View1,删除View1,哦!删除成功,添加View2,哎?怎么添加的是View1??我的View2呢?
不要问我怎么知道的,因为我就这样干过。
添加删除头尾部
那么我们还是先一步步来介绍下头尾部的实现吧,recyclerview的adapter需要实现以下几个方法:
- 创建ViewHolder:onCreateViewHolder(ViewGroup parent, int viewType);
- 绑定数据:onBindViewHolder(RecyclerView.ViewHolder holder, int position);
- 获得item的总数:getItemCount();
恩,跟ListView还是有点不一样的。在ListView的时候,当我们需要展示几种类型的item时,我们会使用到一个方法:
- 获得item的类型:getItemViewType(int position);
幸运的是,recyclerview中也提供这个方法,有这个方法就好办了,我们把头尾部当成是一种item不就好了嘛,用键值对数组保存头尾部,类型type作为key,view作为value。
因此思路就是:
- 使用键值对数组(ArrayList、HashMap、SparseArrayCompat等)保存头尾部
- 在getItemViewType方法中,判断是否头尾部,是的话返回对应的key,也就是type
- 在onCreateViewHolder中,判断是否是头尾部,是的话返回对应的ViewHolder
- 在onBindViewHolder中,判断是否是头尾部,是的话不处理
大概就是这样,下面我们看看这部分代码,一个一个来:
首先,键值对保存头尾部:
protected SparseArrayCompat<View> mHeaderViews = new SparseArrayCompat<>();
protected SparseArrayCompat<View> mFooterViews = new SparseArrayCompat<>();
然后,getItemCount方法,返回item数量+头尾部总数:
@Override
public int getItemCount() {
return getRealItemCount()+getHeadersCount()+getFootersCount();
}
再然后,getItemViewType方法:
@Override
public int getItemViewType(int position) {
if (isHeaderPosition(position)) {
return mHeaderViews.keyAt(position);
}
if (isFooterPosition(position)) {
return mFooterViews.keyAt(position - getHeadersCount() - getRealItemCount());
}
return mRealAdapter.getItemViewType(position - getHeadersCount());
}
再再然后,onCreateViewHolder方法:
@Override
public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
if (isHeaderType(viewType)) {
int headerPosition = mHeaderViews.indexOfKey(viewType);
View headerView = mHeaderViews.valueAt(headerPosition);
return createHeaderAndFooterViewHolder(headerView);
}
if (isFooterType(viewType)) {
int footerPosition = mFooterViews.indexOfKey(viewType);
View footerView = mFooterViews.valueAt(footerPosition);
return createHeaderAndFooterViewHolder(footerView);
}
return mRealAdapter.onCreateViewHolder(parent, viewType);
}
再再再然后,onBindViewHolder方法:
@Override
public void onBindViewHolder(RecyclerView.ViewHolder holder, int position) {
if (isHeaderPosition(position) || isFooterPosition(position)) {
} else {
mRealAdapter.onBindViewHolder(holder, realPosition);
}
}
再再再再然后,添加addHeaderView和addFooterView方法:
public void addHeaderView(View view) {
mHeaderViews.put(BASE_ITEM_TYPE_HEADER++, view);
notifyDataSetChanged();
}
public void addFooterView(View view) {
mFooterViews.put(BASE_ITEM_TYPE_FOOTER ++,view);
notifyDataSetChanged();
}
注意:这里如果你使用了网上的代码,比如鸿洋大神的:
private static final int BASE_ITEM_TYPE_HEADER = 100000;
public void addHeaderView(View view)
{
mHeaderViews.put(mHeaderViews.size() + BASE_ITEM_TYPE_HEADER, view);
}
如果你是这样写的,那就会造成前言部分说了View2没有添加成功的问题了。
那么是为什么呢?我们来推演一番:
- 添加View0,type是100000;
- 添加View1,type是100001;
- 删除View1;
- 添加View2,type是100001;
啊哦,getItemViewType拿到的type还是原来view1的type,那自然View2会变成View1了。那怎么办呢?我们用自增不就好了嘛。
恩,接着上面,继续加上删除头尾部的方法:
public void removeHeaderView(View view) {
int index = mHeaderViews.indexOfValue(view);
if (index < 0) return;
mHeaderViews.removeAt(index);
notifyDataSetChanged();
}
public void removeFooterView(View view) {
int index = mFooterViews.indexOfValue(view);
if (index < 0) return;
mFooterViews.removeAt(index);
notifyDataSetChanged();
}
恩,这下应该没问题了。
等等!LinearLayoutManager是没问题了,GridLayoutManager和StaggeredGridLayoutManager呢?其实想想就知道,肯定会有问题的嘛~~问题就是头部和尾部的宽度会和item一样长,而不是我们希望的填满recyclerview的宽度,每个头尾部单独占一行。
当然解决办法鸿洋大大也说过了,网上也有一大堆:
/**解决GridLayoutManager问题*/
@Override
public void onAttachedToRecyclerView(RecyclerView recyclerView) {
mRealAdapter.onAttachedToRecyclerView(recyclerView);
RecyclerView.LayoutManager layoutManager = recyclerView.getLayoutManager();
if (layoutManager instanceof GridLayoutManager) {
final GridLayoutManager gridLayoutManager = (GridLayoutManager) layoutManager;
gridLayoutManager.setSpanSizeLookup(new GridLayoutManager.SpanSizeLookup() {
@Override
public int getSpanSize(int position) {
if (isHeaderPosition(position) || isFooterPosition(position))
return gridLayoutManager.getSpanCount();
return 1;
}
});
}
}
/**解决瀑布流布局问题*/
@Override
public void onViewAttachedToWindow(RecyclerView.ViewHolder holder) {
mRealAdapter.onViewAttachedToWindow(holder);
int position = holder.getLayoutPosition();
if (isHeaderPosition(position) || isFooterPosition(position)) {
ViewGroup.LayoutParams lp = holder.itemView.getLayoutParams();
if (lp != null && lp instanceof StaggeredGridLayoutManager.LayoutParams) {
StaggeredGridLayoutManager.LayoutParams layoutParams = (StaggeredGridLayoutManager.LayoutParams) lp;
layoutParams.setFullSpan(true);
}
}
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
搞定~
好了,再看下效果图吧:



当然,我们要做的是封装一个可以添加删除头尾部的RecyclerView,这算哪门子的封装啊?我们当然要把这些个方法封装好一点,使用起来也爽嘛。
一提起封装,我脑子里就冒出个东西:泛型。
泛型真是个好东西啊,那个灵活,那个爽,啧啧啧,嘿嘿……好了,关于泛型这里就不详细介绍了,我们还是来说下封装吧。(一本正经脸)
在开始封装之前,先问问自己:“如果是你使用,你想怎么用?”
“那还用说?当然是还和原来一样setAdapter,像listview一样直接用addHeaderView啊!”
“恩,没问题,开始吧”
- 继承RecyclerView,重写setAdapter方法,添加addHeaderView等方法;
- 用户设置的adapter,作为真正的adapter(mRealAdapter)传入头尾部Adapter;
先看看第一步:
protected HeaderAndFooterAdapter mAdapter;
protected Adapter mRealAdapter;
@Override
public void setAdapter(Adapter adapter) {
mRealAdapter = adapter;
if (adapter instanceof HeaderAndFooterAdapter) {
mAdapter = (HeaderAndFooterAdapter) adapter;
}
else {
mAdapter = new HeaderAndFooterAdapter(getContext(),adapter);
}
super.setAdapter(mAdapter);
}
public void addHeaderView(View view) {
if (null == view) {
throw new IllegalArgumentException("the view to add must not be null !");
} else if (mAdapter == null) {
throw new IllegalStateException("u must set a adapter first !");
} else {
mAdapter.addHeaderView(view);
}
}
public void addFooterView(View view) {
if (null == view) {
throw new IllegalArgumentException("the view to add must not be null !");
} else if (mAdapter == null) {
throw new IllegalStateException("u must set a adapter first !");
} else {
mAdapter.addFooterView(view);
}
}
public void removeHeaderView(View view) {
if (null == view) {
throw new IllegalArgumentException("the view to remove must not be null !");
} else if (mAdapter == null) {
throw new IllegalStateException("u must set a adapter first !");
} else {
mAdapter.removeHeaderView(view);
}
}
public void removeFooterView(View view) {
if (null == view) {
throw new IllegalArgumentException("the view to remove must not be null !");
} else if (mAdapter == null) {
throw new IllegalStateException("u must set a adapter first !");
} else {
mAdapter.removeFooterView(view);
}
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
- 34
- 35
- 36
- 37
- 38
- 39
- 40
- 41
- 42
- 43
- 44
- 45
- 46
- 47
- 48
- 49
- 50
- 51
- 52
- 53
- 54
- 55
完美,再看看第二步,使用泛型就好啦~
public class HeaderAndFooterAdapter<T extends RecyclerView.Adapter> extends RecyclerView.Adapter {
protected T mRealAdapter;
protected Context mContext;
public HeaderAndFooterAdapter(Context mContext, T mRealAdapter) {
super();
this.mContext = mContext;
this.mRealAdapter = mRealAdapter;
}
public T getRealAdapter() {
return mRealAdapter;
}
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
好了,这样就可以了。使用的时候,你还是按照你原来的方式setAdapter,不用管其他,想addHeaderView就直接调HeaderAndFooterRecyclerView的addHeaderView方法就好啦,和ListView是一样样的。
添加点击事件
本来添加点击事件我不想写了,因为网上太多太多例子了,但想到都说了这么多了,再说一下也没什么啦。
其实添加点击事件就是给每个item添加一个点击监听嘛,然后传入position就够了,注意:因为我们添加了头部和尾部,position需要处理一下,不然到时候对用户来说,你的position就是不准的。
@Override
public void onBindViewHolder(RecyclerView.ViewHolder holder, int position) {
if (isHeaderPosition(position) || isFooterPosition(position)) {
} else {
final int realPosition = position - getHeadersCount();
mRealAdapter.onBindViewHolder(holder, realPosition);
if (mOnItemClickListener != null) {
holder.itemView.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
mOnItemClickListener.OnItemClick(realPosition);
}
});
}
if (mOnItemLongClickListener != null) {
holder.itemView.setOnLongClickListener(new View.OnLongClickListener() {
@Override
public boolean onLongClick(View v) {
return mOnItemLongClickListener.onItemLongClick(realPosition);
}
});
}
}
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
这样就可以了,点击和长按就加上去了,接下来跟addHeaderView一样,加几个方法用来调用就可以了。
在adapter中加入:
protected OnItemClickListener mOnItemClickListener;
protected OnItemLongClickListener mOnItemLongClickListener;
public void setOnItemClickListener(OnItemClickListener onItemClickListener) {
this.mOnItemClickListener = onItemClickListener;
}
public void setOnItemLongClickListener(OnItemLongClickListener onItemLongClickListener) {
this.mOnItemLongClickListener = onItemLongClickListener;
}
在HeaderAndFooterRecyclerView中加入:
public void setOnItemClickListener(OnItemClickListener onItemClickListener) {
if (null == mAdapter) {
throw new IllegalStateException("u must set a adapter first !");
} else {
mAdapter.setOnItemClickListener(onItemClickListener);
}
}
public void setOnItemLongClickListener(OnItemLongClickListener onItemLongClickListener) {
if (null == mAdapter) {
throw new IllegalStateException("u must set a adapter first !");
} else {
mAdapter.setOnItemLongClickListener(onItemLongClickListener);
}
}
搞定,至此,PTLRecyclerView的头尾部相关就介绍完了,其实PTLRecyclerView的HeaderAndFooterRecyclerView中还封装了EmptyView的实现,这里就不介绍了,有兴趣的同学可以去看看源码。
源码地址:https://github.com/whichname/PTLRecyclerView
有意见或建议或疑问等等,欢迎提出~~
传送门:
android 打造真正的下拉刷新上拉加载recyclerview(一):使用
android 打造真正的下拉刷新上拉加载recyclerview(三):下拉刷新上拉加载
android 打造真正的下拉刷新上拉加载recyclerview(四):自动加载和其他封装