效果图
通过装饰者模式添加头部底部
ListView提供了addHeadView和addFootView方法,使用起来非常方便,而ReyclerView则需要我们自己在adapter中判断,常规做法无外乎判断position==0,来添加头布局,不过这个做法只适合单个,而且不通用。
我们看下ListView.addHeadView是如何实现的
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;
if (mAdapter != null) {
if (!(mAdapter instanceof HeaderViewListAdapter)) {
mAdapter = new HeaderViewListAdapter(mHeaderViewInfos, mFooterViewInfos, mAdapter);
}
if (mDataSetObserver != null) {
mDataSetObserver.onChanged();
}
}
}
这里 mAdapter = new HeaderViewListAdapter(mHeaderViewInfos,mFooterViewInfos, mAdapter),new 了一个新的adapter,并把当前的adapter传进去了,这里其实采用的装饰者模式来做的,照着他的源码做一个试试。
1、创建WrapperHeadAndFootAdapter继承成RecylcerView.Adapter,同时,创建头部集合和底部集合,添加集合和移除集合方法。
这里采用SparseArray 取代HashMap 原因有两点:
- SparseArray默认以int为key,少了拆箱装箱流程
- 在千量级,性能优于HashMap,并且更轻量,更省内存。
可以看看这篇 –> Android内存优化–SparseArray和ArrayMap
public class WrapperHeadAndFootAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolder> {
//头布局集合
public SparseArray<View> mHeads =new SparseArray<>();
//底布局集合
public SparseArray<View> mFoots =new SparseArray<>();
//被装饰的Adapter
public RecyclerView.Adapter mAdapter;
//头部集合的key
public static int HEAD_KEY=1000000;
//底部集合的key
public static int FOOT_KEY=3000000;
public WrapperHeadAndFootAdapter(RecyclerView.Adapter adapter){
if(adapter==null){
throw new IllegalArgumentException("adapter cannot null");
}
mAdapter=adapter;
}
@Override
public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
return null;
}
@Override
public void onBindViewHolder(RecyclerView.ViewHolder holder, int position) {
}
@Override
public int getItemCount() {
return 0;
}
/**
* 添加头布局
*/
public void addHeadView(View view){
if(mHeads.indexOfValue(view)==-1){
mHeads.put(HEAD_KEY++,view);
}
}
/**
* 移除头布局
*/
public void removeHeadView(View view){
removeHeadView(view,-1);
}
/**
* 移除头布局
*/
public void removeHeadView(View view,int posistion){
int index = mHeads.indexOfValue(view);
if(index >=0){
mHeads.removeAt(index);
}
notifyItemRemoved(posistion);
}
/**
* 添加底布局
*/
public void addFootView(View view){
if(mFoots.indexOfValue(view)==-1){
mFoots.put(FOOT_KEY++,view);
}
}
/**
* 删除底布局
*/
public void removeFootView(View view){
removeFootView(view,-1);
}
/**
* 删除底布局
*/
public void removeFootView(View view,int position){
int index = mFoots.indexOfValue(view);
if(index>0){
mFoots.removeAt(index);
}
notifyItemRemoved(position);
}
}
这里,可能有疑问为什么mHeads和mFoots的key取值分别为100000和300000,其实这里不一定非要是这个,你也可以是0或者负数,但是这里面有个问题,当在getItemViewType中,返回key的时候,假如是0,而我们的mAdapter的默认返回值就是0,那在onCreateViewHolder中就会产生冲突,尤其是多布局的情况下,viewType的值并不确定,所以保险起见,弄了一个这个值,这个值随意,只要保证不冲突即可。
@Override
public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
//头布局--viewType是mHeads的key
if (mHeads.indexOfKey(viewType)>=0){
return new RecyclerView.ViewHolder(mHeads.get(viewType)) {
};
}
if(mFoots.indexOfKey(viewType)>=0){
return new RecyclerView.ViewHolder(mFoots.get(viewType)) {
};
}
return mAdapter.onCreateViewHolder(parent,viewType);
}
@Override
public void onBindViewHolder(RecyclerView.ViewHolder holder, int position) {
int normlPosition= position - mHeads.size();
int itemCount = mAdapter.getItemCount();
if(isHeadView(position)){
//如果是头部,不绑定
if(onHeadClickListener!=null){
//设置头部的点击事件
final View headView = mHeads.get(mHeads.keyAt(position));
if(headView!=null){
headView.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
onHeadClickListener.onClick(headView,getHeadPosition(headView));
}
});
}
}
return;
}
if(isFootView(position)){
//如果是底部,不绑定
if(onFootClickListener!=null){
//设置底部点击事件
final View footView = mFoots.get(mFoots.keyAt(normlPosition-itemCount));
if(footView!=null){
footView.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
onFootClickListener.onClick(footView,getHeadPosition(footView));
}
});
}
return;
}
//正常布局
if(normlPosition<itemCount){
mAdapter.onBindViewHolder(holder,normlPosition);
}
}
@Override
public int getItemCount() {
return mHeads.size()+mAdapter.getItemCount()+mFoots.size();
}
@Override
public int getItemViewType(int position) {
//头布局
if(isHeadView(position)){
return mHeads.keyAt(position);
}
//正常列表
int normlPosition=position-mHeads.size();
int itemCount = mAdapter.getItemCount();
if(normlPosition<itemCount){
return mAdapter.getItemViewType(normlPosition);
}
//底布局
int footPosition=normlPosition-itemCount;
return mFoots.keyAt(footPosition);
}
private boolean isFootView(int position){
return position>=mHeads.size()+mAdapter.getItemCount();
}
private boolean isHeadView(int position){
int size = mHeads.size();
return position<size;
}
/**
* 设置头部的点击事件
* @param onHeadClickListener
*/
public void setOnHeadClickListener(OnHeadClickListener onHeadClickListener){
this.onHeadClickListener = onHeadClickListener;
}
/**
* 设置底部的点击事件
*/
public void setOnFootClickListener(OnFootClickListener onFootClickListener){
this.onFootClickListener = onFootClickListener;
}
/**
* 解决GridLayoutManager添加头部和底部不占用一行的问题
*/
public void adjustSpanSize(RecyclerView recycler) {
if (recycler.getLayoutManager() instanceof GridLayoutManager) {
final GridLayoutManager layoutManager = (GridLayoutManager) recycler.getLayoutManager();
layoutManager.setSpanSizeLookup(new GridLayoutManager.SpanSizeLookup() {
@Override
public int getSpanSize(int position) {
boolean isHeaderOrFooter = isHeaderPosition(position) || isFooterPosition(position);
return isHeaderOrFooter ? layoutManager.getSpanCount() : 1;
}
});
}
}
/**
* 获取头部的位置
* @param view
* @return
*/
public int getHeadPosition(View view) {
return mHeads.indexOfValue(view);
}
/**
* 获取底部的位置
*/
public int getFootPosition(View view){
int position=mFoots.indexOfValue(view)+mHeads.size()+mAdapter.getItemCount();
return position;
}
上面代码 其实没什么好注意的,唯一注意的是在onBindViewHolder绑定数据的时候,如果是头布局或者底布局要记得return,否则会爆空指针异常。
下面是头部和底部点击的监听
public interface OnFootClickListener {
void onClick(View view, int position);
}
public interface OnHeadClickListener {
void onClick(View view, int position);
}
调用
RecyclerView recyclerView = (RecyclerView) findViewById(R.id.recyclerView);
findViewById(R.id.chose).setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
if(!chose){
chose=!chose;
recyclerView.setLayoutManager(new GridLayoutManager(TestActivity1.this,2));
recyclerView.getAdapter().notifyDataSetChanged();
wrapperAdapter.spanSize(recyclerView);
}else{
chose=!chose;
recyclerView.setLayoutManager(new LinearLayoutManager(TestActivity1.this));
recyclerView.getAdapter().notifyDataSetChanged();
}
}
});
recyclerView.setLayoutManager(new LinearLayoutManager(this));
//原始adapter
MyAdapter dapter = new MyAdapter(getDatas(), this);
//wrapper
wrapperAdapter = new WrapperHeadAndFootAdapter(dapter);
LayoutInflater layoutInflater = LayoutInflater.from(this);
recyclerView.setAdapter(wrapperAdapter);
wrapperAdapter.addHeadView(layoutInflater.inflate(R.layout.head_view,recyclerView,false));
wrapperAdapter.addHeadView(layoutInflater.inflate(R.layout.head_view,recyclerView,false));
wrapperAdapter.addFootView(layoutInflater.inflate(R.layout.head_view,recyclerView,false));
wrapperAdapter.addFootView(layoutInflater.inflate(R.layout.head_view,recyclerView,false));
//原始adapter的条目点击事件
dapter.setOnItemClickListener(new OnItemClickListener<CharBean>() {
@Override
public void onItem(int position, CharBean charBean) {
Toast.makeText(TestActivity1.this,charBean.getSrc(),Toast.LENGTH_SHORT).show();
}
});
//底部监听事件
wrapperAdapter.setOnFootClickListener(new OnFootClickListener() {
@Override
public void onClick(View view, int position) {
wrapperAdapter.removeFootView(view,position);
}
});
//头部监听事件
wrapperAdapter.setOnHeadClickListener(new OnHeadClickListener() {
@Override
public void onClick(View view, int position) {
wrapperAdapter.removeHeadView(view,position);
}
});
这样当然有点麻烦,我们可以封装成一个WrapperRecyclerView,例如这样
就可以欢快的调用了。
@Override
public void setAdapter(Adapter adapter) {
if (mAdapter != null) {
mAdapter.unregisterAdapterDataObserver(mDataObserver);
mAdapter = null;
}
mAdapter = adapter;
mAdapter.notifyDataSetChanged();
mWrapperAdapter = new WrapperHeadAndFootAdapter(adapter,mHeads,mFoots);
super.setAdapter(mWrapperAdapter);
//适配GridLayoutManager
mWrapperAdapter.spanSize(this);
//注册监听器
mWrapperAdapter.registerAdapterDataObserver(mDataObserver);
}