android 打造真正的下拉重新整理上拉載入recyclerview(二):新增刪除頭尾部
前言
在上一篇文章中,我們介紹了下拉重新整理上拉載入RecyclerView的使用,從這篇開始,我將對這個專案的具體實現詳細介紹,這篇首先介紹新增刪除頭尾部的實現。
大家都知道recyclerview並不能像listview一樣,可以直接使用addHeaderView和addFooterView新增頭部和尾部,而在實際的專案中,我們常常需要實現這些功能。既然recyclerview不提供,那我們就自己寫一個唄。
但是啊,新增有了,刪除頭尾部的介紹網上比較少。看過鴻洋大神這篇文章的同學想必都知道,給recyclerview新增頭尾部,其實就是 在adapter中使用鍵值陣列對儲存頭部和尾部
這時候,有同學把網上的程式碼拷下來,開始在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 SparseArrayCompat<View> mHeaderViews = new SparseArrayCompat<>();
protected SparseArrayCompat<View> mFooterViews = new SparseArrayCompat<>();
- 1
- 2
然後,getItemCount方法,返回item數量+頭尾部總數:
@Override
public int getItemCount() {
return getRealItemCount()+getHeadersCount()+getFootersCount();
}
- 1
- 2
- 3
- 4
再然後,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());
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
再再然後,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);
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
再再再然後,onBindViewHolder方法:
@Override
public void onBindViewHolder(RecyclerView.ViewHolder holder, int position) {
if (isHeaderPosition(position) || isFooterPosition(position)) {
} else {
mRealAdapter.onBindViewHolder(holder, realPosition);
}
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
再再再再然後,新增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();
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
注意:這裡如果你使用了網上的程式碼,比如鴻洋大神的:
private static final int BASE_ITEM_TYPE_HEADER = 100000; public void addHeaderView(View view) { mHeaderViews.put(mHeaderViews.size() + BASE_ITEM_TYPE_HEADER, view); }
- 1
- 2
- 3
- 4
- 5
- 6
如果你是這樣寫的,那就會造成前言部分說了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();
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
恩,這下應該沒問題了。
等等!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);
}
}
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
搞定~
好了,再看下效果圖吧:
當然,我們要做的是封裝一個可以新增刪除頭尾部的RecyclerView,這算哪門子的封裝啊?我們當然要把這些個方法封裝好一點,使用起來也爽嘛。
一提起封裝,我腦子裡就冒出個東西:泛型。
泛型真是個好東西啊,那個靈活,那個爽,嘖嘖嘖,嘿嘿……好了,關於泛型這裡就不詳細介紹了,我們還是來說下封裝吧。(一本正經臉)
在開始封裝之前,先問問自己:“如果是你使用,你想怎麼用?”
“那還用說?當然是還和原來一樣setAdapter,像listview一樣直接用addHeaderView啊!”
“恩,沒問題,開始吧”
- 繼承RecyclerView,重寫setAdapter方法,新增addHeaderView等方法;
- 使用者設定的adapter,作為真正的adapter(mRealAdapter)傳入頭尾部Adapter;
先看看第一步:
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);
}
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
- 34
- 35
- 36
- 37
- 38
- 39
- 40
- 41
- 42
- 43
- 44
- 45
- 46
- 47
- 48
- 49
- 50
- 51
- 52
- 53
- 54
- 55
完美,再看看第二步,使用泛型就好啦~
public class HeaderAndFooterAdapter<T extends RecyclerView.Adapter> extends 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;
}
//其他方法上面已介紹,這裡就省略了。
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
好了,這樣就可以了。使用的時候,你還是按照你原來的方式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);
}
});
}
}
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
這樣就可以了,點選和長按就加上去了,接下來跟addHeaderView一樣,加幾個方法用來呼叫就可以了。
在adapter中加入:
protected OnItemClickListener mOnItemClickListener;
protected OnItemLongClickListener mOnItemLongClickListener;
public void setOnItemClickListener(OnItemClickListener onItemClickListener) {
this.mOnItemClickListener = onItemClickListener;
}
public void setOnItemLongClickListener(OnItemLongClickListener onItemLongClickListener) {
this.mOnItemLongClickListener = onItemLongClickListener;
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
在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);
}
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
搞定,至此,PTLRecyclerView的頭尾部相關就介紹完了,其實PTLRecyclerView的HeaderAndFooterRecyclerView中還封裝了EmptyView的實現,這裡就不介紹了,有興趣的同學可以去看看原始碼。
有意見或建議或疑問等等,歡迎提出~~