Recycleview添加頭部

本文已授權(quán)微信公眾號:鴻洋(hongyangAndroid)在微信公眾號平臺原創(chuàng)首發(fā)。

轉(zhuǎn)載請標(biāo)明出處:

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

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

1、概述

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

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

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

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

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

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

恩,其實(shí)本來我是想偷個(gè)懶的,因?yàn)長oader寫過一篇類似的文章,文章見文末參考鏈接。但是我發(fā)現(xiàn)被別的公眾號推送了~~

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

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

2 思路

(1)原理

對于添加headerView或者footerView的思路

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

有了思路以后,我們心里就妥了,最起碼我們的內(nèi)心中想想是可以實(shí)現(xiàn)的,接下來考慮一些細(xì)節(jié)。

(2)一些細(xì)節(jié)

假設(shè)我們現(xiàn)在已經(jīng)完成了RecyclerView的編寫,忽然有個(gè)需求,需要在列表上加個(gè)HeaderView,此時(shí)我們該怎么辦呢?

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

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

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

況且一個(gè)項(xiàng)目中可能多個(gè)RecyclerView都需要在其列表中添加headerView。

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

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

我希望的用法是這樣的:

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

在不改變原有的Adapter基礎(chǔ)上去增強(qiáng)其功能。

3、初步的實(shí)現(xiàn)

(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();? ? }}

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

這里你可以看到,對于多個(gè)HeaderView,講道理我們首先想到的應(yīng)該是使用List,而這里我們?yōu)槭裁匆褂肧parseArrayCompat呢?

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

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

為什么要這么做呢?

這兩個(gè)問題都需要到復(fù)寫onCreateViewHolder的時(shí)候來說明。

(2)復(fù)寫相關(guān)方法

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首先需要復(fù)寫的就是getItemCount和getItemViewType。

getItemCount很好理解;

對于getItemType,可以看到我們的返回值是mHeaderViews.keyAt(position),這個(gè)值其實(shí)就是我們addHeaderView時(shí)的key,footerView是一樣的處理方式,這里可以看出我們?yōu)槊恳粋€(gè)headerView創(chuàng)建了一個(gè)itemType。

onCreateViewHolder

可以看到,我們分別判斷viewType,如果是headview或者是footerview,我們則為其單獨(dú)創(chuàng)建ViewHolder,這里的ViewHolder是我之前寫的一個(gè)通用的庫里面的類,文末有鏈接。當(dāng)然,你也可以自己寫一個(gè)ViewHolder的實(shí)現(xiàn)類,只需要將對應(yīng)的headerView作為itemView傳入ViewHolder的構(gòu)造即可。

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

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

為什么我要讓每個(gè)headerView對應(yīng)一個(gè)itemType,而不是固定的一個(gè)?

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

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

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

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

onBindViewHolder

onBindViewHolder比較簡單,發(fā)現(xiàn)是HeaderView或者FooterView直接return即可,因?yàn)閷τ陬^部和底部我們僅僅做展示即可,對于事件應(yīng)該是在addHeaderView等方法前設(shè)置。

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

我們看看運(yùn)行效果:

感覺還是不錯的,再不改動原來的Adapter的基礎(chǔ)上,我們完成了初步的實(shí)現(xiàn)。

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

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

那么我們該如何處理呢?讓每個(gè)headerView獨(dú)立的占據(jù)一行?

4、進(jìn)一步的完善

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

(1)針對GridLayoutManager

在我們的HeaderAndFooterWrapper中復(fù)寫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());? ? }}

當(dāng)發(fā)現(xiàn)layoutManager為GridLayoutManager時(shí),通過設(shè)置SpanSizeLookup,對其getSpanSize方法,返回值設(shè)置為layoutManager.getSpanCount();

現(xiàn)在看一下運(yùn)行效果:

哈,終于正常了。

(3)對于StaggeredGridLayoutManager

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

依然不復(fù)雜,重寫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的處理,效果圖就不貼了。

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

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

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

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

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