RecyclerView從入門到入神—為RecyclerView添加HeaderView, FooterView和EmptyView(二)

cover.jpg

上一篇 初識RecyclerView婚瓜,是RecylerView的入門篇腐泻,主要講解了什么是RecylerView,RecylerView的優(yōu)勢以及三種布局管理器的區(qū)別毁兆,我們對RecyclerVIew有了初步了解

本篇是Recylerview的進(jìn)階篇主到,我將一步步帶領(lǐng)大家為RecylerView添加HeaderView, FooterView, EmptyView, 以及完成對GloriousRecyclerView的封裝茶行,使我們的開發(fā)更加便捷

為RecyclerView添加HeaderView


參考ListView

我們使用ListView的時候知道,要為ListView添加HeaderView是非常方便的登钥,我們只需要調(diào)用ListView的addHeaderView(View v)方法即可畔师。

于是,果斷翻看RecyclerView的源碼

RecyclerView_SourceCode_1.png
RecyclerView_SourceCode_2.png

遺憾的是牧牢,我們并沒有找到添加HeaderView的方法看锉,退而求其次

從LayoutManager入手

我們找到在RecyclerView的內(nèi)部靜態(tài)抽象類LayoutManager中有addView()方法

RecyclerView_SourceCode_3.png

上一篇我們已經(jīng)知道,RecyclerView已經(jīng)實現(xiàn)了三種布局管理器结执,這里度陆,我們就用最簡單的LinearLayoutManager來嘗試下添加HeaderView

mLayoutManager = new LinearLayoutManager(this, RecyclerView.VERTICAL, false);
mRecyclerView.setLayoutManager(mLayoutManager);
mLayoutManager.addView(LayoutInflater.from(this).inflate(R .layout.layout_header, null, false), 0);

不幸的是,在運行時爆出空指針異常

RecyclerView_Header_Error_1.png

查看RecyclerView 7075行献幔,發(fā)現(xiàn)holder為空

RecyclerView_SourceCode_4.png

而holder由7074行得來

final ViewHolder holder  = getChildViewHolderInt(child);

繼續(xù)查看getChildViewHolderInt(child)方法

RecyclerView_SourceCode_5.png

holder是由子View的LayoutParams得來

這個LayoutParams是RecyclerView內(nèi)部靜態(tài)內(nèi),里面包含了一個ViewHolder mViewHolder 成員變量

由于我們添加的HeaderView是普通的 View / ViewGroup 趾诗,所以并沒有什么ViewHolder, 于此蜡感,此路不通耶

從ViewGroup入手


本著不拋棄蹬蚁,不放棄的精神,讓我們來大開腦洞吧郑兴,由于RecyclerView是繼承自ViewGroup的犀斋,我們知道ViewGroup有addView(View child, int index)方法,那我們試試不妨

mLayoutManager = new LinearLayoutManager(this, orientation, false);
mRecyclerView.setLayoutManager(mLayoutManager);

View header = LayoutInflater.from(this).inflate(R .layout.layout_header, null, false);
mRecyclerView.addView(header, 0);

悲劇的是情连,在運行時依然爆出空指針異常

RecyclerView_Header_Error_2.png

繼續(xù)查看源碼(這里我就不貼了叽粹,有興趣的可以自己翻看),發(fā)現(xiàn)依然是缺少ViewHolder的緣故却舀,由此看來虫几,這條路也不通了

從Adapter入手

思路

屢次受挫,確實是有點動搖軍心挽拔,仿佛看不到希望辆脸,但是有句話叫做 Nerver say Nerver, 好吧,至少我們知道了一點螃诅,我們要想在RecylerView中添加任何子View啡氢,那么這個View必須要有ViewHolder

我們在上一篇中講到:RecyclerView的Adapter默認(rèn)要求使用ViewHolder ,嗯术裸,似乎我們找到了正道倘是。

我們創(chuàng)建RecyclerView的Adapter時,必須要實現(xiàn)三個方法

RecyclerView_SourceCode_6.png

其中

onCreateViewHolder(ViewGroup parent, int viewType)

第二個參數(shù)袭艺,int viewType搀崭,由名字我們猜測是Item的類型,如果沒錯的話匹表,那么我們的Header和正常的Item就是兩種類型了门坷,那么,這個類型是怎么得來的呢袍镀?

查看源碼默蚌,叫我們參考getItemViewType(int)方法:

public int getItemViewType(int position) {
    return 0;
}

源碼里,傳入一個position,默認(rèn)返回0

這里我們得到了啟發(fā)苇羡,我們在自己的Adapter里:

實現(xiàn)
  1. 首先定義:
private int ITEM_TYPE_NORMAL = 0;
private int ITEM_TYPE_HEADER = 1;
  1. getItemViewType()中绸吸,假如position傳入0,我們的返回值返回 ITEM_TYPE_HEADER设江,其他的position锦茁,我們返回 ITEM_TYPE_NORMAL,這樣就區(qū)分了viewType

  2. onCreateViewHolder()中叉存,我們根據(jù)不同的viewType返回不同的ViewHolder

  3. onBindViewHolder()中码俩,我們首先根據(jù)positon調(diào)用getItemViewType(int position)方法,得到不同的viewType歼捏,如果得到 ITEM_TYPE_HEADER 稿存,我們直接return,如果得到 ITEM_TYPE_NORMAL笨篷,那么,由于有Header的存在瓣履,我們在設(shè)置Item的數(shù)據(jù)時率翅,應(yīng)該把position -1

  4. getItemCount()中,由于我們多了HeaderView袖迎,所以要在真實數(shù)據(jù)個數(shù)中+1

效果展示
RecyclerView_With_Header.png

為RecyclerView添加EmptyView, FooterView


為RecyclerView添加FooterView冕臭,EmptyView 其實和添加HeaderView是類似的,這里就不多言了燕锥,直接把同時添加Header辜贵,F(xiàn)ooter和Empty View的源碼貼上

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

    private List<String> mDatas = new ArrayList<>();
    private Context mContext;
    private View mHeaderView;
    private View mFooterView;
    private View mEmptyView;

    private int ITEM_TYPE_NORMAL = 0;
    private int ITEM_TYPE_HEADER = 1;
    private int ITEM_TYPE_FOOTER = 2;
    private int ITEM_TYPE_EMPTY = 3;


    public DemoAdapter(Context context) {
        mContext = context;
    }

    public void setDatas(List<String> datas) {
        mDatas = datas;
        notifyDataSetChanged();
    }

    // 創(chuàng)建視圖
    @Override
    public RecyclerView.ViewHolder
    onCreateViewHolder(ViewGroup parent, int viewType) {
        if (viewType == ITEM_TYPE_HEADER) {
            return new ViewHolder(mHeaderView);
        } else if (viewType == ITEM_TYPE_EMPTY) {
            return new ViewHolder(mEmptyView);
        } else if (viewType == ITEM_TYPE_FOOTER) {
            return new ViewHolder(mFooterView);
        } else {
            View v = LayoutInflater.from(mContext)
                    .inflate(
                            R.layout.layout_recyclerview_item_view,
                            parent,
                            false);
            return new ViewHolder(v);
        }
    }

    @Override
    public int getItemViewType(int position) {
        if (null != mHeaderView && position == 0) {
            return ITEM_TYPE_HEADER;
        }
        if (null != mFooterView
                && position == getItemCount() - 1) {
            return ITEM_TYPE_FOOTER;
        }
        if (null != mEmptyView && mDatas.size() == 0){
            return ITEM_TYPE_EMPTY;
        }
        return ITEM_TYPE_NORMAL;

    }

    // 為Item綁定數(shù)據(jù)
    @Override
    public void onBindViewHolder
    (RecyclerView.ViewHolder holder, int position) {
        int type = getItemViewType(position);
        if (type == ITEM_TYPE_HEADER
                || type == ITEM_TYPE_FOOTER
                || type == ITEM_TYPE_EMPTY) {
            return;
        }
        int realPos = getRealItemPosition(position);
        ((DemoAdapter.ViewHolder) holder)
                .mTextView
                .setText(mDatas.get(realPos));
    }

    private int getRealItemPosition(int position) {
        if (null != mHeaderView) {
            return position - 1;
        }
        return position;
    }

    @Override
    public int getItemCount() {
        int itemCount = mDatas.size();
        if (null != mEmptyView && itemCount == 0) {
            itemCount++;
        }
        if (null != mHeaderView) {
            itemCount++;
        }
        if (null != mFooterView) {
            itemCount++;
        }
        return itemCount;
    }

    public void addHeaderView(View view) {
        mHeaderView = view;
        notifyItemInserted(0);
    }

    public void addFooterView(View view) {
        mFooterView = view;
        notifyItemInserted(getItemCount() - 1);
    }

    public void setEmptyView(View view) {
        mEmptyView = view;
        notifyDataSetChanged();
    }

    class ViewHolder extends RecyclerView.ViewHolder {
        TextView mTextView;

        ViewHolder(View v) {
            super(v);
            mTextView = (TextView)
                    v.findViewById(R.id.item_title);
        }
    }
}

Activity中

View footer = LayoutInflater.from(this).inflate(R.layout.layout_footer, mRecyclerView, false);
View header = LayoutInflater.from(this).inflate(R.layout.layout_header, mRecyclerView, false);
View empty = LayoutInflater.from(this).inflate(R.layout.layout_empty, mRecyclerView, false);

adapter.addHeaderView(header);
adapter.addFooterView(footer);
adapter.setEmptyView(empty);

下圖展示的是同時添加了Header和Footer View

RecyclerView_With_Header_Footer.png

下圖展示的是Empty View

RecyclerView_With_Empty.png

封裝


其實上面的代碼在一般情況對添加HeaderView ,FooterView ,Empty View已經(jīng)夠用了,不過麻煩的是脯宿,我們在不同的地方念颈,需要重復(fù)Copy一些代碼,顯然连霉,這是不能容忍的

那么榴芳,我們能不能像ListView那樣 把什么addHeaderView(),addFooterView(),setEmptyView()直接封裝在RecyclerView里呢?答案是肯定的跺撼!

技巧

這里我們用到了裝飾模式窟感,我們用自定義的RecyclerView中的內(nèi)部類Adapter來裝飾原始從Activity傳入的Adapter,我們可以毫無影響之前的邏輯來添加這些額外的Header,Footer,Empty View

實現(xiàn)

廢話不多說歉井,直接上源碼柿祈,拿走,不謝

GloriousRecyclerView

/*
 * Copyright (C) 2017 CXP 277371483@qq.com
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package com.xpc.recylerviewdemo;

import android.content.Context;
import android.support.annotation.Nullable;
import android.support.v7.widget.RecyclerView;
import android.util.AttributeSet;
import android.view.View;
import android.view.ViewGroup;

/**
 * Created on 17-2-14
 *
 * @author cxp
 */

public class GloriousRecyclerView extends RecyclerView {

    private View mHeaderView;
    private View mFooterView;
    private View mEmptyView;
    private GloriousAdapter mGloriousAdapter;

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

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

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

    public void addHeaderView(View view) {
        mHeaderView = view;
        mGloriousAdapter.notifyItemInserted(0);
    }

    public void addFooterView(View view) {
        mFooterView = view;
        mGloriousAdapter.notifyItemInserted(mGloriousAdapter.getItemCount() - 1);
    }

    public void setEmptyView(View view) {
        mEmptyView = view;
        mGloriousAdapter.notifyDataSetChanged();
    }

    @Override
    public void setAdapter(Adapter adapter) {
        if (adapter != null) {
            mGloriousAdapter = new GloriousAdapter(adapter);
        }
        super.setAdapter(mGloriousAdapter);
    }

    private class GloriousAdapter extends RecyclerView.Adapter<ViewHolder> {

        private Adapter mOriginalAdapter;
        private int ITEM_TYPE_NORMAL = 0;
        private int ITEM_TYPE_HEADER = 1;
        private int ITEM_TYPE_FOOTER = 2;
        private int ITEM_TYPE_EMPTY = 3;

        //聰明的人會發(fā)現(xiàn)我們這里用了一個裝飾模式
        public GloriousAdapter(Adapter originalAdapter) {
            mOriginalAdapter = originalAdapter;
        }

        @Override
        public ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
            if (viewType == ITEM_TYPE_HEADER) {
                return new GloriousViewHolder(mHeaderView);
            } else if (viewType == ITEM_TYPE_EMPTY) {
                return new GloriousViewHolder(mEmptyView);
            } else if (viewType == ITEM_TYPE_FOOTER) {
                return new GloriousViewHolder(mFooterView);
            } else {
                return mOriginalAdapter.onCreateViewHolder(parent, viewType);
            }
        }

        @Override
        public void onBindViewHolder(ViewHolder holder, int position) {
            int type = getItemViewType(position);
            if (type == ITEM_TYPE_HEADER || type == ITEM_TYPE_FOOTER || type == ITEM_TYPE_EMPTY) {
                return;
            }
            int realPosition = getRealItemPosition(position);
            mOriginalAdapter.onBindViewHolder(holder, realPosition);
        }

        @Override
        public int getItemCount() {
            int itemCount = mOriginalAdapter.getItemCount();
            //加上其他各種View
            if (null != mEmptyView && itemCount == 0) itemCount++;
            if (null != mHeaderView) itemCount++;
            if (null != mFooterView) itemCount++;
            return itemCount;
        }

        @Override
        public int getItemViewType(int position) {
            if (null != mHeaderView && position == 0) return ITEM_TYPE_HEADER;
            if (null != mFooterView && position == getItemCount() - 1) return ITEM_TYPE_FOOTER;
            if (null != mEmptyView && mOriginalAdapter.getItemCount() == 0) return ITEM_TYPE_EMPTY;
            return ITEM_TYPE_NORMAL;
        }

        private int getRealItemPosition(int position) {
            if (null != mHeaderView) {
                return position - 1;
            }
            return position;
        }

        /**
         * ViewHolder 是一個抽象類
         */
        class GloriousViewHolder extends ViewHolder {

            GloriousViewHolder(View itemView) {
                super(itemView);
            }
        }
    }
}

Activity

public class GloriousActivity extends AppCompatActivity {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_glorious_recycler_view);
        GloriousRecyclerView recyclerView = (GloriousRecyclerView) findViewById(R.id.recycler_view);
        RecyclerView.LayoutManager layoutManager = new LinearLayoutManager(this,     RecyclerView.VERTICAL, false);
        recyclerView.setLayoutManager(layoutManager);
        
        NormalAdapter adapter = new NormalAdapter(this);
        adapter.setDatas(constructTestDatas());
        
        View footer = LayoutInflater.from(this).inflate(R.layout.layout_footer, recyclerView, false);
        View header = LayoutInflater.from(this).inflate(R.layout.layout_header, recyclerView, false);
        View empty = LayoutInflater.from(this).inflate(R.layout.layout_empty, recyclerView, false);
        
        recyclerView.setAdapter(adapter);
        recyclerView.addHeaderView(header);
        recyclerView.addFooterView(footer);
        recyclerView.setEmptyView(empty);
    }

    private List<String> constructTestDatas() {
        List<String> datas = new ArrayList<>();
        datas.add("劉一");
        //...
        datas.add("鄭十");
        return datas;
    }
}

layout

<RelativeLayout
    xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:background="#888">

    <com.xpc.recylerviewdemo.GloriousRecyclerView
        android:id="@+id/recycler_view"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        />

</RelativeLayout>

總結(jié)


本章一步步帶領(lǐng)大家為RecylerView添加HeaderView,FooterView,EmptyView,以及完成了對GloriousRecyclerView的封裝哩至,使我們的開發(fā)更加的便捷躏嚎。

網(wǎng)上或許有類似的解決方案,但是都沒有講解為什么要這樣做菩貌,正所謂知其然不知其所以然卢佣。如果你認(rèn)真讀了這篇文章,相信會對你有所幫助箭阶。

上一篇

RecyclerView從入門到入神——初識RecyclerView(一)

下一篇

RecyclerView從入門到入神——為RecyclerView添加下拉刷新和上拉加載更多(三)

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末虚茶,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子仇参,更是在濱河造成了極大的恐慌嘹叫,老刑警劉巖,帶你破解...
    沈念sama閱讀 207,113評論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件诈乒,死亡現(xiàn)場離奇詭異罩扇,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)怕磨,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,644評論 2 381
  • 文/潘曉璐 我一進(jìn)店門暮蹂,熙熙樓的掌柜王于貴愁眉苦臉地迎上來寞缝,“玉大人癌压,你說我怎么就攤上這事仰泻。” “怎么了滩届?”我有些...
    開封第一講書人閱讀 153,340評論 0 344
  • 文/不壞的土叔 我叫張陵集侯,是天一觀的道長。 經(jīng)常有香客問我帜消,道長棠枉,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 55,449評論 1 279
  • 正文 為了忘掉前任泡挺,我火速辦了婚禮辈讶,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘娄猫。我一直安慰自己贱除,他們只是感情好,可當(dāng)我...
    茶點故事閱讀 64,445評論 5 374
  • 文/花漫 我一把揭開白布媳溺。 她就那樣靜靜地躺著月幌,像睡著了一般。 火紅的嫁衣襯著肌膚如雪悬蔽。 梳的紋絲不亂的頭發(fā)上扯躺,一...
    開封第一講書人閱讀 49,166評論 1 284
  • 那天,我揣著相機(jī)與錄音蝎困,去河邊找鬼录语。 笑死,一個胖子當(dāng)著我的面吹牛禾乘,可吹牛的內(nèi)容都是我干的澎埠。 我是一名探鬼主播,決...
    沈念sama閱讀 38,442評論 3 401
  • 文/蒼蘭香墨 我猛地睜開眼盖袭,長吁一口氣:“原來是場噩夢啊……” “哼失暂!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起鳄虱,我...
    開封第一講書人閱讀 37,105評論 0 261
  • 序言:老撾萬榮一對情侶失蹤弟塞,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后拙已,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體决记,經(jīng)...
    沈念sama閱讀 43,601評論 1 300
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 36,066評論 2 325
  • 正文 我和宋清朗相戀三年倍踪,在試婚紗的時候發(fā)現(xiàn)自己被綠了系宫。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片索昂。...
    茶點故事閱讀 38,161評論 1 334
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖扩借,靈堂內(nèi)的尸體忽然破棺而出椒惨,到底是詐尸還是另有隱情,我是刑警寧澤潮罪,帶...
    沈念sama閱讀 33,792評論 4 323
  • 正文 年R本政府宣布康谆,位于F島的核電站,受9級特大地震影響嫉到,放射性物質(zhì)發(fā)生泄漏沃暗。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 39,351評論 3 307
  • 文/蒙蒙 一何恶、第九天 我趴在偏房一處隱蔽的房頂上張望孽锥。 院中可真熱鬧,春花似錦细层、人聲如沸惜辑。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,352評論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽韵丑。三九已至,卻和暖如春虚缎,著一層夾襖步出監(jiān)牢的瞬間撵彻,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 31,584評論 1 261
  • 我被黑心中介騙來泰國打工实牡, 沒想到剛下飛機(jī)就差點兒被人妖公主榨干…… 1. 我叫王不留陌僵,地道東北人。 一個月前我還...
    沈念sama閱讀 45,618評論 2 355
  • 正文 我出身青樓创坞,卻偏偏與公主長得像碗短,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子题涨,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 42,916評論 2 344

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