前面已經說過RecyclerView的使用,RecyclerView通過其高度的可定制性深受大家的青睐,也有非常多的使用者開始對它進行封裝或者改造,進而滿足越來越多的需求,相信用過的一定會愛不釋手。
在實際開發中經常會遇到給清單添加标題這樣的需求,可是要給RecyclerView加個headerView或者footerView卻發現沒有這樣的方法,怎麼辦呢?網上搜到的大多數方案都是通過控制Adapter的ItemType來設定的,不管是添加headerView還是footerView,它們都是Item的一種,隻不過顯示在特定的位置,那麼我們完全可以通過為其設定ItemType來完成。是以核心就是根據不同的ItemType去加載不同的布局。
有了思路,我們就簡單實作下吧
擴充卡代碼如下:
public class HeaderAndFooterAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolder> {
private static final int ITEM_TYPE_HEADER = 1;
private static final int ITEM_TYPE_FOOTER = 2;
private Context mContext;
private List<String> mDatas = new ArrayList<>();
public HeaderAndFooterAdapter(Context mContext, List<String> mDatas) {
this.mDatas = mDatas;
this.mContext = mContext;
}
/*根據getItemViewType()方法傳回的不同類型建立不同的ViewHolder*/
@Override
public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
if (viewType == ITEM_TYPE_HEADER) {
View view = LayoutInflater.from(mContext).inflate(android.R.layout.simple_list_item_1, parent, false);
HeaderHolder viewHolder = new HeaderHolder(view);
return viewHolder;
} else if (viewType == ITEM_TYPE_FOOTER) {
View view = LayoutInflater.from(mContext).inflate(android.R.layout.simple_list_item_1, parent, false);
FootHolder viewHolder = new FootHolder(view);
return viewHolder;
} else {
View view = LayoutInflater.from(mContext).inflate(R.layout.item_recyclerview_main, parent, false);
ItemHolder viewHolder = new ItemHolder(view);
return viewHolder;
}
}
@Override
public void onBindViewHolder(RecyclerView.ViewHolder holder, int position) {
if (holder instanceof HeaderHolder) {
HeaderHolder headerHolder = (HeaderHolder) holder;
headerHolder.textViewHeader.setText(mDatas.get(position));
} else if (holder instanceof FootHolder) {
FootHolder footHolder = (FootHolder) holder;
footHolder.textViewFoot.setText(mDatas.get(position));
} else {
ItemHolder itemHolder = (ItemHolder) holder;
itemHolder.textViewItem.setText(mDatas.get(position));
}
}
@Override
public int getItemCount() {
return mDatas == null ? 0 : mDatas.size();
}
/*根據位置來傳回不同的item類型*/
@Override
public int getItemViewType(int position) {
if (position == 0) {
return ITEM_TYPE_HEADER;
} else if (position + 1 == getItemCount()) {
return ITEM_TYPE_FOOTER;
} else
return 0;
}
/*頭部Item*/
class HeaderHolder extends RecyclerView.ViewHolder {
public TextView textViewHeader;
public HeaderHolder(View itemView) {
super(itemView);
textViewHeader = (TextView) itemView.findViewById(android.R.id.text1);
}
}
/*底部Item*/
class FootHolder extends RecyclerView.ViewHolder {
public TextView textViewFoot;
public FootHolder(View itemView) {
super(itemView);
textViewFoot = (TextView) itemView.findViewById(android.R.id.text1);
}
}
class ItemHolder extends RecyclerView.ViewHolder {
public TextView textViewItem;
public ItemHolder(View itemView) {
super(itemView);
textViewItem = (TextView) itemView.findViewById(R.id.tv_main_item);
}
}
}
-
getItemViewType
由于我們增加了headerView和footerView首先需要複寫的就是getItemCount和getItemViewType。getItemCount很好了解,對于getItemType,傳回的就是item所代表的type值。
-
onCreateViewHolder
可以看到,我們分别判斷viewType,如果是headerView或者是footView,我們則為其單獨建立ViewHolder。
-
onBindViewHolder
onBindViewHolder比較簡單,隻要根據不同的ViewHolder來進行綁定資料即可。
Activity類如下:
public class MainActivity extends AppCompatActivity {
private RecyclerView mRecyclerView;
private List<String> mDatas = new ArrayList<>();
private HeaderAndFooterAdapter mAdapter;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
mRecyclerView = (RecyclerView) findViewById(R.id.ryv_main_content);
LinearLayoutManager linearLayoutManager = new LinearLayoutManager(this);
mRecyclerView.setLayoutManager(linearLayoutManager);
mAdapter=new HeaderAndFooterAdapter(this,mDatas);
mRecyclerView.setAdapter(mAdapter);
initData();
mAdapter.notifyDataSetChanged();
}
private void initData() {
mDatas.add("清單頭");
for (int i=0;i<20;i++){
mDatas.add("item"+i);
}
mDatas.add("清單尾");
}
}
效果如下:
好了,現在問題來了,假設我們現在已經完成了RecyclerView的編寫,忽然有個需求,需要在清單上加個HeaderView,此時我們該怎麼辦呢?打開我們的Adapter,然後按照我們上述的原理,添加特殊的ViewType,然後修改代碼完成。這是比較正常的做法了,但是有個問題是,如果需要添加多個viewType,那麼可能我們的Adapter需要修改的幅度就比較大了,比如getItemType、getItemCount、onBindViewHolder、onCreateViewHolder等,幾乎所有的方法都要進行改變。如果一個項目中多個RecyclerView都需要在其清單中添加headerView,想想都頭大了,是以直接改Adapter的代碼是非常不劃算的,最好能夠設計一個類,可以無縫的為原有的Adapter添加headerView和footerView。我也是看到了鴻洋大神的這篇文章才有了收獲,思路是通過類似裝飾者模式,去設計一個類,增強原有Adapter的功能,使其支援addHeaderView和addFooterView。這樣我們就可以不去改動我們之前已經完成的代碼,靈活的去擴充功能了。幸好大學的課本沒丢,又翻出出來,重新看看裝飾者模式,哈哈~~
初步實作
裝飾類
public class HeaderAndFooterWrapper<T> extends RecyclerView.Adapter<RecyclerView.ViewHolder> {
private static final int BASE_ITEM_TYPE_HEADER = 0x10;
private static final int BASE_ITEM_TYPE_FOOTER = 0x20;
private SparseArrayCompat<View> mHeaderViews = new SparseArrayCompat<>();
private SparseArrayCompat<View> mFootViews = new SparseArrayCompat<>();
private RecyclerView.Adapter mInnerAdapter;
public HeaderAndFooterWrapper(RecyclerView.Adapter adapter) {
mInnerAdapter = adapter;
}
@Override
public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
if (mHeaderViews.get(viewType) != null) {
ViewHolder holder = ViewHolder.createViewHolder(parent.getContext(), mHeaderViews.get(viewType));
return holder;
} else if (mFootViews.get(viewType) != null) {
ViewHolder holder = ViewHolder.createViewHolder(parent.getContext(), mFootViews.get(viewType));
return holder;
}
return mInnerAdapter.onCreateViewHolder(parent, viewType);
}
@Override
public int getItemViewType(int position) {
if (isHeaderViewPos(position)) {
return mHeaderViews.keyAt(position);
} else if (isFooterViewPos(position)) {
return mFootViews.keyAt(position - getHeadersCount() - getRealItemCount());
}
return mInnerAdapter.getItemViewType(position - getHeadersCount());
}
private int getRealItemCount() {
return mInnerAdapter.getItemCount();
}
@Override
public void onBindViewHolder(RecyclerView.ViewHolder holder, int position) {
if (isHeaderViewPos(position)) {
return;
}
if (isFooterViewPos(position)) {
return;
}
mInnerAdapter.onBindViewHolder(holder, position - getHeadersCount());
}
@Override
public int getItemCount() {
return getHeadersCount() + getFootersCount() + getRealItemCount();
}
private boolean isHeaderViewPos(int position) {
return position < getHeadersCount();
}
private boolean isFooterViewPos(int position) {
return position >= getHeadersCount() + getRealItemCount();
}
public void addHeaderView(View view) {
mHeaderViews.put(mHeaderViews.size() + BASE_ITEM_TYPE_HEADER, view);
}
public void addFootView(View view) {
mFootViews.put(mFootViews.size() + BASE_ITEM_TYPE_FOOTER, view);
}
public int getHeadersCount() {
return mHeaderViews.size();
}
public int getFootersCount() {
return mFootViews.size();
}
}
注意這幾個方法:
- 可以看到我們的傳回值是mHeaderViews.keyAt(position),這個值其實就是我們 addHeaderView時的key,footerView是一樣的處理方式,也就是說每一個headerView都會建立不同的itemType。
- 可以看到,我們分别判斷viewType,如果是HeaderView或者是FooterView,我們則為其單獨建立ViewHolder,這裡的 ViewHolder我就直接拿鴻洋大神的用了,大家也可以自己寫,隻需要将對應的 headerView作為itemView傳入ViewHolder的構造即可。而這裡也就展現出SparseArrayCompat的作用了,對于headerView假設我們有多個,那麼onCreateViewHolder傳回的ViewHolder中的itemView應該對應不同的headerView,如果是List,那麼不同的headerView應該對應着:list.get(0),list.get(1)等。但是onCreateViewHolder方法并沒有position參數,隻有itemType參數,如果itemType還是固定的一個值,那麼你是沒有辦法根據參數得到不同的headerView的。
是以,我利用SparseArrayCompat,将其itemType作為key,value為我們的headerView,在 onCreateViewHolder中,直接通過itemType,即可獲得我們的headerView,然後構造ViewHolder對象。
效果:
效果差不多,我們再來看看GridLayoutMananger的效果:
雖然效果不是我們想要的,但邏輯是正确的,好了,現在改下代碼吧
針對GridLayoutManager
在HeaderAndFooterWrapper類中複寫onAttachedToRecyclerView方法
@Override
public void onAttachedToRecyclerView(RecyclerView recyclerView) {
mInnerAdapter.onAttachedToRecyclerView(recyclerView);
RecyclerView.LayoutManager layoutManager = recyclerView.getLayoutManager();
if (layoutManager instanceof GridLayoutManager) {
final GridLayoutManager gridLayoutManager = (GridLayoutManager) layoutManager;
final GridLayoutManager.SpanSizeLookup spanSizeLookup = gridLayoutManager.getSpanSizeLookup();
gridLayoutManager.setSpanSizeLookup(new GridLayoutManager.SpanSizeLookup() {
@Override
public int getSpanSize(int position) {
int viewType = getItemViewType(position);
if (mHeaderViews.get(viewType) != null) {
return gridLayoutManager.getSpanCount();
} else if (mFootViews.get(viewType) != null) {
return gridLayoutManager.getSpanCount();
}
if (spanSizeLookup != null)
return spanSizeLookup.getSpanSize(position);
return 1;
}
});
gridLayoutManager.setSpanCount(gridLayoutManager.getSpanCount());
}
}
現在看一下運作效果:
對于StaggeredGridLayoutManager
當然了,我們怎麼能忘了最重要的StaggeredGridLayoutManager呢 ?StaggeredGridLayoutManager并沒有setSpanSizeLookup這樣的方法,但是處理依然不複雜,重寫onViewAttachedToWindow方法,如下:
@Override
public void onViewAttachedToWindow(RecyclerView.ViewHolder holder) {
mInnerAdapter.onViewAttachedToWindow(holder);
int position = holder.getLayoutPosition();
if (isHeaderViewPos(position) || isFooterViewPos(position)) {
ViewGroup.LayoutParams lp = holder.itemView.getLayoutParams();
if (lp != null
&& lp instanceof StaggeredGridLayoutManager.LayoutParams) {
StaggeredGridLayoutManager.LayoutParams p =
(StaggeredGridLayoutManager.LayoutParams) lp;
p.setFullSpan(true);
}
}
}
哦了,就是這樣
源代碼參考:
http://blog.csdn.net/lmj623565791/article/details/51854533