Recycleview添加頭部

本文已授權微信公眾號:鴻洋(hongyangAndroid)在微信公眾號平臺原創首發。

轉載請標明出處:

http://blog.csdn.net/lmj623565791/article/details/51854533;

本文出自:【張鴻洋的博客】

1、概述

RecyclerView通過其高度的可定制性深受大家的青睞,也有非常多的使用者開始對它進行封裝或者改造,從而滿足越來越多的需求。

如果你對RecyclerView不陌生的話,你一定遇到過這樣的情況,我想給RecyclerView加個headerView或者footerView,當你敲出.addHeaderView,你會發現并沒有添加頭部或者底部View的相關API。

那么本文主要的內容很明顯了,完成以下工作:

如何為RecyclerView添加HeaderView(支持多個)

如何為RecyclerView添加FooterView(支持多個)

如何讓HeaderView或者FooterView適配各種LayoutManager

恩,其實本來我是想偷個懶的,因為Loader寫過一篇類似的文章,文章見文末參考鏈接。但是我發現被別的公眾號推送了~~

那我只能考慮自己換種思路來解決這個問題,并且提供盡可能多的功能了~

本文首發于我的公眾號,歡迎掃碼關注(二維碼見左側欄)。

2 思路

(1)原理

對于添加headerView或者footerView的思路

其實HeaderView實際上也是Item的一種,只不過顯示在頂部的位置,那么我們完全可以通過為其設置ItemType來完成。

有了思路以后,我們心里就妥了,最起碼我們的內心中想想是可以實現的,接下來考慮一些細節。

(2)一些細節

假設我們現在已經完成了RecyclerView的編寫,忽然有個需求,需要在列表上加個HeaderView,此時我們該怎么辦呢?

打開我們的Adapter,然后按照我們上述的原理,添加特殊的ViewType,然后修改代碼完成。

這是比較常規的做法了,但是有個問題是,如果需要添加viewType,那么可能我們的Adapter需要修改的幅度就比較大了,比如getItemType、getItemCount、onBindViewHolder、onCreateViewHolder等,幾乎所有的方法都要進行改變。

這樣來看,出錯率是非常高的。

況且一個項目中可能多個RecyclerView都需要在其列表中添加headerView。

這么來看,直接改Adapter的代碼是非常不劃算的,最好能夠設計一個類,可以無縫的為原有的Adapter添加headerView和footerView。

本文的思路是通過類似裝飾者模式,去設計一個類,增強原有Adapter的功能,使其支持addHeaderView和addFooterView。這樣我們就可以不去改動我們之前已經完成的代碼,靈活的去擴展功能了。

我希望的用法是這樣的:

mHeaderAndFooterWrapper =newHeaderAndFooterWrapper(mAdapter);t1.setText("Header 1");TextView t2 =newTextView(this);mHeaderAndFooterWrapper.addHeaderView(t2);

在不改變原有的Adapter基礎上去增強其功能。

3、初步的實現

(1) 基本代碼

publicclassHeaderAndFooterWrapperextendsRecyclerView.Adapter{privatestaticfinalintBASE_ITEM_TYPE_HEADER =100000;privatestaticfinalintBASE_ITEM_TYPE_FOOTER =200000;privateSparseArrayCompat mHeaderViews =newSparseArrayCompat<>();privateSparseArrayCompat mFootViews =newSparseArrayCompat<>();privateRecyclerView.Adapter mInnerAdapter;publicHeaderAndFooterWrapper(RecyclerView.Adapter adapter)? ? {? ? ? ? mInnerAdapter = adapter;? ? }privatebooleanisHeaderViewPos(intposition)? ? {returnposition < getHeadersCount();? ? }privatebooleanisFooterViewPos(intposition)? ? {returnposition >= getHeadersCount() + getRealItemCount();? ? }publicvoidaddHeaderView(View view)? ? {? ? ? ? mHeaderViews.put(mHeaderViews.size() + BASE_ITEM_TYPE_HEADER, view);? ? }publicvoidaddFootView(View view)? ? {? ? ? ? mFootViews.put(mFootViews.size() + BASE_ITEM_TYPE_FOOTER, view);? ? }publicintgetHeadersCount()? ? {returnmHeaderViews.size();? ? }publicintgetFootersCount()? ? {returnmFootViews.size();? ? }}

首先我們編寫一個Adapter的子類,我們叫做HeaderAndFooterWrapper,然后再其內部添加了addHeaderView,addFooterView等一些輔助方法。

這里你可以看到,對于多個HeaderView,講道理我們首先想到的應該是使用List,而這里我們為什么要使用SparseArrayCompat呢?

SparseArrayCompat有什么特點呢?它類似于Map,只不過在某些情況下比Map的性能要好,并且只能存儲key為int的情況。

并且可以看到我們對每個HeaderView,都有一個特定的key與其對應,第一個headerView對應的是BASE_ITEM_TYPE_HEADER,第二個對應的是BASE_ITEM_TYPE_HEADER+1;

為什么要這么做呢?

這兩個問題都需要到復寫onCreateViewHolder的時候來說明。

(2)復寫相關方法

publicclassHeaderAndFooterWrapperextendsRecyclerView.Adapter{@OverridepublicRecyclerView.ViewHolderonCreateViewHolder(ViewGroup parent,intviewType)? ? {if(mHeaderViews.get(viewType) !=null)? ? ? ? {? ? ? ? ? ? ViewHolder holder = ViewHolder.createViewHolder(parent.getContext(), mHeaderViews.get(viewType));returnholder;? ? ? ? }elseif(mFootViews.get(viewType) !=null)? ? ? ? {? ? ? ? ? ? ViewHolder holder = ViewHolder.createViewHolder(parent.getContext(), mFootViews.get(viewType));returnholder;? ? ? ? }returnmInnerAdapter.onCreateViewHolder(parent, viewType);? ? }@OverridepublicintgetItemViewType(intposition)? ? {if(isHeaderViewPos(position))? ? ? ? {returnmHeaderViews.keyAt(position);? ? ? ? }elseif(isFooterViewPos(position))? ? ? ? {returnmFootViews.keyAt(position - getHeadersCount() - getRealItemCount());? ? ? ? }returnmInnerAdapter.getItemViewType(position - getHeadersCount());? ? }privateintgetRealItemCount()? ? {returnmInnerAdapter.getItemCount();? ? }@OverridepublicvoidonBindViewHolder(RecyclerView.ViewHolder holder,intposition)? ? {if(isHeaderViewPos(position))? ? ? ? {return;? ? ? ? }if(isFooterViewPos(position))? ? ? ? {return;? ? ? ? }? ? ? ? mInnerAdapter.onBindViewHolder(holder, position - getHeadersCount());? ? }@OverridepublicintgetItemCount()? ? {returngetHeadersCount() + getFootersCount() + getRealItemCount();? ? }}

getItemViewType

由于我們增加了headerView和footerView首先需要復寫的就是getItemCount和getItemViewType。

getItemCount很好理解;

對于getItemType,可以看到我們的返回值是mHeaderViews.keyAt(position),這個值其實就是我們addHeaderView時的key,footerView是一樣的處理方式,這里可以看出我們為每一個headerView創建了一個itemType。

onCreateViewHolder

可以看到,我們分別判斷viewType,如果是headview或者是footerview,我們則為其單獨創建ViewHolder,這里的ViewHolder是我之前寫的一個通用的庫里面的類,文末有鏈接。當然,你也可以自己寫一個ViewHolder的實現類,只需要將對應的headerView作為itemView傳入ViewHolder的構造即可。

這個方法中,我們就可以解答之前的問題了:

為什么我要用SparseArrayCompat而不是List?

為什么我要讓每個headerView對應一個itemType,而不是固定的一個?

對于headerView假設我們有多個,那么onCreateViewHolder返回的ViewHolder中的itemView應該對應不同的headerView,如果是List,那么不同的headerView應該對應著:list.get(0),list.get(1)等。

但是問題來了,該方法并沒有position參數,只有itemType參數,如果itemType還是固定的一個值,那么你是沒有辦法根據參數得到不同的headerView的。

所以,我利用SparseArrayCompat,將其key作為itemType,value為我們的headerView,在onCreateViewHolder中,直接通過itemType,即可獲得我們的headerView,然后構造ViewHolder對象。而且我們的取值是從100000開始的,正常的itemType是從0開始取值的,所以正常情況下,是不可能發生沖突的。

需要說明的是,這里的意思并非是一定不能用List,通過一些特殊的處理,List也能達到上述我描述的效果。

onBindViewHolder

onBindViewHolder比較簡單,發現是HeaderView或者FooterView直接return即可,因為對于頭部和底部我們僅僅做展示即可,對于事件應該是在addHeaderView等方法前設置。

這樣就初步完成了我們的裝飾類,我們分別添加兩個headerView和footerView:

我們看看運行效果:

感覺還是不錯的,再不改動原來的Adapter的基礎上,我們完成了初步的實現。

大家都知道RecyclerView比較強大,可以設置不同的LayoutManager,那么我們換成GridLayoutMananger再看看效果。

好像發現了不對的地方,我們的headerView果真被當成普通的Item處理了,不過由于我們的編寫方式,出現上述情況是可以理解的。

那么我們該如何處理呢?讓每個headerView獨立的占據一行?

4、進一步的完善

好在RecyclerView里面為我們提供了一些方法。

(1)針對GridLayoutManager

在我們的HeaderAndFooterWrapper中復寫onAttachedToRecyclerView方法,如下:

@OverridepublicvoidonAttachedToRecyclerView(RecyclerView recyclerView){? ? innerAdapter.onAttachedToRecyclerView(recyclerView);? ? RecyclerView.LayoutManager layoutManager = recyclerView.getLayoutManager();if(layoutManagerinstanceofGridLayoutManager)? ? {finalGridLayoutManager gridLayoutManager = (GridLayoutManager) layoutManager;finalGridLayoutManager.SpanSizeLookup spanSizeLookup = gridLayoutManager.getSpanSizeLookup();? ? ? ? gridLayoutManager.setSpanSizeLookup(newGridLayoutManager.SpanSizeLookup()? ? ? ? {@OverridepublicintgetSpanSize(intposition)? ? ? ? ? ? {intviewType = getItemViewType(position);if(mHeaderViews.get(viewType) !=null)? ? ? ? ? ? ? {returnlayoutManager.getSpanCount();? ? ? ? ? ? ? }elseif(mFootViews.get(viewType) !=null)? ? ? ? ? ? ? {returnlayoutManager.getSpanCount();? ? ? ? ? ? ? }if(oldLookup !=null)returnoldLookup.getSpanSize(position);return1;? ? ? ? ? ? }? ? ? ? });? ? ? ? gridLayoutManager.setSpanCount(gridLayoutManager.getSpanCount());? ? }}

當發現layoutManager為GridLayoutManager時,通過設置SpanSizeLookup,對其getSpanSize方法,返回值設置為layoutManager.getSpanCount();

現在看一下運行效果:

哈,終于正常了。

(3)對于StaggeredGridLayoutManager

在剛才的代碼中我們好像沒有發現StaggeredGridLayoutManager的身影,StaggeredGridLayoutManager并沒有setSpanSizeLookup這樣的方法,那么該如何處理呢?

依然不復雜,重寫onViewAttachedToWindow方法,如下:

@OverridepublicvoidonViewAttachedToWindow(RecyclerView.ViewHolder holder){? ? mInnerAdapter.onViewAttachedToWindow(holder);intposition = holder.getLayoutPosition();if(isHeaderViewPos(position) || isFooterViewPos(position))? ? {? ? ? ? ViewGroup.LayoutParams lp = holder.itemView.getLayoutParams();if(lp !=null&& lpinstanceofStaggeredGridLayoutManager.LayoutParams)? ? ? ? {? ? ? ? ? ? StaggeredGridLayoutManager.LayoutParams p =? ? ? ? ? ? ? ? ? ? (StaggeredGridLayoutManager.LayoutParams) lp;? ? ? ? ? ? p.setFullSpan(true);? ? ? ? }? ? }}

這樣就完成了對StaggeredGridLayoutManager的處理,效果圖就不貼了。

到此,我們就完成了整個HeaderAndFooterWrapper的編寫,可以在不改變原Adapter代碼的情況下,為其添加一個或者多個headerView或者footerView,以及完成了如何讓HeaderView或者FooterView適配各種LayoutManager

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。

推薦閱讀更多精彩內容

  • 過去的這一兩年, RecyclerView越來越引起了我們Android開發人員的注意,RecyclerView的...
    OlivineVip閱讀 1,292評論 0 14
  • 這篇文章分三個部分,簡單跟大家講一下 RecyclerView 的常用方法與奇葩用法;工作原理與ListView比...
    LucasAdam閱讀 4,426評論 0 27
  • 冬日的傍晚, 寂靜的膠東半島, 唯有北風呼嘯, 刺骨的寒冷, 從海的方向, 席卷大地, 把人逼進燒著炭火的房子。 ...
    盛翰澤遠閱讀 254評論 1 1
  • Q:為什么補水保濕對我們這樣重要呢? A:很多肌膚問題,比如出油、長皺紋等,其實都是肌膚缺水引起的。 對于油性肌膚...
    10fd852a4fab閱讀 284評論 0 1
  • 請你先看下面這道簡單的算術題,用最短時間給出答案: 球拍和球一共1.10美元。球拍比球貴1美元。請問球多少錢? 你...
    some俊俊閱讀 622評論 0 1