RecyclerView封装--添加HeaderView和FooterView

1 前言

之前已经介绍过,将RecyclerView做了一个最基本的简单封装,包括Adapter的封装,和支持多种item布局等,今天会来介绍如何高效的添加头部和底部,下一篇会介绍,如何添加下拉刷新和上拉加载更多等。

最后完成的效果如下:
RecyclerView封装--添加HeaderView和FooterView_第1张图片RecyclerView封装--添加HeaderView和FooterView_第2张图片

添加头部和底部其实是一种装饰器设计模式,那么我们先来看看什么是装饰器设计模式

2 装饰器设计模式

装饰者模式(Decorator [‘dekəreitə] Pattren),是在不改变原类文件和使用继承的情况下,动态地扩展一个对象的功能,它是通过创建一个包装对象,也就是装饰来包裹真实的对象。
这里写图片描述

Component:组件对象的接口,可以给这些对象动态的添加职责;
ConcreteComponent:具体的组件对象,实现了组件接口。该对象通常就是被装饰器装饰的原始对象,可以给这个对象添加职责;

Decorator:所有装饰器的父类,需要定义一个与组件接口一致的接口(主要是为了实现装饰器功能的复用,即具体的装饰器A可以装饰另外一个具体的装饰器B,因为装饰器类也是一个Component),并持有一个Component对象,该对象其实就是被装饰的对象。如果不继承组件接口类,则只能为某个组件添加单一的功能,即装饰器对象不能在装饰其他的装饰器对象。

ConcreteDecorator:具体的装饰器类,实现具体要向被装饰对象添加的功能。用来装饰具体的组件对象或者另外一个具体的装饰器对象。

对于RecyclerView来说,我们是改变其Adapter而已,对Adapter使用对象装饰器模式,即在原来的Adapter中扩展,为其添加HeaderView 和FooterView

3 添加HeaderView 和FooterView

为RecycerView 添加HeaderView 和FooterView 思路如下:

1 继承已包装好的Adapter:BaseRecyclerAdapter
2 用两个SparseArray来保存HeaderView和FooterView
3 重写BaseRecyclerAdapter的onCreateViewHolder,onBindViewHolder,getItemViewType等方法,在对应position处返回Header或者Footer的type
4 提供外部添加HeaderView和FooterView以及删除的接口
5 RecyclerView 中做响应的修改,以适应现在的Adapter

废话不多说,先上Adapter的代码:

/**
 * Email: 1273482124@qq.com
 * Created by qiyei2015 on 2017/5/20.
 * Version: 1.0
 * Description: 利用装饰器设计模式来构造添加头部和底部的RecyclerAdapter
 */
public abstract class XRecyclerAdapter<T> extends BaseRecyclerAdapter<T> {

    /**
     * 头部的View
     */
    private SparseArray mHeaderViews;
    /**
     * 底部View
     */
    private SparseArray mFooterViews;
    /**
     * 头部的View索引 
     */
    private int BASE_TYPE_HEADER = 1000000;
    /**
     * 底部的View索引 
     */
    private int BASE_TYPE_FOOTER = 2000000;
    /**
     * 不包含头部和底部的adapter
     */
    private BaseRecyclerAdapter mAdapter;

    public XRecyclerAdapter(Context context, List datas, int layoutId) {
        super(context, datas, layoutId);
        init();
    }

    public XRecyclerAdapter(Context context, List datas, IMultiTypeLayout typeLayout) {
        super(context, datas, typeLayout);
        init();
    }

    public XRecyclerAdapter(BaseRecyclerAdapter adapter) {
        mAdapter = adapter;
        init();
    }

    @Override
    public BaseViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
        if (isHeaderView(viewType)) {
            return onCreateHeaderViewHolder(parent, viewType);
        }

        if (isFooterView(viewType)){
            return onCreateFooterViewHolder(parent, viewType);
        }

        return super.onCreateViewHolder(parent, viewType);
    }

    @Override
    public void onBindViewHolder(BaseViewHolder holder, int position) {
        if (isHeaderPosition(position) || isFooterPosition(position)){
            return;
        }
        super.onBindViewHolder(holder, position - mHeaderViews.size());
    }

    @Override
    public int getItemViewType(int position) {
        if (isHeaderPosition(position)){
            return mHeaderViews.keyAt(position);
        }

        if (isFooterPosition(position)){
            return mFooterViews.keyAt(position - mHeaderViews.size() - super.getItemCount());
        }

        return super.getItemViewType(position - mHeaderViews.size());
    }

    @Override
    public int getItemCount() {
        return super.getItemCount() + mHeaderViews.size() + mFooterViews.size();
    }

    /**
     * 添加HeaderView
     * @param view
     */
    public void addHeaderView(View view){
        if (view == null){
            return;
        }

        int position = mHeaderViews.indexOfValue(view);
        Log.d(TAG,"addHeaderView,position:" + position + ",mHeaderViews.size():" + mHeaderViews.size());
        if (position >= 0){
            return;
        }
        mHeaderViews.put(BASE_TYPE_HEADER++,view);
        notifyDataSetChanged();
    }

    /**
     * 添加FooterView
     * @param view
     */
    public void addFooterView(View view){
        int position = mFooterViews.indexOfValue(view);
        if (position < 0){
            mFooterViews.put(BASE_TYPE_FOOTER++,view);
        }
        notifyDataSetChanged();
    }

    /**
     * 移除HeaderView
     * @param view
     */
    public void removeHeaderView(View view){
        int pos = mHeaderViews.indexOfValue(view);
        if (pos< 0){
            return;
        }
        mHeaderViews.removeAt(pos);
        notifyDataSetChanged();
    }

    /**
     * 移除FooterView
     * @param view
     */
    public void removeFooterView(View view){
        int pos = mFooterViews.indexOfValue(view);
        if (pos< 0){
            return;
        }
        mFooterViews.removeAt(pos);
        notifyDataSetChanged();
    }

    /**
     * 解决GridLayoutManager添加头部和底部不占用一行的问题
      * @param recycler
     */
    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;
                }
            });
        }
    }

    /**
     * 初始化
     */
    private void init(){
        mHeaderViews = new SparseArray<>();
        mFooterViews = new SparseArray<>();
    }

    /**
     * 判断是否是HeaderView
     * @param viewType
     * @return
     */
    private boolean isHeaderView(int viewType){
        int index = mHeaderViews.indexOfKey(viewType);
        return index >= 0;
    }

    /**
     * 判断是否是HeaderView
     * @param viewType
     * @return
     */
    private boolean isFooterView(int viewType){
        int index = mFooterViews.indexOfKey(viewType);
        return index >= 0;
    }

    /**
     * 创建HeaderViewHolder
     * @param parent
     * @param viewType
     * @return
     */
    private BaseViewHolder onCreateHeaderViewHolder(ViewGroup parent, int viewType){
        View view = mHeaderViews.get(viewType);
        return new BaseViewHolder(view);
    }

    /**
     * 创建FooterViewHolder
     * @param parent
     * @param viewType
     * @return
     */
    private BaseViewHolder onCreateFooterViewHolder(ViewGroup parent, int viewType){
        View view = mFooterViews.get(viewType);
        return new BaseViewHolder(view);
    }

    /**
     * 判断此position是否是Header
     * @param position
     * @return
     */
    private boolean isHeaderPosition(int position){
        return position < mHeaderViews.size();
    }

    /**
     * 判断此处是否是Footer
     * @param position
     * @return
     */
    private boolean isFooterPosition(int position){
        return position >= mHeaderViews.size() + super.getItemCount();
    }

}

上述代码有几个关键点:
1 在int getItemViewType(int position)中,先判断是否是HeaderView或者FooterView,如果是,我们这里直接返回的是HeaderView或者FooterView在SparseArray中的key,为什么这样,请见下面

2 在BaseViewHolder onCreateViewHolder(ViewGroup parent, int viewType)中我们需要根据具体的viewType创建不同的ViewHolder,关键的来了,由于我们刚刚在position处返回的viewType是SparseArray中的key,那么这里的viewType也就是我们SparseArray的key,我们自然就可以获取到对应个的view啦,所以是不是一个很巧妙的设计呢?省去了单独建一个map保存type与View的对应关系。

3 onBindViewHolder(BaseViewHolder holder, int position)中需要判断是否是HeaderView与FooterView,如果是就不绑定,这是因为HeaderView与FooterView不和数据发生关系。

RecyclerView 中的代码需要做如下修改:

/**
 * Email: [email protected]
 * Created by qiyei2015 on 2017/5/20.
 * Version: 1.0
 * Description: 自定义扩展的Recycler,支持添加HeaderView,FooterView,上拉加载更多,下拉刷新,以及侧滑删除等
 */
public class WrapRecyclerView extends RecyclerView {
    /**
     * 调试用的标志
     */
    private final static String TAG = WrapRecyclerView.class.getSimpleName();
    /**
     * 未有头与底的adapter
     */
    private BaseRecyclerAdapter mAdapter;
    /**
     * 封装了头部与底部的Adapter
     */
    private XRecyclerAdapter mWarpAdapter;

    /**
     * 数据为空的显示页
     */
    private View mEmptyView;
    /**
     * 加载页
     */
    private View mLoadingView;

    /**
     * 定义数据观察者
     */
    private AdapterDataObserver mDataObserver = new AdapterDataObserver() {
        @Override
        public void onChanged() {
            checkAdapter(mAdapter);
            if (mWarpAdapter != mAdapter){
                mWarpAdapter.notifyDataSetChanged();
            }
            dataChanged();
        }

        @Override
        public void onItemRangeMoved(int fromPosition, int toPosition, int itemCount) {
            checkAdapter(mAdapter);
            if (mWarpAdapter != mAdapter){
                mWarpAdapter.notifyItemMoved(fromPosition,toPosition);
            }
            dataChanged();
        }

        @Override
        public void onItemRangeChanged(int positionStart, int itemCount) {
            checkAdapter(mAdapter);
            if (mWarpAdapter != mAdapter){
                mWarpAdapter.notifyItemRangeChanged(positionStart,itemCount);
            }
            dataChanged();
        }

        @Override
        public void onItemRangeChanged(int positionStart, int itemCount, Object payload) {
            checkAdapter(mAdapter);
            if (mWarpAdapter != mAdapter){
                mWarpAdapter.notifyItemRangeChanged(positionStart,itemCount,payload);
            }
            dataChanged();
        }

        @Override
        public void onItemRangeInserted(int positionStart, int itemCount) {
            checkAdapter(mAdapter);
            if (mWarpAdapter != mAdapter){
                mWarpAdapter.notifyItemRangeInserted(positionStart,itemCount);
            }
            dataChanged();
        }

        @Override
        public void onItemRangeRemoved(int positionStart, int itemCount) {
            checkAdapter(mAdapter);
            if (mWarpAdapter != mAdapter){
                mWarpAdapter.notifyItemRangeRemoved(positionStart,itemCount);
            }
            dataChanged();
        }
    };

    public WrapRecyclerView(Context context) {
        super(context);
    }

    public WrapRecyclerView(Context context, @Nullable AttributeSet attrs, int defStyle) {
        super(context, attrs, defStyle);
    }

    public WrapRecyclerView(Context context, @Nullable AttributeSet attrs) {
        super(context, attrs);
    }

    /**
     * @param adapter the {@link #mAdapter} to set
     */
    public void setAdapter(Adapter adapter) {
        if (mAdapter != null){
            mAdapter.unregisterAdapterDataObserver(mDataObserver);
            mAdapter = null;
        }

        mAdapter = (BaseRecyclerAdapter) adapter;

        if (adapter instanceof XRecyclerAdapter){
            mWarpAdapter = (XRecyclerAdapter) adapter;
        }else {
            mWarpAdapter = new XRecyclerAdapter(mAdapter){
                @Override
                public void convert(BaseViewHolder holder, Object data, int position) {

                }
            };
        }

        super.setAdapter(mWarpAdapter);

        mAdapter.registerAdapterDataObserver(mDataObserver);
        //调整GridLayoutManager的添加头部和底部不占一行的问题
        mWarpAdapter.adjustSpanSize(this);
    }

    /**
     * 添加HeaderView
     * @param view
     */
    public void addHeaderView(View view){
        checkAdapter(mWarpAdapter);
        mWarpAdapter.addHeaderView(view);
    }

    /**
     * 添加FooterView
     * @param view
     */
    public void addFooterView(View view){
        checkAdapter(mWarpAdapter);
        mWarpAdapter.addFooterView(view);
    }

    /**
     * 移除HeaderView
     * @param view
     */
    public void removeHeaderView(View view){
        checkAdapter(mWarpAdapter);
        mWarpAdapter.removeHeaderView(view);
    }

    /**
     * 添加HeaderView到指定位置
     * @param view
     */
    public void addRefreshView(View view){
        checkAdapter(mWarpAdapter);
        mWarpAdapter.addRefreshView(view);
    }

    /**
     * 添加FooterView
     * @param view
     */
    public void addLoadMoreView(View view){
        checkAdapter(mWarpAdapter);
        mWarpAdapter.addLoadMoreView(view);
    }

    /**
     * 移除FooterView
     * @param view
     */
    public void removeFooterView(View view){
        checkAdapter(mWarpAdapter);
        mWarpAdapter.removeFooterView(view);
    }

    /**
     * 添加空的view
     * @param view
     */
    public void setEmptyView(View view){
        this.mEmptyView = view;
    }

    /**
     * 设置加载页
     * @param view
     */
    public void setLoadingView(View view){
        this.mLoadingView = view;
    }

    /**
     * 监测Adapter
     * @param adapter
     */
    private void checkAdapter(Adapter adapter){
        if (adapter == null){
            throw new NullPointerException("adapter is null,please setAdapter() first");
        }
    }

    /**
     * 数据改变时需要判断是否为空
     */
    private void dataChanged() {
        if (mAdapter.getItemCount() == 0){
            if (mEmptyView != null){
                mEmptyView.setVisibility(VISIBLE);
            }else {
                mEmptyView.setVisibility(GONE);
            }
        }else {
            if (mEmptyView != null){
                mEmptyView.setVisibility(GONE);
            }
        }
    }
}

4 测试

测试代码如下:

public class RecyclerViewTestActivity extends AppCompatActivity {

    private WarpRecyclerView mRecyclerView;

    private List mDatas;


    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_recycler_view_test);

        mRecyclerView = (WarpRecyclerView ) findViewById(R.id.recycler_view);
        initData();

        initView();
    }

    private void initView() {
        mRecyclerView.setLayoutManager(new LinearLayoutManager(this));
        mRecyclerView.addItemDecoration(new CategoryItemDecoration(getDrawable(R.drawable.recyclerview_decoration)));
        //mRecyclerView.setAdapter(new CommonAdapter(this,R.layout.recyclerview_item));
        mRecyclerView.setXRecyclerListener(this);
        mRecyclerView.setAdapter(new XRecyclerAdapter(this,mDatas,R.layout.recyclerview_item) {
            @Override
            public void convert(BaseViewHolder holder, String data, int position) {

                holder.setText(R.id.tv,data)
                        .setTextColor(R.id.tv,Color.RED)
                        .setTextSize(R.id.tv,28);
            }
        });



        for (int i = 0;i < 5 ;i++){
            TextView header = new TextView(this);
            header.setText("头部 " + i);
            header.setTextSize(30);

            TextView footer = new TextView(this);
            footer.setText("底部 "+ i);
            footer.setTextSize(30);

            mRecyclerView.addHeaderView(header);
            mRecyclerView.addFooterView(footer);
        }

    }

    private void initData(){

        mDatas = new ArrayList<>();
        for (int i = 0;i < 50;i++){
            String s = new String("测试 " + i);
            mDatas.add(s);
        }
    }

}

效果如下图:
RecyclerView封装--添加HeaderView和FooterView_第3张图片

你可能感兴趣的:(android,应用开发)