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)實(shí)現(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);

不幸的是,在運(yùn)行時爆出空指針異常

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);

悲劇的是,在運(yùn)行時依然爆出空指針異常

RecyclerView_Header_Error_2.png

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

從Adapter入手

思路

屢次受挫,確實(shí)是有點(diǎn)動搖軍心,仿佛看不到希望,但是有句話叫做 Nerver say Nerver, 好吧,至少我們知道了一點(diǎn),我們要想在RecylerView中添加任何子View,那么這個View必須要有ViewHolder

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

我們創(chuàng)建RecyclerView的Adapter時,必須要實(shí)現(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里:

實(shí)現(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í)數(shù)據(jù)個數(shù)中+1

效果展示
RecyclerView_With_Header.png

為RecyclerView添加EmptyView, FooterView


為RecyclerView添加FooterView,EmptyView 其實(shí)和添加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

封裝


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

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

技巧

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

實(shí)現(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)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容