RecyclerView添加headerview和footview

来源:互联网 发布:百度洗地 知乎 编辑:程序博客网 时间:2024/05/03 23:57

随着使用的普及,RecyclerView基本取代了listViewgridView等控件。

在日常开发中,有时候会使用到headerviewfootview,但是RecyclerView的API并没有提供类似于listviewaddfootview()等放法,这种情况应该怎么处理呢?

普遍的解决方案就是通过指定adapteritemType来区分将其与内容区域区分开。

本篇文章参考了其他博主的文章,并将一些细节部分进行了完善
鸿洋的博客:
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来包裹,直接加入headerViewfooterView,利用viewtype来与普通的item对象作出区分。

其中的mHeaderViewsmFootViews都是SparseArrayCompat<View>SparseArrayCompat是近期Android推荐的类似于hashmap的一种存储结构,特定服务于Android,更为高效。

mHeaderViewsmFootViews里的view是如何进行标识的呢,主要是根据初始定义的BASE_ITEM_TYPE_HEADERBASE_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系列东西,好吧,原创部分是自己的实现

源码还在更新,更新好了再上传

0 0