Android教程網
  1. 首頁
  2. Android 技術
  3. Android 手機
  4. Android 系統教程
  5. Android 游戲
 Android教程網 >> Android技術 >> 關於Android編程 >> ExtraViewWrapperAdapter--添加額外頭部尾部功能的裝飾adapter

ExtraViewWrapperAdapter--添加額外頭部尾部功能的裝飾adapter

編輯:關於Android編程

概述

對於ListView有自帶的方法添加headerView及footerView,但是RecycleView僅僅只是維護緩存的View,本身並不處理內容顯示,都交給了RecycleView.Adapter處理,所以如果想要讓RecycleView也可以添加headerView和footerView的話,只有兩種方式

是重寫RecycleView 是通過Adapter的方式去解決這個問題,將headerView及footerView轉換成內部數據的形式顯示出來.

針對這個問題,這裡使用第二種方式,擴展了Adapter,通過裝飾者模式,只需要將自己的Adatper包括進外層的Adapter中,即可直接添加headerView及footerView,同時不會對原有的數據造成任何影響.
這裡需要注意的是,此處的ExtroViewWrapperAdaper包裝類是通用的,但是更針對於HeaderRecycleAdatper,包括實現了HeaderRecycleAdapter相關的一些接口用於擴展.

對於普通的Adapter,也是可以直接使用.


關於頭部和尾部

由於通過Adapter來完成頭部與尾部的功能添加(這樣做的好處是在任何的RecycleView上都是可以使用的),所以實現方式上跟普通的多類型item的Adapter的實現方式是一致的,這裡不再強調.主要說明如何進行裝飾.

分離原始數據及裝飾數據

對於Adapter,由於每個Adapter完成的功能都不同,所以我們只能不能知道Adapter是具體如何完成的,那麼我們需要盡量將原始的數據與裝飾的數據(頭部/尾部)分離開來.ExtraViewWrapperAdapter主要就是要處理這個.
首先是保存原有Adapter的引用.對於頭部和尾部一般是只添加一個,但這裡考慮到可能會添加多個,所以是允許添加多個的.

對於添加多個的情況,每一個headerView都可能不同,這時就需要提供每一個headerView的一個viewTag(標志),這個標志是唯一的,用於分辨不同的headerView的類型以顯示出來.
對此使用一個內部類來管理添加的headerView或者footerView.

/**
 * 頭部/尾部添加額外View的緩存處理類
 */
public static class HeaderFooterViewCache {
    private List> mViewCacheMap;
    private Map mIndexMap;

    public HeaderFooterViewCache() {
        mViewCacheMap = new LinkedList>();
        mIndexMap = new ArrayMap();
    }

    //返回當前保存的View的個數
    public int size();

    //根據位置獲取對應的標簽
    public int getViewViewTag(int position);

    //根據標簽獲取對應的view
    public View getView(int viewTag);

    //檢測是否已經存在某個標簽
    public boolean isContainsView(int viewTag);

    //添加新view及其唯一標簽,當該標簽已經存在某個view時,將替換該view,返回被替換的view,或者是null;若view為null,返回null,添加失敗
    public View addView(int viewTag, View view);

    //移除某個標簽對應的view
    public boolean removeView(int viewTag);

    //清除所有的view
    public void clearAllView();

    //獲取view的標簽列表,標簽應該是唯一的
    public Set getViewTags();
}

這裡僅給出部分方法簽名及其作用說明.具體實現暫不給出.這裡緩存時使用兩個不同的數據結構,一個是List,一個是Map,原因是List用來保存添加的view的順序,Map是用於根據標簽匹配並保存添加的view.


headerView與footerView的創建與顯示

Adapter是自己加載layout並創建view的,但是headerView與footerView是不同的,是直接添加的view並不通過adapter加載與綁定數據.所以在這裡使用的ViewHolder也只是一個臨時保存的容器而已.
onCreateViewHolder()中創建headerView與footerView的holder

@Override
public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
    return new ExtraViewHolder(mHeaderCache.getView(viewType));
}

ExtraViewHolder類的實現僅僅是繼承RecycleView.ViewHolder而已,並沒有任何其它的操作.這裡只是為了配合Adapter的實現方式.
headerView與footerView的創建完全由外部處理,Adapter並不作任何處理(僅僅是緩存並顯示而已),view的獲取是通過緩存的HeaderFooterViewCache類通過唯一的viewTag(標簽)進行獲取的.


使用唯一的標簽

由以上可知,headerView與FooterView是被HeaderFooterViewCache緩存的,並且使用唯一的viewTag進行標識.
使用Map的原因是標簽必須是唯一的,一個標簽也只能用於一個view,這樣在Adapter加載view時才不會導致有關viewType問題.
以下為Adapter需要實現的一些方法.

//根據位置獲取view的類型
public int getItemViewType(int position){
    //這裡僅給出headerView位置的代碼,其它位置代碼忽略
    return mHeaderCache.getViewViewTag(position);
}

//根據view的類型加載view
public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType){
    //這裡僅給出headerView的holder創建
    return new ExtraViewHolder(mHeaderCache.getView(viewType));
}

Map中的唯一的viewTag標簽就是用於此處的viewType,由於加載holder是通過viewType來創建的,所以不同的viewTag代表了不同的viewType,也是代表了不同的view.
通過這種方式處理的原因是,headerView與footerView是在創建ExtraViewHolder時需要使用的,而創建一個holder只會在onCreateViewHolder()中進行創建.此時參數來源僅有兩個:

parent,父容器 viewType,item類型

這兩個參數顯然僅有第二個參數可以使用.由於headerView和FooterView理論上也不會有很多,因此可以通過指定唯一的viewTag匹配對應的view,同時將viewTag傳遞到onCreateViewHolder()就可以獲取到該tag對應的view了.

對此,在Adapter的getItemViewType()方法中返回的viewType實際上也就是view所對應的viewTag.這樣在一定程度上可以減少很多的計算與匹配工作.


關於headerView與FooterView位置的計算

RecycleView很重要的一個特點是根據position進行顯示item.由於添加了headerView與FooterView,所以原始Adapter中position相對於原來的位置必定會改變.
這裡需要計算headerView與FooterView的數量.關於這部分在前面給出的緩存類HeaderFooterViewCache中已經有相關的方法了,所以可以通過該方法直接獲取其對應的數量.

private int getHeaderViewCount() {
    return mHeaderCache.size();
}

這裡需要注意,headerView是通過mHeaderCache緩存管理的,footerView是通過mFooterCache緩存管理的,並不是同一個類管理兩種view.
得到headerView或者footerView的數量後,就可以很方便地計算了.headerView必定在InnerAdapter的item之前,所以position對應的前面都是headerView.

//headerView的position
int headerPosition=position;
//innerAdapter中item的position,除去header部分
int innerPosition=position-headerViewCount;
//footerView的position,除去header及innerAdapter的item數量
int footerPosition=position-headerViewCount-innerAdapter.getItemCount();

ExtraViewWrapperAdapter提供了獲取內部adapter的位置的方法,該方法就是基於以上的方式進行計算得到的.

//這裡的wrapAdapterPosition指 ExtraViewWrapperAdapter 對應的position
public int getInnerAdapterPosition(int wrapAdapterPosition) {
    int headerViewCount = getHeaderViewCount();
    int innerPosition = wrapAdapterPosition - headerViewCount;
    if (mInnerAdapter != null && innerPosition < mInnerAdapter.getItemCount()) {
        return innerPosition;
    } else {
        return -1;
    }
}

頭部尾部的判斷方式

通過以上計算頭部尾部的位置,我們是可以得到他們的判斷方式的.因為判斷頭部尾部是給定一個position,判斷其是否為頭部或者尾部.

//當position在前面的位置且位於headerViewCount的數量范圍內,則說明當前位置為頭部
private boolean isHeaderView(int position) {
    //計算頭部view結束的位置
    int headerEndPosition = getHeaderViewCount();
    return position >= 0 && position < headerEndPosition;
}

尾部的計算方式也是相同的.都是計算當前位置是否在指定的view類型范圍內即可.


HeaderRecycleAdapter的接口相關

由於ExtraViewWrapperAdapter是為了兼容HeaderRecycleAdapter,所以實現了與其相同的一些接口,包括:

IStickerHeaderDecoration,用於顯示固定頭部 ISpanSizeHandler,用於兼容顯示GridLayout的布局方式

由於innerAdapter有自己的接口實現,wrapperAdapter並不能代替其實現的功能,所以只能是通過保存對應的接口實現,並在實現這些接口時(與position有關的方法中)屏蔽headerView及footerView的部分,有關innerAdapter的item部分由其對應的接口自行處理.
大致類似以下的處理方式,此處與兩個接口有關,需要了解的請查看HeaderRecycleAdapterStickHeaderItemDecoration兩個類的分析文章.

//如 IStickerHeaderDecoration 接口中,判斷當前position的item是否有固定頭部時
@Override
public boolean hasStickHeader(int position) {
    //當前位置的item為headerView或者footerView,都不存在固定頭部
    if (isHeaderView(position) || isFooterView(position)) {
        return false;
    } else if (mIStickHeaderDecoration != null) {
        //若不是,則說明當前位置應該是innerAdapter中的item,計算innerAdapter中對應的位置並回調其相關的接口處理
        return mIStickHeaderDecoration.hasStickHeader(getInnerAdapterPosition(position));
    } else {
        return false;
    }
}

這裡涉及到了原innerAdapter相關的一些接口問題.由於HeaderRecycleAdapter是已經實現了相關的接口,所以可以在保存adapter時保存其實現接口的引用.

public void setInnerAdapter(@NonNull RecyclerView.Adapter innerAdapter) {
    mInnerAdapter = innerAdapter;
    //當前adapter實現了 IStickerHeaderDecoration接口,則保存其接口引用
    if (mInnerAdapter != null && mInnerAdapter instanceof StickHeaderItemDecoration.IStickerHeaderDecoration) {
        mIStickHeaderDecoration = (StickHeaderItemDecoration.IStickerHeaderDecoration) mInnerAdapter;
    }

    //當前adapter實現了 ISpanSizeHandler接口,則保存其接口引用
    if (mInnerAdapter != null && mInnerAdapter instanceof HeaderSpanSizeLookup.ISpanSizeHandler) {
        mISpanSizeLookup = (HeaderSpanSizeLookup.ISpanSizeHandler) mInnerAdapter;
    }
}

通過直接判斷adatper是否實現了對應接口直接保存引用,可以更准確地綁定innerAdapter及其相關接口的實現.同時,也存在相關的接口直接設置方法手動綁定當前innerAdapter的實現接口.


其它

ExtraViewWrapperAdapter大致實現的原理如上,主要是為了增加headerView及footerView的添加功能,同時又能兼容原有innerAdapter的功能(主要指HeaderRecycleAdapter).
同時提供了添加多個headerView及footerView的功能,並額外添加了refreshView(刷新View)及loadView(加載View)分別在頂部及底部顯示,默認為null不存在.

//設置刷新顯示的view
mExtraAdapter.setRefreshingHeaderView(yourRefreshView);
//切換刷新/加載/header/footer顯示的狀態
ExtraViewWrapAdapter.setRefreshingViewStatus(true,true,rv);

使用方式

使用方式很簡單,需要添加headerView或者footerView時,直接進行添加,然後設置原始數據innerAdatper,如果是實現了ISpanSizeHandlerIStcikerHeaderDecoration接口的adapter則不需要作額外處理,否則需要考慮一下是否要設置相關的接口實現.

//設置innearAdapter
mExtraAdapter = new ExtraViewWrapAdapter(mNormalAdapter);
//添加headerView
mExtraAdapter.addHeaderView(R.id.header_view, yourView);
//使用extraViewWrapperAdapter代碼原有的adapter作為recycleView的數據綁定
rv.setAdapter(mExtraAdapter);

GitHub地址

https://github.com/CrazyTaro/RecycleViewAdapter
與之前的RecycleView相關的都置於同一個項目中


示例圖片

圖片

  1. 上一頁:
  2. 下一頁:
熱門文章
閱讀排行版
Copyright © Android教程網 All Rights Reserved