1. 程式人生 > >為RecyclerView新增頭檢視和尾檢視

為RecyclerView新增頭檢視和尾檢視

大家都知道RecyclerView本身是沒有實現頭檢視和尾檢視的,但是我們也有方法。今天我們實現的是將RecyclerView變成GirdLayout佈局實現頭檢視和尾檢視。原理很簡單,就是通過getItemViewType()方法將RecyclerView中所有的item進行分類,以前我們對ListView進行分組也是通過這個方法進行實現,所不同的時最後我們還要設定頭檢視和尾檢視的佔位。好了,現在我們直接進入程式碼講解。

首先,我們建立一個抽象類SectionForRecyclerViewAdapter,我們在定義三個泛型<Hextends RecyclerView.ViewHolder,

VH extends RecyclerView.ViewHolder,F extends RecyclerView.ViewHolder>分別繼承RecyclerView.ViewHolder,其中<H>類是繼承RecyclerView.ViewHolder的header檢視,<VH>類是繼承RecyclerView.ViewHolder的item檢視,<F>類是繼承RecyclerView.ViewHolder的footer檢視,最後抽象類在繼承RecyclerView.Adapter<RecyclerView.ViewHolder>完整的程式碼如下:

<pre name="code" class="java">public abstract class SectionForRecyclerViewAdapter<H extends RecyclerView.ViewHolder,
        VH extends RecyclerView.ViewHolder,
        F extends RecyclerView.ViewHolder>
        extends RecyclerView.Adapter<RecyclerView.ViewHolder>{
}

接下來,我們開始我們開始定義我們所需要的變數。首先定義三個分別代表三個型別的三個靜態常量:

private static final int TYPE_SECTION_HEADER = -1;
private static final int TYPE_SECTION_FOOTER = -2;
private static final int TYEP_ITEM = -3;

接著定義可記錄RecyclerView中每個item是屬於第幾組,在組中的位置數變數:

private int[] sectionForPosition = null;//第幾組
private int[] positionWithInSection = null;//在組中的位置數<span style="font-family: Arial, Helvetica, sans-serif; background-color: rgb(255, 255, 255);">	</span>

最後,我們定義RecyclerView中每個item是否是header檢視,footer檢視的boolean陣列和總item數:

<pre name="code" class="java">private boolean[] isHeader = null;
private boolean[] isFooter = null;
private int count = 0;//總數量

好了,所有所需要的變數我們就定義好了,完整的程式碼如下:

<pre name="code" class="java">public abstract class SectionForRecyclerViewAdapter<H extends RecyclerView.ViewHolder,
        VH extends RecyclerView.ViewHolder,
        F extends RecyclerView.ViewHolder>
        extends RecyclerView.Adapter<RecyclerView.ViewHolder>{

    private static final int TYPE_SECTION_HEADER = -1;
    private static final int TYPE_SECTION_FOOTER = -2;
    private static final int TYEP_ITEM = -3;

    private int[] sectionForPosition = null;//第幾組
    private int[] positionWithInSection = null;//在組中的位置數
    private boolean[] isHeader = null;
    private boolean[] isFooter = null;
    private int count = 0;//總數量
}

抽象類嘛,對吧,那我們肯定是需要抽象方法的呀,那麼下面我們就開始定義要被子類所實現的抽象方法:

<pre name="code" class="objc">//第一個方法,獲取組數:
protected abstract int getSectionCount();
//第二個方法,獲取每組item數:
protected abstract int getItemCountForSection(int section);
//第三個方法,判斷是否有footer檢視:
protected abstract boolean hasFooterInSection(int section);
//第四個方法,用類H建立header檢視,所需引數同RecyclerView中的onCreateViewHolder一樣:
protected abstract H onCreateSectionHeaderViewHolder(ViewGroup parent, int viewType);
//第五個方法,用類F建立footer檢視,所有參需同RecyclerView中的onCreateViewHolder一樣:
protected abstract F onCreateSectionFooterViewHolder(ViewGroup parent, int viewType);
//第六個方法,用類VH建立item檢視,所需引數同RecyclerView中的onCreateViewHolder一樣:
protected abstract VH onCreateItemViewHolder(ViewGroup parent, int viewType);
//第七個方法,繫結header檢視,注意第二個引數為section,代表組數:
protected abstract void onBindSectionHeaderViewHodler(H holder, int section);
//第八個方法,繫結footer檢視,第二個引數同第七個方法一樣:
protected abstract void onBindSectionFooterViewHolder(F holder, int section);
//第九個方法,繫結item檢視,這時我們就需要三個引數了,第一個不說,第二section代表組數,第三個position為組數中的位置,而不是整個RecyclerView中的位置了
protected abstract void onBindItemViewHolder(VH holder, int section, int position);

好了,九個抽象方法我們就定義完啦,完整程式碼如下:

public abstract class SectionForRecyclerViewAdapter<H extends RecyclerView.ViewHolder,
        VH extends RecyclerView.ViewHolder,
        F extends RecyclerView.ViewHolder>
        extends RecyclerView.Adapter<RecyclerView.ViewHolder>{

    private static final int TYPE_SECTION_HEADER = -1;
    private static final int TYPE_SECTION_FOOTER = -2;
    private static final int TYEP_ITEM = -3;

    private int[] sectionForPosition = null;//第幾組
    private int[] positionWithInSection = null;//在組中的位置數
    private boolean[] isHeader = null;
    private boolean[] isFooter = null;
    private int count = 0;//總數量

    /**
     * 獲取組數
     */
    protected abstract int getSectionCount();

    /**
     * 獲取每組的item數
     */
    protected abstract int getItemCountForSection(int section);

    /**
     * 判斷是否有footer檢視
     */
    protected abstract boolean hasFooterInSection(int section);

    /**
     * 用類H建立header檢視
     */
    protected abstract H onCreateSectionHeaderViewHolder(ViewGroup parent, int viewType);

    /**
     * 用類F建立Footer檢視
     */
    protected abstract F onCreateSectionFooterViewHolder(ViewGroup parent, int viewType);

    /**
     * 用類VH建立item檢視
     */
    protected abstract VH onCreateItemViewHolder(ViewGroup parent, int viewType);

    /**
     * 繫結header檢視
     */
    protected abstract void onBindSectionHeaderViewHodler(H holder, int section);

    /**
     * 繫結Footer檢視
     */
    protected abstract void onBindSectionFooterViewHolder(F holder, int section);

    /**
     * 繫結item檢視
     */
    protected abstract void onBindItemViewHolder(VH holder, int section, int position);
}

好,那麼準備工作我們就做好了,接下我們要乾的就是去實現功能。首先,我們肯定會有個構造方法,我們先寫出來,具體要裡面有沒我們要初始化的方法我們先來個TODO標記下,回頭我們在看看要實現什麼:

public SectionForRecyclerViewAdapter() {
     super();
     //TODO
}

然後RecyclerView中還有個方法我們是需要重寫的,那麼這個方法是幹嘛的呢,我們查下API。API是這樣定義的,Called by RecyclerView when it starts observing this Adapter.大概意思就是Adapter觀察者回調方法,既然知道了這個方法是幹什麼用的,那我們又要怎麼去用呢,我們也還是TODO下,等會實現:

@Override
public void onAttachedToRecyclerView(RecyclerView recyclerView) {
    super.onAttachedToRecyclerView(recyclerView);
    //TODO
<span style="font-family: Arial, Helvetica, sans-serif;">}</span>

接下來我們要實現獲取下count的數量,count的數量總的有包括header和item的數量,然後我們還要判斷下是否有footer,如果有的話我們還要加上footer:

    //獲取count的數量
    private int countItems() {
        int count = 0;
        int sections = getSectionCount();

        for (int i = 0; i < sections; i++) {
            count += 1 + getItemCountForSection(i) + (hasFooterInSection(i) ? 1 : 0);
        }

        return count;
    }

然後我們就可以初始化陣列的長度了:

private void initAllArraysLength(int count) {
        sectionForPosition = new int[count];
        positionWithInSection = new int[count];
        isHeader = new boolean[count];
        isFooter = new boolean[count];
}

同時,我們在重寫getItemCount()方法:

@Override
public int getItemCount() {
     return count;
}

既然設定了長度了,我們就開始對這個四個陣列進行操作,首先我們先定義一個用來填充他們的方法,如下:

    //為整個count設定是否是頭節點,尾節點,節點
    private void setPrecomputedItem(int index, boolean isHeader, boolean isFooter, int section, int position) {
        this.isHeader[index] = isHeader;
        this.isFooter[index] = isFooter;
        sectionForPosition[index] = section;
        positionWithInSection[index] = position;
    }

這個方法實現後,我們開始設定item的型別:

    //設定item的型別
    private void setupItemViewType() {
        int sections = getSectionCount();
        int index = 0;

        for (int i = 0; i < sections; i++) {
            setPrecomputedItem(index, true, false, i, 0);
            index++;

            for (int j = 0; j < getItemCountForSection(i); j++) {
                setPrecomputedItem(index, false, false, i, j);
                index++;
            }

            if (hasFooterInSection(i)) {
                setPrecomputedItem(index, false, true, i, 0);
                index++;
            }
        }
    }

這幾個方法實現後,我們是不是需要呼叫下呀,我們在定義一個init方法同意呼叫他們:

    private void init() {
        count = countItems();
        initAllArraysLength(count);
        setupItemViewType();
    }

OK,count和四個陣列我們就告一段落了,同時我們還記錄了每個item的型別,接下來我們就開始重寫getItemViewType()方法,告訴系統,我們有幾種型別,在重寫getItemViewType的同時,我們先實現幾個方法,首先實現的是獲取header、footer和item檢視的型別:

    /**
     * 獲取header檢視型別
     */
    protected int getSectionHeaderViewType(int section) {
        return TYPE_SECTION_HEADER;
    }

    /**
     * 獲取footer檢視型別
     */
    protected int getSectionFooterViewType(int section) {
        return TYPE_SECTION_FOOTER;
    }

    /**
     * 獲取item檢視型別
     */
    protected int getSectionItemViewType(int section, int position) {
        return TYEP_ITEM;
    }

然後是,實現該position是否是header或footer檢視,注意,這裡的position指的時整個RecyclerView的position而不是分組裡的position,同時我們還把這個方法設定為共有的,還記得我一開始說的要為RecyclerView設定為GirdLayout佈局吧,在這裡我們就需要這兩個方法:

    /**
     * 判斷該節點是否是Header
     */
    public boolean isSectionHeaderPosition(int position) {
        if (isHeader == null) {
            init();
        }

        return isHeader[position];
    }

    /**
     * 判斷該節點是否是Footer
     */
    public boolean isSectionFooterPosition(int position) {
        if (isFooter == null) {
            init();
        }
        return isFooter[position];
    }

OK,準備方法實現好了,我們就開始重寫getItemViewType:

    @Override
    public int getItemViewType(int position) {
        if (sectionForPosition == null) {
            init();
        }

        int section = sectionForPosition[position];
        int index = positionWithInSection[position];

        if (isSectionHeaderPosition(position)) {
            return getSectionHeaderViewType(section);
        } else if (isSectionFooterPosition(position)) {
            return getSectionFooterViewType(section);
        } else {
            return getSectionItemViewType(section, position);
        }
    }

我們已經告訴系統我們有幾種型別的檢視了,接下來我們就開始重寫建立檢視和繫結檢視方法,同樣,在實現之前,我們先實現兩個方法,用來判斷header和footer檢視型別:

    /**
     * 判斷該節點是否是Header
     */
    public boolean isSectionHeaderPosition(int position) {
        if (isHeader == null) {
            init();
        }

        return isHeader[position];
    }

    /**
     * 判斷該節點是否是Footer
     */
    public boolean isSectionFooterPosition(int position) {
        if (isFooter == null) {
            init();
        }
        return isFooter[position];
    }

實現後,我們開始重寫,具體我就不說了,直接貼上程式碼:

    @Override
    public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {

        RecyclerView.ViewHolder viewHolder;

        if (isSectionHeaderViewType(viewType)) {
            viewHolder = onCreateSectionHeaderViewHolder(parent, viewType);
        } else if (isSectionFooterViewType(viewType)) {
            viewHolder = onCreateSectionFooterViewHolder(parent, viewType);
        } else {
            viewHolder = onCreateItemViewHolder(parent, viewType);
        }


        return viewHolder;
    }

    @Override
    public void onBindViewHolder(RecyclerView.ViewHolder holder, int position) {
        int sections = sectionForPosition[position];
        int index = positionWithInSection[position];

        if (isSectionHeaderPosition(position)) {
            onBindSectionHeaderViewHodler((H) holder, sections);
        } else if (isSectionFooterPosition(position)) {
            onBindSectionFooterViewHolder((F) holder, sections);
        } else {
            onBindItemViewHolder((VH) holder, sections, index);
        }
    }<span style="font-family: Arial, Helvetica, sans-serif; background-color: rgb(255, 255, 255);">	</span>

這兩個實現後,我們基本算是完成了,還記得我們有幾個未實現的方法吧,我們都標記著TODO,實現他們的之前,我們先建立個內部類,用於觀察資料的變化,具體方法可以檢視下API。

class SectionDataObserver extends RecyclerView.AdapterDataObserver {
        @Override
        public void onChanged() {
            init();
        }
    }

觀察類實現後,我們在構造方法內進行註冊:

 public SectionForRecyclerViewAdapter() {
        super();
        //註冊adapter資料觀察者
        registerAdapterDataObserver(new SectionDataObserver());
    }

最後,我們在實現這個觀察者回調方法:

    @Override
    public void onAttachedToRecyclerView(RecyclerView recyclerView) {
        super.onAttachedToRecyclerView(recyclerView);
        init();
    }

好了,到這我們把SectionForRecyclerViewAdapter類就全部完成了,完整程式碼如下:

import android.support.v7.widget.RecyclerView;
import android.view.ViewGroup;


/**
 *
 * @param <H>類是繼承RecyclerView.ViewHolder的header檢視
 * @param <VH>類是繼承RecyclerView.ViewHolder的item檢視
 * @param <F>類是繼承RecyclerView.ViewHolder的footer檢視
 *
 */
public abstract class SectionForRecyclerViewAdapter<H extends RecyclerView.ViewHolder,
        VH extends RecyclerView.ViewHolder,
        F extends RecyclerView.ViewHolder>
        extends RecyclerView.Adapter<RecyclerView.ViewHolder>{

    private static final int TYPE_SECTION_HEADER = -1;
    private static final int TYPE_SECTION_FOOTER = -2;
    private static final int TYEP_ITEM = -3;

    private int[] sectionForPosition = null;//第幾組
    private int[] positionWithInSection = null;//在組中的位置數
    private boolean[] isHeader = null;
    private boolean[] isFooter = null;
    private int count = 0;//總數量

    public SectionForRecyclerViewAdapter() {
        super();
        //註冊adapter資料觀察者
        registerAdapterDataObserver(new SectionDataObserver());
    }

    @Override
     public void onAttachedToRecyclerView(RecyclerView recyclerView) {
        super.onAttachedToRecyclerView(recyclerView);
        init();
    }

    /**
     *
     * @return 總數量(包括header和footer的數量)
     */
    @Override
    public int getItemCount() {
        return count;
    }

    /**
     * 初始化
     */
    private void init() {
        count = countItems();
        initAllArraysLength(count);
        setupItemViewType();
    }

    /**
     * 設定陣列的長度
     */
    private void initAllArraysLength(int count) {
        sectionForPosition = new int[count];
        positionWithInSection = new int[count];
        isHeader = new boolean[count];
        isFooter = new boolean[count];
    }

    //獲取count的數量
    private int countItems() {
        int count = 0;
        int sections = getSectionCount();

        for (int i = 0; i < sections; i++) {
            count += 1 + getItemCountForSection(i) + (hasFooterInSection(i) ? 1 : 0);
        }

        return count;
    }

    //設定item的型別
    private void setupItemViewType() {
        int sections = getSectionCount();
        int index = 0;

        for (int i = 0; i < sections; i++) {
            setPrecomputedItem(index, true, false, i, 0);
            index++;

            for (int j = 0; j < getItemCountForSection(i); j++) {
                setPrecomputedItem(index, false, false, i, j);
                index++;
            }

            if (hasFooterInSection(i)) {
                setPrecomputedItem(index, false, true, i, 0);
                index++;
            }
        }
    }

    //為整個count設定是否是頭節點,尾節點,節點
    private void setPrecomputedItem(int index, boolean isHeader, boolean isFooter, int section, int position) {
        this.isHeader[index] = isHeader;
        this.isFooter[index] = isFooter;
        sectionForPosition[index] = section;
        positionWithInSection[index] = position;
    }

    @Override
    public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {

        RecyclerView.ViewHolder viewHolder;

        if (isSectionHeaderViewType(viewType)) {
            viewHolder = onCreateSectionHeaderViewHolder(parent, viewType);
        } else if (isSectionFooterViewType(viewType)) {
            viewHolder = onCreateSectionFooterViewHolder(parent, viewType);
        } else {
            viewHolder = onCreateItemViewHolder(parent, viewType);
        }


        return viewHolder;
    }

    @Override
    public void onBindViewHolder(RecyclerView.ViewHolder holder, int position) {
        int sections = sectionForPosition[position];
        int index = positionWithInSection[position];

        if (isSectionHeaderPosition(position)) {
            onBindSectionHeaderViewHodler((H) holder, sections);
        } else if (isSectionFooterPosition(position)) {
            onBindSectionFooterViewHolder((F) holder, sections);
        } else {
            onBindItemViewHolder((VH) holder, sections, index);
        }
    }



    @Override
    public int getItemViewType(int position) {
        if (sectionForPosition == null) {
            init();
        }

        int section = sectionForPosition[position];
        int index = positionWithInSection[position];

        if (isSectionHeaderPosition(position)) {
            return getSectionHeaderViewType(section);
        } else if (isSectionFooterPosition(position)) {
            return getSectionFooterViewType(section);
        } else {
            return getSectionItemViewType(section, position);
        }
    }

    /**
     * 獲取header檢視型別
     */
    protected int getSectionHeaderViewType(int section) {
        return TYPE_SECTION_HEADER;
    }

    /**
     * 獲取footer檢視型別
     */
    protected int getSectionFooterViewType(int section) {
        return TYPE_SECTION_FOOTER;
    }

    /**
     * 獲取item檢視型別
     */
    protected int getSectionItemViewType(int section, int position) {
        return TYEP_ITEM;
    }

    /**
     * 判斷該節點是否是Header
     */
    public boolean isSectionHeaderPosition(int position) {
        if (isHeader == null) {
            init();
        }

        return isHeader[position];
    }

    /**
     * 判斷該節點是否是Footer
     */
    public boolean isSectionFooterPosition(int position) {
        if (isFooter == null) {
            init();
        }
        return isFooter[position];
    }

    /**
     * 判斷是否是header檢視
     */
    protected boolean isSectionHeaderViewType(int viewType) {
        return  viewType == TYPE_SECTION_HEADER;
    }

    /**
     * 判斷是否是footer檢視
     */
    protected boolean isSectionFooterViewType(int viewType) {
        return viewType == TYPE_SECTION_FOOTER;
    }

    /**
     * 獲取組數
     */
    protected abstract int getSectionCount();

    /**
     * 獲取每組的item數
     */
    protected abstract int getItemCountForSection(int section);

    /**
     * 判斷是否有footer檢視
     */
    protected abstract boolean hasFooterInSection(int section);

    /**
     * 用類H建立header檢視
     */
    protected abstract H onCreateSectionHeaderViewHolder(ViewGroup parent, int viewType);

    /**
     * 用類F建立Footer檢視
     */
    protected abstract F onCreateSectionFooterViewHolder(ViewGroup parent, int viewType);

    /**
     * 用類VH建立item檢視
     */
    protected abstract VH onCreateItemViewHolder(ViewGroup parent, int viewType);

    /**
     * 繫結header檢視
     */
    protected abstract void onBindSectionHeaderViewHodler(H holder, int section);

    /**
     * 繫結Footer檢視
     */
    protected abstract void onBindSectionFooterViewHolder(F holder, int section);

    /**
     * 繫結item檢視
     */
    protected abstract void onBindItemViewHolder(VH holder, int section, int position);

    class SectionDataObserver extends RecyclerView.AdapterDataObserver {
        @Override
        public void onChanged() {
            init();
        }
    }

}

接下來,我們還要實現個GridLayout佈局管理類,具體程式碼如下:

public class SectionedSpanSizeLookup extends GridLayoutManager.SpanSizeLookup {

    protected SectionForRecyclerViewAdapter<?, ?, ?> adapter = null;
    protected GridLayoutManager layoutManager = null;

    public SectionedSpanSizeLookup(SectionForRecyclerViewAdapter<?, ?, ?> adapter,
                   GridLayoutManager layoutManager) {
        this.adapter = adapter;
        this.layoutManager = layoutManager;
    }

    @Override
    public int getSpanSize(int position) {
        if (adapter.isSectionHeaderPosition(position) ||
                adapter.isSectionFooterPosition(position)) {
            return layoutManager.getSpanCount();
        } else {
            return 1;
        }
    }
}

OK,到這裡我們就全部寫完了。

我們來看看效果圖:


原始碼進行效果實現,具體可下載下來看看

連結: http://pan.baidu.com/s/1jHzKDme 密碼: gn5c