裝飾設(shè)計(jì)模式(二) - RecyclerView添加頭部和底部

前言

裝飾設(shè)計(jì)模式系列文章
裝飾設(shè)計(jì)模式(一) - 吃飯小示例
裝飾設(shè)計(jì)模式(二) - RecyclerView添加頭部和底部
裝飾設(shè)計(jì)模式(三) - 源碼中用到的裝飾設(shè)計(jì)模式

1. 概述


上一節(jié)我們講解了裝飾設(shè)計(jì)模式的定義潘悼、及如何去寫裝飾設(shè)計(jì)模式,并且也寫了一個(gè)小的實(shí)例 —— 吃飯小示例镇辉,那么這一節(jié)我們就采用裝飾設(shè)計(jì)模式來分析下 給RecyclerView添加頭部和底部

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


其實(shí) RecyclerView是不支持頭部和底部的View對(duì)象的添加湿酸,但是ListView是支持的早抠。我們必須要想辦法解決這個(gè)問題到踏,當(dāng)然網(wǎng)上的解決辦法有很多遂填,但是我個(gè)人所贊同的就是采用裝飾設(shè)計(jì)模式 添加頭部和底部,那么接下來我們就首先來看下ListView它為什么可以添加頭部和底部逼蒙;

3. ListView為什么可以添加頭部和底部从绘?


  • ListView源碼中的 addHeaderView()方法,里邊有 HeaderViewListAdapter 這個(gè)類是牢,這個(gè)類是ListView源碼給我們寫好的僵井,可以添加頭部和底部;
  • 而RecyclerView是沒有寫這個(gè)類驳棱,所以 RecyclerView不支持手動(dòng)添加 頭部和底部批什,但是我們完全可以參照 ListView中的這個(gè)類的寫法,來 實(shí)現(xiàn) RecyclerView添加 頭部和底部社搅;
  • HeaderViewListAdapter這個(gè)類也是采用的是 裝飾設(shè)計(jì)模式驻债,讓 ListAdapter 變得更加強(qiáng)大;
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){
        // 必須是:在設(shè)置 setAdapter之后合呐,才能夠去設(shè)置頭部和底部
        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>:裝飾設(shè)計(jì)模式的 RecyclerView.Adapter

/**
 * Email: 2185134304@qq.com
 * Created by Novate 2018/5/12 17:25
 * Version 1.0
 * Params:
 * Description:    裝飾設(shè)計(jì)模式的 RecyclerView.Adapter - 我們對(duì)其進(jìn)行功能擴(kuò)展,使其能夠支持頭部和底部的添加
*/

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中點(diǎn)擊 item條目 調(diào)用了 notifyDataSetChanged淌实,
        // 這里就會(huì)收到通知,然后在這里也調(diào)用 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) {
        // 現(xiàn)在問題就是:如果想知道是哪個(gè)部分拆祈,就必須知道 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);
            }
        }
    }

    /**
     * 總共返回有多少條 = 頭部條數(shù) + 真實(shí)的Adapter條數(shù) + 底部條數(shù)
     */
    @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中調(diào)用即可:

/**
 * 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凛剥,每次都必須 創(chuàng)建RecyclerView,然后寫裝飾設(shè)計(jì)模式WrapRecyclerAdapter
        // 把 RecyclerAdapter 包裝到 WrapRecyclerAdapter中轻姿,然后setAdapter時(shí)候 設(shè)置 WrapRecyclerAdapter
        // 比較麻煩 犁珠,下邊 把RecyclerView封裝成WrapRecyclerView
        /*
        mRecyclerView = (RecyclerView) findViewById(R.id.recycler_view) ;
        // RecyclerView必須設(shè)置布局管理,否則沒有數(shù)據(jù)
        mRecyclerView.setLayoutManager(new LinearLayoutManager(this));
        *//*mRecyclerView.setAdapter(new RecyclerAdapter());*//*

        // 采用裝飾設(shè)計(jì)模式互亮,讓其支持添加頭部和底部
        RecyclerAdapter recyclerAdapter = new RecyclerAdapter() ;
        WrapRecyclerAdapter wrapRecyclerAdapter = new WrapRecyclerAdapter(recyclerAdapter) ;
        mRecyclerView.setAdapter(wrapRecyclerAdapter);
        // 添加頭部 下邊這兩種效果是一樣的 犁享,但是這種寫法有問題,就是頭部只有一小部分豹休,并沒有充滿屏幕
        // 原因就是:父布局容器為null炊昆,才導(dǎo)致沒有充滿屏幕的,只需要添加一個(gè)父布局容器威根,把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凤巨,在這里移除當(dāng)前item,調(diào)用notifyDataSetChanged
            // 然后在WrapRecyclerAdapter的構(gòu)造方法中 注冊(cè)觀察者registerAdapterDataObserver洛搀,
            // 再去調(diào)用調(diào)用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) ;
            }
        }
    }
}
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末敢茁,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子留美,更是在濱河造成了極大的恐慌彰檬,老刑警劉巖,帶你破解...
    沈念sama閱讀 211,290評(píng)論 6 491
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件谎砾,死亡現(xiàn)場離奇詭異逢倍,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)景图,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,107評(píng)論 2 385
  • 文/潘曉璐 我一進(jìn)店門较雕,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人挚币,你說我怎么就攤上這事亮蒋。” “怎么了忘晤?”我有些...
    開封第一講書人閱讀 156,872評(píng)論 0 347
  • 文/不壞的土叔 我叫張陵宛蚓,是天一觀的道長。 經(jīng)常有香客問我设塔,道長凄吏,這世上最難降的妖魔是什么远舅? 我笑而不...
    開封第一講書人閱讀 56,415評(píng)論 1 283
  • 正文 為了忘掉前任,我火速辦了婚禮痕钢,結(jié)果婚禮上图柏,老公的妹妹穿的比我還像新娘。我一直安慰自己任连,他們只是感情好蚤吹,可當(dāng)我...
    茶點(diǎn)故事閱讀 65,453評(píng)論 6 385
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著随抠,像睡著了一般裁着。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上拱她,一...
    開封第一講書人閱讀 49,784評(píng)論 1 290
  • 那天二驰,我揣著相機(jī)與錄音,去河邊找鬼秉沼。 笑死桶雀,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的唬复。 我是一名探鬼主播矗积,決...
    沈念sama閱讀 38,927評(píng)論 3 406
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢(mèng)啊……” “哼敞咧!你這毒婦竟也來了棘捣?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 37,691評(píng)論 0 266
  • 序言:老撾萬榮一對(duì)情侶失蹤妄均,失蹤者是張志新(化名)和其女友劉穎柱锹,沒想到半個(gè)月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體丰包,經(jīng)...
    沈念sama閱讀 44,137評(píng)論 1 303
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,472評(píng)論 2 326
  • 正文 我和宋清朗相戀三年壤巷,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了邑彪。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 38,622評(píng)論 1 340
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡胧华,死狀恐怖寄症,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情矩动,我是刑警寧澤有巧,帶...
    沈念sama閱讀 34,289評(píng)論 4 329
  • 正文 年R本政府宣布,位于F島的核電站悲没,受9級(jí)特大地震影響篮迎,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,887評(píng)論 3 312
  • 文/蒙蒙 一甜橱、第九天 我趴在偏房一處隱蔽的房頂上張望逊笆。 院中可真熱鬧,春花似錦岂傲、人聲如沸难裆。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,741評(píng)論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽乃戈。三九已至,卻和暖如春亩进,著一層夾襖步出監(jiān)牢的瞬間症虑,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 31,977評(píng)論 1 265
  • 我被黑心中介騙來泰國打工镐侯, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留侦讨,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 46,316評(píng)論 2 360
  • 正文 我出身青樓苟翻,卻偏偏與公主長得像韵卤,于是被迫代替她去往敵國和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子崇猫,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 43,490評(píng)論 2 348

推薦閱讀更多精彩內(nèi)容