装饰设计模式(二) - RecyclerView添加头部和底部

前言

装饰设计模式系列文章
装饰设计模式(一) - 吃饭小示例
装饰设计模式(二) - RecyclerView添加头部和底部
装饰设计模式(三) - 源码中用到的装饰设计模式

1. 概述


上一节我们讲解了装饰设计模式的定义、及如何去写装饰设计模式,并且也写了一个小的实例 —— 吃饭小示例,那么这一节我们就采用装饰设计模式来分析下 给RecyclerView添加头部和底部

2. RecyclerView 不支持添加头部和底部


其实 RecyclerView是不支持头部和底部的View对象的添加,但是ListView是支持的。我们必须要想办法解决这个问题,当然网上的解决办法有很多,但是我个人所赞同的就是采用装饰设计模式 添加头部和底部,那么接下来我们就首先来看下ListView它为什么可以添加头部和底部;

3. ListView为什么可以添加头部和底部?


  • ListView源码中的 addHeaderView()方法,里边有 HeaderViewListAdapter 这个类,这个类是ListView源码给我们写好的,可以添加头部和底部;
  • 而RecyclerView是没有写这个类,所以 RecyclerView不支持手动添加 头部和底部,但是我们完全可以参照 ListView中的这个类的写法,来 实现 RecyclerView添加 头部和底部;
  • HeaderViewListAdapter这个类也是采用的是 装饰设计模式,让 ListAdapter 变得更加强大;
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;

        // Wrap the adapter if it wasn't already wrapped.
        if (mAdapter != null) {
            if (!(mAdapter instanceof HeaderViewListAdapter)) {
                wrapHeaderListAdapterInternal();
            }
        }
    }

4. 具体代码如下


1>:封装 WrapRecyclerView 替代 原始的 RecyclerView

/**
 * Email: 2185134304@qq.com
 * Created by Novate 2018/5/12 18:47
 * Version 1.0
 * Params:
 * Description:    封装的 WrapRecyclerView,替代 原始的 RecyclerView
*/

public class WrapRecyclerView extends RecyclerView {

    private WrapRecyclerAdapter mAdapter ;

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

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

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


    @Override
    public void setAdapter(Adapter adapter) {
        mAdapter = new WrapRecyclerAdapter(adapter) ;
        super.setAdapter(adapter);
    }

    /**
     * 添加头部
     */
    public void addHeaderView(View view){
        // 必须是:在设置 setAdapter之后,才能够去设置头部和底部
        if (mAdapter != null){
            mAdapter.addHeaderView(view);
        }
    }


    /**
     * 添加底部
     */
    public void addFooterView(View view){
        if (mAdapter != null){
            mAdapter.addFooterView(view);
        }
    }


    /**
     * 移除头部
     */
    public void removeHeaderView(View view){
        if (mAdapter != null){
            mAdapter.removeHeaderView(view);
        }
    }


    /**
     * 移除底部
     */
    public void removeFooterView(View view){
        if (mAdapter != null){
            mAdapter.removeFooterView(view);
        }
    }
}

2>:装饰设计模式的 RecyclerView.Adapter

/**
 * Email: 2185134304@qq.com
 * Created by Novate 2018/5/12 17:25
 * Version 1.0
 * Params:
 * Description:    装饰设计模式的 RecyclerView.Adapter - 我们对其进行功能扩展,使其能够支持头部和底部的添加
*/

public class WrapRecyclerAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolder>{

    // 原来的 RecyclerView.Adapter,并不支持头部和底部的添加
    private final RecyclerView.Adapter mRealAdapter ;

    ArrayList<View> mHeaderViews;
    ArrayList<View> mFooterViews;

    public WrapRecyclerAdapter(RecyclerView.Adapter adapter){
        this.mRealAdapter = adapter ;


        // 只要MainActivity中点击 item条目 调用了 notifyDataSetChanged,
        // 这里就会收到通知,然后在这里也调用 notifyDataSetChanged方法,就能够删除item了
        mRealAdapter.registerAdapterDataObserver(new RecyclerView.AdapterDataObserver() {
            @Override
            public void onChanged() {
                notifyDataSetChanged();
            }
        });

        mHeaderViews = new ArrayList() ;
        mFooterViews = new ArrayList<>() ;
    }

    @Override
    public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int position) {
        // 现在问题就是:如果想知道是哪个部分,就必须知道 position,也就是位置,但是目前只有 viewType

        // 头部返回 头部的ViewHolder
        int numHeaders = getHeadersCount();
        if (position < numHeaders) {
            return createHeaderFooterViewHolder(mHeaderViews.get(position));
        }
        // mRealAdapter返回 mRealAdapter的ViewHolder
        final int adjPosition = position - numHeaders;
        int adapterCount = 0;
        if (mRealAdapter != null) {
            adapterCount = mRealAdapter.getItemCount();
            if (adjPosition < adapterCount) {
                // 直接传 position ,不兼容 万能适配多布局条目
                return mRealAdapter.onCreateViewHolder(parent,mRealAdapter.getItemViewType(adjPosition));
            }
        }
        // 底部返回 底部的ViewHolder
        return createHeaderFooterViewHolder(mFooterViews.get(adjPosition - adapterCount));
    }


    @Override
    public int getItemViewType(int position) {
        // 把 位置postion 作为 viewType
        return position;
    }

    public int getHeadersCount() {
        return mHeaderViews.size();
    }

    public int getFootersCount() {
        return mFooterViews.size();
    }


    private RecyclerView.ViewHolder createHeaderFooterViewHolder(View view) {
        return new RecyclerView.ViewHolder(view){};
    }


    @Override
    public void onBindViewHolder(RecyclerView.ViewHolder holder, int position) {
        // 头部和底部是不需要处理的,只有中间的 mRealAdapter需要处理
        int numHeaders = getHeadersCount();
        if (position < numHeaders) {
            return ;
        }

        final int adjPosition = position - numHeaders;
        int adapterCount = 0;
        if (mRealAdapter != null) {
            adapterCount = mRealAdapter.getItemCount();
            if (adjPosition < adapterCount) {
                mRealAdapter.onBindViewHolder(holder,position);
            }
        }
    }

    /**
     * 总共返回有多少条 = 头部条数 + 真实的Adapter条数 + 底部条数
     */
    @Override
    public int getItemCount() {
        return mHeaderViews.size() + mRealAdapter.getItemCount() + mFooterViews.size();
    }


    /**
     * 添加头部
     */
    public void addHeaderView(View view){
        if (!mHeaderViews.contains(view)){
            mHeaderViews.add(view) ;
            notifyDataSetChanged();
        }
    }


    /**
     * 添加底部
     */
    public void addFooterView(View view){
        if (!mFooterViews.contains(view)){
            mFooterViews.add(view) ;
            notifyDataSetChanged();
        }
    }


    /**
     * 移除头部
     */
    public void removeHeaderView(View view){
        if(mHeaderViews.contains(view)){
            mHeaderViews.remove(view) ;
            notifyDataSetChanged();
        }
    }


    /**
     * 移除底部
     */
    public void removeFooterView(View view){
        if (mFooterViews.contains(view)){
            mFooterViews.remove(view) ;
            notifyDataSetChanged();
        }
    }
}

3>:最后在 MainActivity中调用即可:

/**
 * Email: 2185134304@qq.com
 * Created by Novate 2018/5/12 17:06
 * Version 1.0
 * Params:
 * Description:
*/

public class MainActivity extends AppCompatActivity {

    private RecyclerView mRecyclerView ;
    private List<Integer> mItems;
    private WrapRecyclerView recycler_view;

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

        mItems = new ArrayList<>();
        for (int i=0;i<100;i++){
            mItems.add(i);
        }

        // 使用原始 RecyclerView,每次都必须 创建RecyclerView,然后写装饰设计模式WrapRecyclerAdapter
        // 把 RecyclerAdapter 包装到 WrapRecyclerAdapter中,然后setAdapter时候 设置 WrapRecyclerAdapter
        // 比较麻烦 ,下边 把RecyclerView封装成WrapRecyclerView
        /*
        mRecyclerView = (RecyclerView) findViewById(R.id.recycler_view) ;
        // RecyclerView必须设置布局管理,否则没有数据
        mRecyclerView.setLayoutManager(new LinearLayoutManager(this));
        *//*mRecyclerView.setAdapter(new RecyclerAdapter());*//*

        // 采用装饰设计模式,让其支持添加头部和底部
        RecyclerAdapter recyclerAdapter = new RecyclerAdapter() ;
        WrapRecyclerAdapter wrapRecyclerAdapter = new WrapRecyclerAdapter(recyclerAdapter) ;
        mRecyclerView.setAdapter(wrapRecyclerAdapter);
        // 添加头部 下边这两种效果是一样的 ,但是这种写法有问题,就是头部只有一小部分,并没有充满屏幕
        // 原因就是:父布局容器为null,才导致没有充满屏幕的,只需要添加一个父布局容器,把null修改为mRecyclerView就可以了
//        View headView = LayoutInflater.from(this).inflate(R.layout.layout_header_view , null) ;
//        View headView = LayoutInflater.from(this).inflate(R.layout.layout_header_view , null , false) ;
        View headView = LayoutInflater.from(this).inflate(R.layout.layout_header_view , mRecyclerView , false) ;
        wrapRecyclerAdapter.addHeaderView(headView);*/


        //  把RecyclerView封装成WrapRecyclerView
        recycler_view = (WrapRecyclerView) findViewById(R.id.recycler_view);
        recycler_view.setLayoutManager(new LinearLayoutManager(this));
        recycler_view.setAdapter(new RecyclerAdapter());
        View headView = LayoutInflater.from(this).inflate(R.layout.layout_header_view , recycler_view , false) ;
        recycler_view.addHeaderView(headView);

    }


    private class RecyclerAdapter extends RecyclerView.Adapter<RecyclerAdapter.ViewHolder>{

        @Override
        public ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
            View itemView = LayoutInflater.from(MainActivity.this).inflate(R.layout.item_rv , parent , false) ;
            return new ViewHolder(itemView);
        }

        @Override
        public void onBindViewHolder(ViewHolder holder, final int position) {
            holder.text.setText("position = "+mItems.get(position));

            // 这里是删除RecyclerView 的 item,在这里移除当前item,调用notifyDataSetChanged
            // 然后在WrapRecyclerAdapter的构造方法中 注册观察者registerAdapterDataObserver,
            // 再去调用调用notifyDataSetChanged就可以删除item了
            holder.text.setOnClickListener(new View.OnClickListener() {
                @Override
                public void onClick(View v) {
                    mItems.remove(position) ;
                    notifyDataSetChanged();
                }
            });
        }

        @Override
        public int getItemCount() {
            return mItems.size();
        }

        class ViewHolder extends RecyclerView.ViewHolder{

            public TextView text ;
            public ViewHolder(View itemView) {
                super(itemView);
                text = (TextView) itemView.findViewById(R.id.text) ;
            }
        }
    }
}

推荐阅读更多精彩内容