本文已授權(quán)微信公眾號:鴻洋(hongyangAndroid)在微信公眾號平臺原創(chuàng)首發(fā)。
轉(zhuǎn)載請標(biāo)明出處:
http://blog.csdn.net/lmj623565791/article/details/51854533;
本文出自:【張鴻洋的博客】
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è)欄)。
對于添加headerView或者footerView的思路
其實(shí)HeaderView實(shí)際上也是Item的一種,只不過顯示在頂部的位置,那么我們完全可以通過為其設(shè)置ItemType來完成。
有了思路以后,我們心里就妥了,最起碼我們的內(nèi)心中想想是可以實(shí)現(xiàn)的,接下來考慮一些細(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)其功能。
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í)候來說明。
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ù)一行?
好在RecyclerView里面為我們提供了一些方法。
在我們的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