装饰设计模式(二) - 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) ;
            }
        }
    }
}
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 158,425评论 4 361
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 67,058评论 1 291
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 108,186评论 0 243
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 43,848评论 0 204
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,249评论 3 286
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,554评论 1 216
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,830评论 2 312
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,536评论 0 197
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,239评论 1 241
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,505评论 2 244
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 32,004评论 1 258
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,346评论 2 253
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 32,999评论 3 235
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,060评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,821评论 0 194
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,574评论 2 271
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,480评论 2 267

推荐阅读更多精彩内容