随着使用的普及,RecyclerView
基本取代了listView
、gridView
等控件。
在日常开发中,有时候会使用到headerview
、footview
,但是RecyclerView
的API并没有提供类似于listview
的addfootview()
等放法,这种情况应该怎么处理呢?
普遍的解决方案就是通过指定adapter
的itemType
来区分将其与内容区域区分开。
本篇文章参考了其他博主的文章,并将一些细节部分进行了完善
鸿洋的博客:
Android 优雅的为RecyclerView添加HeaderView和FooterView
亓斌的博客
RecyclerView添加Header的正确方式
这是鸿洋封装的外部wrapper对象,很完善
public class HeaderAndFooterWrapper<T> extends RecyclerView.Adapter<RecyclerView.ViewHolder> {
private static final int BASE_ITEM_TYPE_HEADER = 100000;
private static final int BASE_ITEM_TYPE_FOOTER = 200000;
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) {
BaseViewHolder holder = BaseViewHolder.createViewHolder(parent.getContext(), mHeaderViews.get(viewType));
return holder;
} else if (mFootViews.get(viewType) != null) {
BaseViewHolder holder = BaseViewHolder.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();
}
@Override
public void onAttachedToRecyclerView(RecyclerView recyclerView) {
WrapperUtils.onAttachedToRecyclerView(mInnerAdapter, recyclerView, new WrapperUtils.SpanSizeCallback() {
@Override
public int getSpanSize(GridLayoutManager layoutManager, GridLayoutManager.SpanSizeLookup oldLookup, int position) {
int viewType = getItemViewType(position);
if (mHeaderViews.get(viewType) != null) {
return layoutManager.getSpanCount();
} else if (mFootViews.get(viewType) != null) {
return layoutManager.getSpanCount();
}
if (oldLookup != null)
return oldLookup.getSpanSize(position);
return 1;
}
});
}
@Override
public void onViewAttachedToWindow(RecyclerView.ViewHolder holder) {
mInnerAdapter.onViewAttachedToWindow(holder);
int position = holder.getLayoutPosition();
if (isHeaderViewPos(position) || isFooterViewPos(position)) {
WrapperUtils.setFullSpan(holder);
}
}
private boolean isHeaderViewPos(int position) {
return position < getHeadersCount();
}
private boolean isFooterViewPos(int position) {
return position >= getHeadersCount() + getRealItemCount();
}
public void addHeaderView(View view) {
view.setTag(mHeaderViews.size() + BASE_ITEM_TYPE_HEADER);
mHeaderViews.put(mHeaderViews.size() + BASE_ITEM_TYPE_HEADER, view);
}
public void addFootView(View view) {
view.setTag(mFootViews.size() + BASE_ITEM_TYPE_FOOTER);
mFootViews.put(mFootViews.size() + BASE_ITEM_TYPE_FOOTER, view);
}
public int getHeadersCount() {
return mHeaderViews.size();
}
public int getFootersCount() {
return mFootViews.size();
}
}
基本思路就是利用装饰者模式,不去动内部adapter
,利用wrapper
来包裹,直接加入headerView
和footerView
,利用viewtype
来与普通的item
对象作出区分。
其中的mHeaderViews
和mFootViews
都是SparseArrayCompat<View>
,SparseArrayCompat
是近期Android
推荐的类似于hashmap
的一种存储结构,特定服务于Android
,更为高效。
而mHeaderViews
和mFootViews
里的view是如何进行标识的呢,主要是根据初始定义的BASE_ITEM_TYPE_HEADER
和BASE_ITEM_TYPE_FOOTER
,两个的初始量设置的比较大,不会出现于普通视图的冲突问题。然后根据加入view时,mFootViews的元素数量和基础量相加得到每个view的位置,整体的功能就能实现了
再来看看其中的BaseViewHolder是这样的
public class BaseViewHolder extends RecyclerView.ViewHolder {
protected final SparseArray<View> mViews;
protected View mConvertView;
private Context mContext;
public BaseViewHolder(Context context, View itemView) {
super(itemView);
mContext = context;
mViews = new SparseArray<>();
mConvertView = itemView;
}
public static BaseViewHolder createViewHolder(Context context, View itemView) {
BaseViewHolder holder = new BaseViewHolder(context, itemView);
return holder;
}
public static BaseViewHolder createViewHolder(Context context,
ViewGroup parent, int layoutId) {
View itemView = LayoutInflater.from(context).inflate(layoutId, parent,
false);
BaseViewHolder holder = new BaseViewHolder(context, itemView);
return holder;
}
/**
* 通过资源Id获取对应控件,如果没有,则加入views
*
* @param viewId 资源Id
* @param <T> view
* @return
*/
public <T extends View> T getView(int viewId) {
View view = mViews.get(viewId);
if (view == null) {
view = mConvertView.findViewById(viewId);
mViews.put(viewId, view);
}
return (T) view;
}
/**
* 实现通用的属性设置方法
*
* @param viewId 控件Id
* @param value string value
* @return
*/
public BaseViewHolder setText(int viewId, String value) {
TextView view = getView(viewId);
view.setText(value);
return BaseViewHolder.this;
}
}
还有WrapperUtils处理的是不同layoutManager的适配问题
public class WrapperUtils
{
public interface SpanSizeCallback
{
int getSpanSize(GridLayoutManager layoutManager , GridLayoutManager.SpanSizeLookup oldLookup, int position);
}
public static void onAttachedToRecyclerView(RecyclerView.Adapter innerAdapter, RecyclerView recyclerView, final SpanSizeCallback callback)
{
innerAdapter.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)
{
return callback.getSpanSize(gridLayoutManager, spanSizeLookup, position);
}
});
gridLayoutManager.setSpanCount(gridLayoutManager.getSpanCount());
}
}
public static void setFullSpan(RecyclerView.ViewHolder holder)
{
ViewGroup.LayoutParams lp = holder.itemView.getLayoutParams();
if (lp != null
&& lp instanceof StaggeredGridLayoutManager.LayoutParams)
{
StaggeredGridLayoutManager.LayoutParams p = (StaggeredGridLayoutManager.LayoutParams) lp;
p.setFullSpan(true);
}
}
}
最后在activity中就可以完整使用了
写完发现用的还是鸿洋的baseAdapter系列东西,好吧,原创部分是自己的实现
源码还在更新,更新好了再上传