前面已经说过RecyclerView的使用,RecyclerView通过其高度的可定制性深受大家的青睐,也有非常多的使用者开始对它进行封装或者改造,从而满足越来越多的需求,相信用过的一定会爱不释手。
在实际开发中经常会遇到给列表添加标题这样的需求,可是要给RecyclerView加个headerView或者footerView却发现没有这样的方法,怎么办呢?网上搜到的大多数方案都是通过控制Adapter的ItemType来设置的,不管是添加headerView还是footerView,它们都是Item的一种,只不过显示在特定的位置,那么我们完全可以通过为其设置ItemType来完成。所以核心就是根据不同的ItemType去加载不同的布局。
有了思路,我们就简单实现下吧
适配器代码如下:
public class HeaderAndFooterAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolder> {
private static final int ITEM_TYPE_HEADER = 1;
private static final int ITEM_TYPE_FOOTER = 2;
private Context mContext;
private List<String> mDatas = new ArrayList<>();
public HeaderAndFooterAdapter(Context mContext, List<String> mDatas) {
this.mDatas = mDatas;
this.mContext = mContext;
}
/*根据getItemViewType()方法返回的不同类型创建不同的ViewHolder*/
@Override
public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
if (viewType == ITEM_TYPE_HEADER) {
View view = LayoutInflater.from(mContext).inflate(android.R.layout.simple_list_item_1, parent, false);
HeaderHolder viewHolder = new HeaderHolder(view);
return viewHolder;
} else if (viewType == ITEM_TYPE_FOOTER) {
View view = LayoutInflater.from(mContext).inflate(android.R.layout.simple_list_item_1, parent, false);
FootHolder viewHolder = new FootHolder(view);
return viewHolder;
} else {
View view = LayoutInflater.from(mContext).inflate(R.layout.item_recyclerview_main, parent, false);
ItemHolder viewHolder = new ItemHolder(view);
return viewHolder;
}
}
@Override
public void onBindViewHolder(RecyclerView.ViewHolder holder, int position) {
if (holder instanceof HeaderHolder) {
HeaderHolder headerHolder = (HeaderHolder) holder;
headerHolder.textViewHeader.setText(mDatas.get(position));
} else if (holder instanceof FootHolder) {
FootHolder footHolder = (FootHolder) holder;
footHolder.textViewFoot.setText(mDatas.get(position));
} else {
ItemHolder itemHolder = (ItemHolder) holder;
itemHolder.textViewItem.setText(mDatas.get(position));
}
}
@Override
public int getItemCount() {
return mDatas == null ? 0 : mDatas.size();
}
/*根据位置来返回不同的item类型*/
@Override
public int getItemViewType(int position) {
if (position == 0) {
return ITEM_TYPE_HEADER;
} else if (position + 1 == getItemCount()) {
return ITEM_TYPE_FOOTER;
} else
return 0;
}
/*头部Item*/
class HeaderHolder extends RecyclerView.ViewHolder {
public TextView textViewHeader;
public HeaderHolder(View itemView) {
super(itemView);
textViewHeader = (TextView) itemView.findViewById(android.R.id.text1);
}
}
/*底部Item*/
class FootHolder extends RecyclerView.ViewHolder {
public TextView textViewFoot;
public FootHolder(View itemView) {
super(itemView);
textViewFoot = (TextView) itemView.findViewById(android.R.id.text1);
}
}
class ItemHolder extends RecyclerView.ViewHolder {
public TextView textViewItem;
public ItemHolder(View itemView) {
super(itemView);
textViewItem = (TextView) itemView.findViewById(R.id.tv_main_item);
}
}
}
-
getItemViewType
由于我们增加了headerView和footerView首先需要复写的就是getItemCount和getItemViewType。getItemCount很好理解,对于getItemType,返回的就是item所代表的type值。
-
onCreateViewHolder
可以看到,我们分别判断viewType,如果是headerView或者是footView,我们则为其单独创建ViewHolder。
-
onBindViewHolder
onBindViewHolder比较简单,只要根据不同的ViewHolder来进行绑定数据即可。
Activity类如下:
public class MainActivity extends AppCompatActivity {
private RecyclerView mRecyclerView;
private List<String> mDatas = new ArrayList<>();
private HeaderAndFooterAdapter mAdapter;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
mRecyclerView = (RecyclerView) findViewById(R.id.ryv_main_content);
LinearLayoutManager linearLayoutManager = new LinearLayoutManager(this);
mRecyclerView.setLayoutManager(linearLayoutManager);
mAdapter=new HeaderAndFooterAdapter(this,mDatas);
mRecyclerView.setAdapter(mAdapter);
initData();
mAdapter.notifyDataSetChanged();
}
private void initData() {
mDatas.add("列表头");
for (int i=0;i<20;i++){
mDatas.add("item"+i);
}
mDatas.add("列表尾");
}
}
效果如下:
好了,现在问题来了,假设我们现在已经完成了RecyclerView的编写,忽然有个需求,需要在列表上加个HeaderView,此时我们该怎么办呢?打开我们的Adapter,然后按照我们上述的原理,添加特殊的ViewType,然后修改代码完成。这是比较常规的做法了,但是有个问题是,如果需要添加多个viewType,那么可能我们的Adapter需要修改的幅度就比较大了,比如getItemType、getItemCount、onBindViewHolder、onCreateViewHolder等,几乎所有的方法都要进行改变。如果一个项目中多个RecyclerView都需要在其列表中添加headerView,想想都头大了,所以直接改Adapter的代码是非常不划算的,最好能够设计一个类,可以无缝的为原有的Adapter添加headerView和footerView。我也是看到了鸿洋大神的这篇文章才有了收获,思路是通过类似装饰者模式,去设计一个类,增强原有Adapter的功能,使其支持addHeaderView和addFooterView。这样我们就可以不去改动我们之前已经完成的代码,灵活的去扩展功能了。幸好大学的课本没丢,又翻出出来,重新看看装饰者模式,哈哈~~
初步实现
装饰类
public class HeaderAndFooterWrapper<T> extends RecyclerView.Adapter<RecyclerView.ViewHolder> {
private static final int BASE_ITEM_TYPE_HEADER = 0x10;
private static final int BASE_ITEM_TYPE_FOOTER = 0x20;
private SparseArrayCompat<View> mHeaderViews = new SparseArrayCompat<>();
private SparseArrayCompat<View> mFootViews = new SparseArrayCompat<>();
private RecyclerView.Adapter mInnerAdapter;
public HeaderAndFooterWrapper(RecyclerView.Adapter adapter) {
mInnerAdapter = adapter;
}
@Override
public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
if (mHeaderViews.get(viewType) != null) {
ViewHolder holder = ViewHolder.createViewHolder(parent.getContext(), mHeaderViews.get(viewType));
return holder;
} else if (mFootViews.get(viewType) != null) {
ViewHolder holder = ViewHolder.createViewHolder(parent.getContext(), mFootViews.get(viewType));
return holder;
}
return mInnerAdapter.onCreateViewHolder(parent, viewType);
}
@Override
public int getItemViewType(int position) {
if (isHeaderViewPos(position)) {
return mHeaderViews.keyAt(position);
} else if (isFooterViewPos(position)) {
return mFootViews.keyAt(position - getHeadersCount() - getRealItemCount());
}
return mInnerAdapter.getItemViewType(position - getHeadersCount());
}
private int getRealItemCount() {
return mInnerAdapter.getItemCount();
}
@Override
public void onBindViewHolder(RecyclerView.ViewHolder holder, int position) {
if (isHeaderViewPos(position)) {
return;
}
if (isFooterViewPos(position)) {
return;
}
mInnerAdapter.onBindViewHolder(holder, position - getHeadersCount());
}
@Override
public int getItemCount() {
return getHeadersCount() + getFootersCount() + getRealItemCount();
}
private boolean isHeaderViewPos(int position) {
return position < getHeadersCount();
}
private boolean isFooterViewPos(int position) {
return position >= getHeadersCount() + getRealItemCount();
}
public void addHeaderView(View view) {
mHeaderViews.put(mHeaderViews.size() + BASE_ITEM_TYPE_HEADER, view);
}
public void addFootView(View view) {
mFootViews.put(mFootViews.size() + BASE_ITEM_TYPE_FOOTER, view);
}
public int getHeadersCount() {
return mHeaderViews.size();
}
public int getFootersCount() {
return mFootViews.size();
}
}
注意这几个方法:
- 可以看到我们的返回值是mHeaderViews.keyAt(position),这个值其实就是我们 addHeaderView时的key,footerView是一样的处理方式,也就是说每一个headerView都会创建不同的itemType。
- 可以看到,我们分别判断viewType,如果是HeaderView或者是FooterView,我们则为其单独创建ViewHolder,这里的 ViewHolder我就直接拿鸿洋大神的用了,大家也可以自己写,只需要将对应的 headerView作为itemView传入ViewHolder的构造即可。而这里也就体现出SparseArrayCompat的作用了,对于headerView假设我们有多个,那么onCreateViewHolder返回的ViewHolder中的itemView应该对应不同的headerView,如果是List,那么不同的headerView应该对应着:list.get(0),list.get(1)等。但是onCreateViewHolder方法并没有position参数,只有itemType参数,如果itemType还是固定的一个值,那么你是没有办法根据参数得到不同的headerView的。
所以,我利用SparseArrayCompat,将其itemType作为key,value为我们的headerView,在 onCreateViewHolder中,直接通过itemType,即可获得我们的headerView,然后构造ViewHolder对象。
效果:
效果差不多,我们再来看看GridLayoutMananger的效果:
虽然效果不是我们想要的,但逻辑是正确的,好了,现在改下代码吧
针对GridLayoutManager
在HeaderAndFooterWrapper类中复写onAttachedToRecyclerView方法
@Override
public void onAttachedToRecyclerView(RecyclerView recyclerView) {
mInnerAdapter.onAttachedToRecyclerView(recyclerView);
RecyclerView.LayoutManager layoutManager = recyclerView.getLayoutManager();
if (layoutManager instanceof GridLayoutManager) {
final GridLayoutManager gridLayoutManager = (GridLayoutManager) layoutManager;
final GridLayoutManager.SpanSizeLookup spanSizeLookup = gridLayoutManager.getSpanSizeLookup();
gridLayoutManager.setSpanSizeLookup(new GridLayoutManager.SpanSizeLookup() {
@Override
public int getSpanSize(int position) {
int viewType = getItemViewType(position);
if (mHeaderViews.get(viewType) != null) {
return gridLayoutManager.getSpanCount();
} else if (mFootViews.get(viewType) != null) {
return gridLayoutManager.getSpanCount();
}
if (spanSizeLookup != null)
return spanSizeLookup.getSpanSize(position);
return 1;
}
});
gridLayoutManager.setSpanCount(gridLayoutManager.getSpanCount());
}
}
现在看一下运行效果:
对于StaggeredGridLayoutManager
当然了,我们怎么能忘了最重要的StaggeredGridLayoutManager呢 ?StaggeredGridLayoutManager并没有setSpanSizeLookup这样的方法,但是处理依然不复杂,重写onViewAttachedToWindow方法,如下:
@Override
public void onViewAttachedToWindow(RecyclerView.ViewHolder holder) {
mInnerAdapter.onViewAttachedToWindow(holder);
int position = holder.getLayoutPosition();
if (isHeaderViewPos(position) || isFooterViewPos(position)) {
ViewGroup.LayoutParams lp = holder.itemView.getLayoutParams();
if (lp != null
&& lp instanceof StaggeredGridLayoutManager.LayoutParams) {
StaggeredGridLayoutManager.LayoutParams p =
(StaggeredGridLayoutManager.LayoutParams) lp;
p.setFullSpan(true);
}
}
}
哦了,就是这样
源代码参考:
http://blog.csdn.net/lmj623565791/article/details/51854533