RecyclerView添加header与footer
前言
這次主要關于RecyclerView添加header和footer的實現方法,我們都知道,在使用ListView的時候我們能自由的給自己的ListView添加頭部與尾部。使用addHeaderView()與addFooterView()方法實現。只要自己自定義布局文件,添加進去即可。但當我們使用RecyclerView的時候就會發現這兩個方便的方法沒有了。那么如果此時我們要實現這些功能時要怎么辦呢?不急,下面我們先來熟悉下ListView的添加header與footer的實現原理。
ListView源碼
既然ListView實現了添加頭部與尾部的原理,所以我們可以先進入ListView的源碼,查看其中的addHeaderView()方法與addFooterView()方法。
public void addHeaderView(View v, Object data, boolean isSelectable) {final FixedViewInfo info = new FixedViewInfo();info.view = v;info.data = data;info.isSelectable = isSelectable;mHeaderViewInfos.add(info);mAreAllItemsSelectable &= isSelectable;// Wrap the adapter if it wasn't already wrapped.if (mAdapter != null) {if (!(mAdapter instanceof HeaderViewListAdapter)) {mAdapter = new HeaderViewListAdapter(mHeaderViewInfos, mFooterViewInfos, mAdapter);}// In the case of re-adding a header view, or adding one later on,// we need to notify the observer.if (mDataSetObserver != null) {mDataSetObserver.onChanged();}}} 這里省略addFooterView()源碼,其實跟addHeaderView()基本一致通過addHeaderView()的源碼我們可以發現,它是使用一個FixedViewInfo類來存儲數據。
public class FixedViewInfo {/** The view to add to the list */public View view;/** The data backing the view. This is returned from {@link ListAdapter#getItem(int)}. */public Object data;/** <code>true</code> if the fixed view should be selectable in the list */public boolean isSelectable;}其中最主要的代碼就是
if (!(mAdapter instanceof HeaderViewListAdapter)) {mAdapter = new HeaderViewListAdapter(mHeaderViewInfos, mFooterViewInfos, mAdapter);}我們會發現它new了一個HeaderViewListAdapter類,那么我們再進入HeaderViewListAdapter源碼中,發現他就相對于一個我們自定義的Adapter對header與footer的封裝。
代碼都比較簡單,主要是思想,其中主要的是有
public int getCount() {if (mAdapter != null) {return getFootersCount() + getHeadersCount() + mAdapter.getCount();} else {return getFootersCount() + getHeadersCount();}}根據position來設置ViewType的值
public int getItemViewType(int position) {int numHeaders = getHeadersCount();if (mAdapter != null && position >= numHeaders) {int adjPosition = position - numHeaders;int adapterCount = mAdapter.getCount();if (adjPosition < adapterCount) {return mAdapter.getItemViewType(adjPosition);}}return AdapterView.ITEM_VIEW_TYPE_HEADER_OR_FOOTER;}再看getView()方法
public View getView(int position, View convertView, ViewGroup parent) {// Header (negative positions will throw an IndexOutOfBoundsException)int numHeaders = getHeadersCount();if (position < numHeaders) {return mHeaderViewInfos.get(position).view;}// Adapterfinal int adjPosition = position - numHeaders;int adapterCount = 0;if (mAdapter != null) {adapterCount = mAdapter.getCount();if (adjPosition < adapterCount) {return mAdapter.getView(adjPosition, convertView, parent);}}// Footer (off-limits positions will throw an IndexOutOfBoundsException)return mFooterViewInfos.get(adjPosition - adapterCount).view;}與getItemViewType類似,當position小于header的count時,此時返回頭部View,當position減去headeer的count時依然小于正常的adapter的count,此時返回正常的View,如果還有,自然剩下的就是footer了。
所以根據ListView的添加頭部與尾部的實現原理,我們可以模仿實現RecyclerView的相同的功能。
EnhanceRecyclerView
那么我們根據ListView來實現一個能夠添加header與footer的RecyclerView,我這里定義為EnhanceRecyclerView
FixedViewInfo
在上面我們看到ListView中使用到了FixedViewInfo,就是一個自定義的類,用來存儲數據,我們做適當的修改如下,View還是不變,依然是添加的視圖,將其余的刪掉,換成viewType。
public class FixedViewInfo {public View view;public int viewType;}addHeaderView與addFooterView
做相應的修改,其中BASE_HEADER_VIEW_TYPE與BASE_FOOTER_VIEW_TYPE是一個final的int數據
public void addHeaderView(View view) {FixedViewInfo info = new FixedViewInfo();info.view = view;info.viewType = BASE_HEADER_VIEW_TYPE + mHeaderViewInfos.size();mHeaderViewInfos.add(info);if (mAdapter != null) {mAdapter.notifyDataSetChanged();}} public void addFooterView(View view) {FixedViewInfo info = new FixedViewInfo();info.view = view;info.viewType = BASE_FOOTER_VIEW_TYPE + mFooterViewInfos.size();mFooterViewInfos.add(info);if (mAdapter != null) {mAdapter.notifyDataSetChanged();}}setAdapter
下面是setAdapter(),使用后面封裝的WrapperRecyclerViewAdapter
@Overridepublic void setAdapter(Adapter adapter) {if (!(adapter instanceof WrapperRecyclerViewAdapter))mAdapter = new WrapperRecyclerViewAdapter(mHeaderViewInfos, mFooterViewInfos, adapter);super.setAdapter(mAdapter);// if (isShouldSpan) { // ((WrapperRecyclerViewAdapter) mAdapter).adjustSpanSize(this); // }}WrapperRecyclerViewAdapter
我們也可以根據ListView的HeaderViewListAdapter來模仿實現一個封裝頭部與尾部的adapter。我這里定義為WrapperRecyclerViewAdapter繼承RecyclerView.Adapter根據情況適當修改。
getItemCount
@Overridepublic int getItemCount() {if (mAdapter != null) {return getHeadersCount() + getFootersCount() + mAdapter.getItemCount();} else {return getHeadersCount() + getFootersCount();}}getItemViewType
@Overridepublic int getItemViewType(int position) {int numHeaders = getHeadersCount();if (position < numHeaders) {return mHeaderViewInfos.get(position).viewType;}int adjPosition = position - numHeaders;int adapterPosition = 0;if (mAdapter != null) {adapterPosition = mAdapter.getItemCount();if (adjPosition < adapterPosition) {return mAdapter.getItemViewType(adjPosition);}}return mFooterViewInfos.get(position - adapterPosition - getHeadersCount()).viewType;}這里邏輯與ListView的基本一致。
onCreateViewHolder
這里需將HeaderViewListAdapter中的getView()方法分成兩部分來實現。
@Overridepublic RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {if (viewType >= EnhanceRecyclerView.BASE_HEADER_VIEW_TYPE && viewType < EnhanceRecyclerView.BASE_HEADER_VIEW_TYPE + getHeadersCount()) {View view = mHeaderViewInfos.get(viewType - EnhanceRecyclerView.BASE_HEADER_VIEW_TYPE).view;return viewHolder(view);} else if (viewType >= EnhanceRecyclerView.BASE_FOOTER_VIEW_TYPE && viewType < EnhanceRecyclerView.BASE_FOOTER_VIEW_TYPE + getFootersCount()) {View view = mFooterViewInfos.get(viewType - EnhanceRecyclerView.BASE_FOOTER_VIEW_TYPE).view;return viewHolder(view);}return mAdapter.onCreateViewHolder(parent, viewType);}根據不同的viewType來實現不同的布局文件.不是header與footer布局時則直接回調原始adapter的onCreateViewHolder()方法。實現正常的布局。
onBindViewHolder
@Overridepublic void onBindViewHolder(RecyclerView.ViewHolder holder, int position) {int numHeaders = getHeadersCount();if (position < numHeaders) {return;}int adjPosition = position - numHeaders;int adapterCount = 0;if (mAdapter != null) {adapterCount = mAdapter.getItemCount();if (adjPosition < adapterCount) {mAdapter.onBindViewHolder(holder, adjPosition);return;}}}根據position的位置來選擇,當position屬于正常的位置范圍之內時,則回調原始的adapter的onBindViewHolder()方法,實現數據的綁定;對于其它的情況,無需做處理。
主要的模仿變動就是這些,然后自己在根據情況進行適當的修改,最后在布局文件中使用自定義的EnhanceRecyclerView替代RecyclerView調整
我們都知道在使用RecyclerView的時候要設置LayoutManager,如果按照上面的實現,當LayoutManager設置為LinearLayout時,自然沒上面問題,頭部與尾部能正常添加;但當我們的LayoutManager設置為GridLayoutManager與StaggeredGridLayoutManager時,我們會發現頭部與尾部的添加出現異常,就是他們不能獨自占一行,所以這里我們要做相應的調整,使他們在添加頭部與尾部的時候獨自占一行。
adjustSpanSize
在上面我們自定義的WrapperRecyclerViewAdapter中添加adjustSpanSize()方法,用來實現調整。
public void adjustSpanSize(RecyclerView recyclerView) {if (recyclerView.getLayoutManager() instanceof GridLayoutManager) {final GridLayoutManager manager = (GridLayoutManager) recyclerView.getLayoutManager();manager.setSpanSizeLookup(new GridLayoutManager.SpanSizeLookup() {@Overridepublic int getSpanSize(int position) {int numHeaders = getHeadersCount();int adjPosition = position - numHeaders;if (position < numHeaders || adjPosition >= mAdapter.getItemCount())return manager.getSpanCount();return 1;}});}if (recyclerView.getLayoutManager() instanceof StaggeredGridLayoutManager) {isStaggered = true;}}可以看出當LayoutManager為GridLayoutManager時,我們通過manager來設置setSpanSizeLookup()在getSpanSize()方法中根據具體判斷來調用 manager.getSpanCount()來實現頭部與尾部的占一行的效果;對于StaggeredGridLayoutManager因為其沒有setSpanSizeLookup()方法,所以我們先做標記,在ViewHolder中為每一個需要的ItemView來設置params,setFullSpa(true)方法來填充一行。
private RecyclerView.ViewHolder viewHolder(View itemView) {if (isStaggered) {StaggeredGridLayoutManager.LayoutParams params = new StaggeredGridLayoutManager.LayoutParams(StaggeredGridLayoutManager.LayoutParams.MATCH_PARENT,StaggeredGridLayoutManager.LayoutParams.WRAP_CONTENT);params.setFullSpan(true);itemView.setLayoutParams(params);}return new RecyclerView.ViewHolder(itemView) {};}setLayoutManager
在EnhanceRecyclerView中重寫setLayoutManager()方法,添加標記。
@Overridepublic void setLayoutManager(LayoutManager layout) {if (layout instanceof GridLayoutManager || layout instanceof StaggeredGridLayoutManager)isShouldSpan = true;super.setLayoutManager(layout);}最后再 在EnhanceRecyclerView的setAdapter()方法中根據標記來判斷是否調用adjustSpanSize()方法來進行調整header與footer.
if (isShouldSpan) {((WrapperRecyclerViewAdapter) mAdapter).adjustSpanSize(this);}其實就是前面setAdapter()中所注釋的代碼。
這樣整個調整部分就基本完成。header與footer的添加也就基本完成了。
總結
通過RecyclerView的頭部與尾部的添加,我們應該能學習如何運用原有的資源來實現我們所需要的功能。雖然方法不一定是最好的,因為使用這種方法實現也存在一些問題,例如不能在初始化(onCreate)中同時添加header與footer,否則會出現布局混亂的現象,現在還不知道為何有這種特例,希望知道的可以告知解決下,但這種模仿的思想還是很值得推薦的。
個人blog地址:https://idisfkj.github.io/arc...
關注
與50位技術專家面對面20年技術見證,附贈技術全景圖
總結
以上是生活随笔為你收集整理的RecyclerView添加header与footer的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: WebDriver原理分析
- 下一篇: Runtime-b