本文已授權微信公眾號:鴻洋(hongyangAndroid)在微信公眾號平臺原創首發。
轉載請標明出處:
http://blog.csdn.net/lmj623565791/article/details/51854533;
本文出自:【張鴻洋的博客】
RecyclerView通過其高度的可定制性深受大家的青睞,也有非常多的使用者開始對它進行封裝或者改造,從而滿足越來越多的需求。
如果你對RecyclerView不陌生的話,你一定遇到過這樣的情況,我想給RecyclerView加個headerView或者footerView,當你敲出.addHeaderView,你會發現并沒有添加頭部或者底部View的相關API。
那么本文主要的內容很明顯了,完成以下工作:
如何為RecyclerView添加HeaderView(支持多個)
如何為RecyclerView添加FooterView(支持多個)
如何讓HeaderView或者FooterView適配各種LayoutManager
恩,其實本來我是想偷個懶的,因為Loader寫過一篇類似的文章,文章見文末參考鏈接。但是我發現被別的公眾號推送了~~
那我只能考慮自己換種思路來解決這個問題,并且提供盡可能多的功能了~
本文首發于我的公眾號,歡迎掃碼關注(二維碼見左側欄)。
對于添加headerView或者footerView的思路
其實HeaderView實際上也是Item的一種,只不過顯示在頂部的位置,那么我們完全可以通過為其設置ItemType來完成。
有了思路以后,我們心里就妥了,最起碼我們的內心中想想是可以實現的,接下來考慮一些細節。
假設我們現在已經完成了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基礎上去增強其功能。
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的時候來說明。
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獨立的占據一行?
好在RecyclerView里面為我們提供了一些方法。
在我們的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