編輯:關於Android編程
在上一篇文章中,我們介紹了下拉刷新上拉加載RecyclerView的使用,從這篇開始,我將對這個項目的具體實現詳細介紹,這篇首先介紹添加刪除頭尾部的實現。
大家都知道recyclerview並不能像listview一樣,可以直接使用addHeaderView和addFooterView添加頭部和尾部,而在實際的項目中,我們常常需要實現這些功能。既然recyclerview不提供,那我們就自己寫一個呗。
但是啊,添加有了,刪除頭尾部的介紹網上比較少。看過鴻洋大神這篇文章的同學想必都知道,給recyclerview添加頭尾部,其實就是 在adapter中使用鍵值數組對存儲頭部和尾部,addHeaderView其實就是往頭部的鍵值對數組中添加view。那麼removeHeaderView呢?當然就是從頭部的鍵值對數組中刪除view啦!!沒錯,就是這麼簡單。
這時候,有同學把網上的代碼拷下來,開始在adapter中添加removeHeaderView方法,恩,完美。添加View0,添加View1,刪除View1,哦!刪除成功,添加View2,哎?怎麼添加的是View1??我的View2呢?
不要問我怎麼知道的,因為我就這樣干過。
那麼我們還是先一步步來介紹下頭尾部的實現吧,recyclerview的adapter需要實現以下幾個方法:
創建ViewHolder:onCreateViewHolder(ViewGroup parent, int viewType); 綁定數據:onBindViewHolder(RecyclerView.ViewHolder holder, int position); 獲得item的總數:getItemCount();恩,跟ListView還是有點不一樣的。在ListView的時候,當我們需要展示幾種類型的item時,我們會使用到一個方法:
獲得item的類型:getItemViewType(int position);幸運的是,recyclerview中也提供這個方法,有這個方法就好辦了,我們把頭尾部當成是一種item不就好了嘛,用鍵值對數組保存頭尾部,類型type作為key,view作為value。
因此思路就是:
使用鍵值對數組(ArrayList、HashMap、SparseArrayCompat等)保存頭尾部 在getItemViewType方法中,判斷是否頭尾部,是的話返回對應的key,也就是type 在onCreateViewHolder中,判斷是否是頭尾部,是的話返回對應的ViewHolder 在onBindViewHolder中,判斷是否是頭尾部,是的話不處理大概就是這樣,下面我們看看這部分代碼,一個一個來:
首先,鍵值對保存頭尾部:
protected SparseArrayCompatmHeaderViews = new SparseArrayCompat<>(); protected SparseArrayCompat mFooterViews = new SparseArrayCompat<>();
然後,getItemCount方法,返回item數量+頭尾部總數:
@Override public int getItemCount() { return getRealItemCount()+getHeadersCount()+getFootersCount(); }
再然後,getItemViewType方法:
@Override public int getItemViewType(int position) { //如果是頭部,返回對應的type if (isHeaderPosition(position)) { return mHeaderViews.keyAt(position); } //如果是尾部,返回對應的type if (isFooterPosition(position)) { return mFooterViews.keyAt(position - getHeadersCount() - getRealItemCount()); } //如果是item,返回真正的adapter的getItemViewType方法 return mRealAdapter.getItemViewType(position - getHeadersCount()); }
再再然後,onCreateViewHolder方法:
@Override public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) { // 如果是頭部 if (isHeaderType(viewType)) { int headerPosition = mHeaderViews.indexOfKey(viewType); View headerView = mHeaderViews.valueAt(headerPosition); return createHeaderAndFooterViewHolder(headerView); } // 如果是尾部 if (isFooterType(viewType)) { int footerPosition = mFooterViews.indexOfKey(viewType); View footerView = mFooterViews.valueAt(footerPosition); return createHeaderAndFooterViewHolder(footerView); } return mRealAdapter.onCreateViewHolder(parent, viewType); }
再再再然後,onBindViewHolder方法:
@Override public void onBindViewHolder(RecyclerView.ViewHolder holder, int position) { if (isHeaderPosition(position) || isFooterPosition(position)) { } else { mRealAdapter.onBindViewHolder(holder, realPosition); } }
再再再再然後,添加addHeaderView和addFooterView方法:
public void addHeaderView(View view) { mHeaderViews.put(BASE_ITEM_TYPE_HEADER++, view); notifyDataSetChanged(); } public void addFooterView(View view) { mFooterViews.put(BASE_ITEM_TYPE_FOOTER ++,view); notifyDataSetChanged(); }
注意:這裡如果你使用了網上的代碼,比如鴻洋大神的:
private static final int BASE_ITEM_TYPE_HEADER = 100000; public void addHeaderView(View view) { mHeaderViews.put(mHeaderViews.size() + BASE_ITEM_TYPE_HEADER, view); }如果你是這樣寫的,那就會造成前言部分說了View2沒有添加成功的問題了。
那麼是為什麼呢?我們來推演一番:
添加View0,type是100000; 添加View1,type是100001; 刪除View1; 添加View2,type是100001;啊哦,getItemViewType拿到的type還是原來view1的type,那自然View2會變成View1了。那怎麼辦呢?我們用自增不就好了嘛。
恩,接著上面,繼續加上刪除頭尾部的方法:
public void removeHeaderView(View view) { int index = mHeaderViews.indexOfValue(view); if (index < 0) return; mHeaderViews.removeAt(index); notifyDataSetChanged(); } public void removeFooterView(View view) { int index = mFooterViews.indexOfValue(view); if (index < 0) return; mFooterViews.removeAt(index); notifyDataSetChanged(); }
恩,這下應該沒問題了。
等等!LinearLayoutManager是沒問題了,GridLayoutManager和StaggeredGridLayoutManager呢?其實想想就知道,肯定會有問題的嘛~~問題就是頭部和尾部的寬度會和item一樣長,而不是我們希望的填滿recyclerview的寬度,每個頭尾部單獨占一行。
當然解決辦法鴻洋大大也說過了,網上也有一大堆:
/**解決GridLayoutManager問題*/ @Override public void onAttachedToRecyclerView(RecyclerView recyclerView) { mRealAdapter.onAttachedToRecyclerView(recyclerView); RecyclerView.LayoutManager layoutManager = recyclerView.getLayoutManager(); if (layoutManager instanceof GridLayoutManager) { final GridLayoutManager gridLayoutManager = (GridLayoutManager) layoutManager; gridLayoutManager.setSpanSizeLookup(new GridLayoutManager.SpanSizeLookup() { @Override public int getSpanSize(int position) { if (isHeaderPosition(position) || isFooterPosition(position)) return gridLayoutManager.getSpanCount(); return 1; } }); } } /**解決瀑布流布局問題*/ @Override public void onViewAttachedToWindow(RecyclerView.ViewHolder holder) { mRealAdapter.onViewAttachedToWindow(holder); int position = holder.getLayoutPosition(); if (isHeaderPosition(position) || isFooterPosition(position)) { ViewGroup.LayoutParams lp = holder.itemView.getLayoutParams(); if (lp != null && lp instanceof StaggeredGridLayoutManager.LayoutParams) { StaggeredGridLayoutManager.LayoutParams layoutParams = (StaggeredGridLayoutManager.LayoutParams) lp; layoutParams.setFullSpan(true); } } }
搞定~
好了,再看下效果圖吧:
當然,我們要做的是封裝一個可以添加刪除頭尾部的RecyclerView,這算哪門子的封裝啊?我們當然要把這些個方法封裝好一點,使用起來也爽嘛。
一提起封裝,我腦子裡就冒出個東西:泛型。
泛型真是個好東西啊,那個靈活,那個爽,啧啧啧,嘿嘿……好了,關於泛型這裡就不詳細介紹了,我們還是來說下封裝吧。(一本正經臉)<喎?/kf/ware/vc/" target="_blank" class="keylink">vcD4NCjxibG9ja3F1b3RlPg0KCTxwPtTav6rKvLfi17DWrsewo6zPyM7KzsrX1Ly6o7ombGRxdW87yOe5+8rHxOPKudPDo6zE48/r1PXDtNPDo78mcmRxdW87PC9wPg0KCTxwPiZsZHF1bzvEx7u508PLtaO/tbHIu8rHu7m6zdStwLTSu9H5c2V0QWRhcHRlcqOsz/FsaXN0dmlld9K70fnWsb3T08NhZGRIZWFkZXJWaWV3sKGjoSZyZHF1bzs8L3A+DQoJPHA+JmxkcXVvO7b3o6zDu87KzOKjrL+qyrywySZyZHF1bzs8L3A+DQo8L2Jsb2NrcXVvdGU+DQq8zLPQUmVjeWNsZXJWaWV3o6zW2NC0c2V0QWRhcHRlcre9t6gszO2802FkZEhlYWRlclZpZXe1yLe9t6ijuyDTw7unyejWw7XEYWRhcHRlcqOs1/fOqtXm1f21xGFkYXB0ZXIobVJlYWxBZGFwdGVyKbSryOvNt86ysr9BZGFwdGVyo7sNCjxwPs/Iv7S/tLXa0ruyvTo8L3A+DQo8cHJlIGNsYXNzPQ=="brush:java;"> protected HeaderAndFooterAdapter mAdapter; protected Adapter mRealAdapter; @Override public void setAdapter(Adapter adapter) { mRealAdapter = adapter; if (adapter instanceof HeaderAndFooterAdapter) { mAdapter = (HeaderAndFooterAdapter) adapter; } else { mAdapter = new HeaderAndFooterAdapter(getContext(),adapter); } super.setAdapter(mAdapter); } public void addHeaderView(View view) { if (null == view) { throw new IllegalArgumentException("the view to add must not be null !"); } else if (mAdapter == null) { throw new IllegalStateException("u must set a adapter first !"); } else { mAdapter.addHeaderView(view); } } public void addFooterView(View view) { if (null == view) { throw new IllegalArgumentException("the view to add must not be null !"); } else if (mAdapter == null) { throw new IllegalStateException("u must set a adapter first !"); } else { mAdapter.addFooterView(view); } } public void removeHeaderView(View view) { if (null == view) { throw new IllegalArgumentException("the view to remove must not be null !"); } else if (mAdapter == null) { throw new IllegalStateException("u must set a adapter first !"); } else { mAdapter.removeHeaderView(view); } } public void removeFooterView(View view) { if (null == view) { throw new IllegalArgumentException("the view to remove must not be null !"); } else if (mAdapter == null) { throw new IllegalStateException("u must set a adapter first !"); } else { mAdapter.removeFooterView(view); } }
完美,再看看第二步,使用泛型就好啦~
public class HeaderAndFooterAdapterextends RecyclerView.Adapter { protected T mRealAdapter; protected Context mContext; public HeaderAndFooterAdapter(Context mContext, T mRealAdapter) { super(); this.mContext = mContext; this.mRealAdapter = mRealAdapter; } public T getRealAdapter() { return mRealAdapter; } //其他方法上面已介紹,這裡就省略了。 }
好了,這樣就可以了。使用的時候,你還是按照你原來的方式setAdapter,不用管其他,想addHeaderView就直接調HeaderAndFooterRecyclerView的addHeaderView方法就好啦,和ListView是一樣樣的。
本來添加點擊事件我不想寫了,因為網上太多太多例子了,但想到都說了這麼多了,再說一下也沒什麼啦。
其實添加點擊事件就是給每個item添加一個點擊監聽嘛,然後傳入position就夠了,注意:因為我們添加了頭部和尾部,position需要處理一下,不然到時候對用戶來說,你的position就是不准的。
@Override public void onBindViewHolder(RecyclerView.ViewHolder holder, int position) { if (isHeaderPosition(position) || isFooterPosition(position)) { } else { final int realPosition = position - getHeadersCount(); mRealAdapter.onBindViewHolder(holder, realPosition); if (mOnItemClickListener != null) { holder.itemView.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { mOnItemClickListener.OnItemClick(realPosition); } }); } if (mOnItemLongClickListener != null) { holder.itemView.setOnLongClickListener(new View.OnLongClickListener() { @Override public boolean onLongClick(View v) { return mOnItemLongClickListener.onItemLongClick(realPosition); } }); } } }
這樣就可以了,點擊和長按就加上去了,接下來跟addHeaderView一樣,加幾個方法用來調用就可以了。
在adapter中加入:
protected OnItemClickListener mOnItemClickListener; protected OnItemLongClickListener mOnItemLongClickListener; public void setOnItemClickListener(OnItemClickListener onItemClickListener) { this.mOnItemClickListener = onItemClickListener; } public void setOnItemLongClickListener(OnItemLongClickListener onItemLongClickListener) { this.mOnItemLongClickListener = onItemLongClickListener; }
在HeaderAndFooterRecyclerView中加入:
public void setOnItemClickListener(OnItemClickListener onItemClickListener) { if (null == mAdapter) { throw new IllegalStateException("u must set a adapter first !"); } else { mAdapter.setOnItemClickListener(onItemClickListener); } } public void setOnItemLongClickListener(OnItemLongClickListener onItemLongClickListener) { if (null == mAdapter) { throw new IllegalStateException("u must set a adapter first !"); } else { mAdapter.setOnItemLongClickListener(onItemLongClickListener); } }
搞定,至此,WZMRecyclerView的頭尾部相關就介紹完了,其實WZMRecyclerView的HeaderAndFooterRecyclerView中還封裝了EmptyView的實現,這裡就不介紹了,有興趣的同學可以去看看源碼。
源碼地址:https://github.com/whichname/WZMRecyclerView
有意見或建議或疑問等等,歡迎提出~~
微信群是我們經常聊天聊工作的地方,但是你需要離開的時候想要將這個微信群轉給一個人來管理該怎麼辦呢?那麼微信群怎麼轉讓群主呢?微信群怎麼轉讓給別人?微信群怎麼
一、關系型數據庫SQLIte 每個應用程序都要使用數據,Android應用程序也不例外,Android使用開源的、與操作系統無關的SQL數據庫&
android-library-publish-to-jcenter是一個幫助Android開發者將AAR庫發布到jcenter的項目,android-library-p
什麼叫Junit Junit是一個java單元測試框架 是 對程序進行白盒測試 一般來說要對一個方法進行測試其結果 可以寫一個main入口 然後調用其方法來進行測