針對RecyclerView的頭部和底部,官方並沒有給我們提供像listView一樣可以直接通過addHeaderView()/addFooterView()的方法,所以只能靠我們自己去實現了,那怎麼實現呢?大家都知道RecyclerView已經為我們封裝好了Adapter和ViewHolder,在... ...
上一節我們講到了 Android 5.X新特性之RecyclerView基本解析及無限復用 相信大家也應該熟悉了RecyclerView的基本使用,這一節我們來學習下,為RecyclerView添加HeaderView和FooterView。
針對RecyclerView的頭部和底部,官方並沒有給我們提供像listView一樣可以直接通過addHeaderView()/addFooterView()的方法,所以只能靠我們自己去實現了,那怎麼實現呢?大家都知道RecyclerView已經為我們封裝好了Adapter和ViewHolder,在Adapter中我們需要重寫onCreateViewHolder(ViewGroup parent, int viewType)這個方法方便我們把ItemView佈局文件或是自定義View傳遞到ViewHolder中,從而達到ItemView的重覆使用和回收等。而我們今天要講的添加頭部和底部和該方法有密不可分的關係。
大家仔細觀察onCreateViewHolder(ViewGroup parent, int viewType)這個方法,在它的參數中,含有一個viewType,它就代表了每一個子列表中的ItemView的類型,而該類型我們又可以通過Adapter中封裝好的getItemViewType()方法來定義。因此,我們可以根據這兩個方法來完成我們今天的學習。
首先,我們也需要在BaseRecyclerAdapter中添加一個addHeaderView的方法,用於接受Activity中傳遞過來的HeaderView,並且把HeaderView添加到第0個ItemView中。
private View mHeaderView;
public void addHeaderView(View headerView){
mHeaderView = headerView;
notifyItemInserted(0);
}
然後,我們在我們自定義的BaseRecyclerAdapter中重寫getItemViewType()方法,並且定義兩個靜態變數來區分我們的viewType的類型,如下:
public final static int TYPE_HEADER = 0;
public final static int TYPE_BODY = 1;
@Override
public int getItemViewType(int position) {
return super.getItemViewType(position);
}
ok,現在我們可以針對getItemViewType()方法來為我們的ItemView設置viewType類型了。我們知道,getItemViewType()預設返回的是0這個類型,所以我們重載該方法,在沒有mHeaderView時,我們讓它返回TYPE_BODY這個類型,而當有mHeaderView,由於把它添加到第0個ItemView中了,所以我們可以根據position等於0的時候讓它返回TYPE_HEADER這個類型。定義好的類型將會在onCreateViewHolder()中使用到。
所以我們的getItemViewType方法可以這樣設計:
@Override
public int getItemViewType(int position) {
if(mHeaderView == null)
return TYPE_BODY;
if(position == 0) {
return TYPE_HEADER;
}
return TYPE_BODY;
}
到這裡大家已經很明確的知道了每個ItemView的viewType類型了,那麼我們就可以在onCreateViewHolder()方法中根據ItemView的viewType來做出不同的判斷了,如下:
@Override
public BaseViewHolderHelper onCreateViewHolder(ViewGroup parent, int viewType) {
if(viewType == TYPE_HEADER && mHeaderView != null){
return new BaseViewHolderHelper(mHeaderView);
}
View view = LayoutInflater.from(parent.getContext()).inflate(mLayoutResId, parent, false);
return new BaseViewHolderHelper(view);
}
代碼一目瞭然,就是根據viewType判斷是否為TYPE_HEADER,如果是,則添加不同的佈局或View,否則,載入正常的佈局,其他的不變。
然後,我們就可以在onBindViewHolder(BaseViewHolderHelper holder, int position)方法中根據當前的position位置來綁定我們要顯示數據了。
@Override
public void onBindViewHolder(BaseViewHolderHelper holder, int position) {
if(getItemViewType(position) == TYPE_HEADER){
return;
}else{
if(mHeaderView != null){
position-- ;
}
holder.itemView.setTag(position);
holder.itemView.setOnClickListener(this);
holder.itemView.setOnLongClickListener(this);
T itemData = mDatas.get(position);
displayContents(holder,itemData);
}
}
代碼解釋:如果當前position位置的類型是TYPE_HEADER,也就是說用來顯示mHeaderView的,這裡我們就直接返回mHeaderView的佈局,不做事件處理了;如果不是,並且RecyclerView是有帶mHeaderView頭部的,那麼由於它占去第0個itemView,所以我們的position是從第一個開始計算的,所以我們必須得到當前真實position位置,並通過position位置來獲取當前的真實數據,如果不帶mHeaderView頭部,則可直接根據position獲取顯示數據,其他的邏輯不變。
還有註意的是當mHeaderView不為空時,我們的數據量大小也有一定的變化,請看:
@Override
public int getItemCount() {
return mHeaderView != null ? mDatas.size() + 1 : mDatas.size();
}
ok,在RecycerActivity的onCreate方法中添加一下兩句:
View headerView = LayoutInflater.from(this).inflate(R.layout.item_view1, null);
mBaseRecyclerAdapter.addHeaderView(headerView);
來看看運行結果吧
ok,已經完成了mHeaderView 的添加。但是有個小問題,當你把RecyclerView的佈局設置為GridLayoutManager時,如:mRecyclerView.setLayoutManager(new GridLayoutManager(this,2));就會出現這種情況:
這種情況也很好解決,在GridLayoutManager中我們可以在SpanSizeLookup中重新設置顯示的列數。
@Override
public void onAttachedToRecyclerView(RecyclerView recyclerView) {
super.onAttachedToRecyclerView(recyclerView);
RecyclerView.LayoutManager manager = recyclerView.getLayoutManager();
if(manager instanceof GridLayoutManager){
final GridLayoutManager gridLayoutManager = ((GridLayoutManager) manager);
gridLayoutManager.setSpanSizeLookup(new GridLayoutManager.SpanSizeLookup() {
@Override
public int getSpanSize(int position) {
//是HeaderView則占所有列,否則只占自己列
return getItemViewType(position) == TYPE_HEADER ? gridLayoutManager.getSpanCount() : 1;
}
});
}
}
最主要的就是在getSpanSize方法中根據我們的需要重新設置就ok了。看看吧
好了,mHeaderView 已基本搞定,現在來看看怎麼添加FooterView了,其實原理是一樣的,也是根據getItemViewType()返回的ViewType類型來載入不同的佈局了。
首先我們需要定義一個內部類FooterViewHolder繼承我們的BaseViewHolderHelper,它主要是用來綁定FooterView佈局文件:
private class FooterViewHolder extends BaseViewHolderHelper{
private TextView footView;
public FooterViewHolder(View itemView) {
super(itemView);
footView = (TextView) itemView.findViewById(R.id.tv_addFooter);
}
}
然後在getItemViewType中獲取到最後的ItemView的位置並返回TYPE_FOOTER類型:
@Override
private View mFooterView;
public final static int TYPE_FOOTER = 2;
......
public int getItemViewType(int position) {
if(position + 1 == getItemCount()){
return TYPE_FOOTER;
}
if(mHeaderView == null)
return TYPE_BODY;
if(position == 0) {
return TYPE_HEADER;
}
return TYPE_BODY;
}
另外在getItemCount()方法中我們因為添加個一個FooterView所以需要在原來的基礎上再加 1 ;
@Override
public int getItemCount() {
return mHeaderView != null ? mDatas.size() + 2 : mDatas.size() + 1;
}
再次在onCreateViewHolder方法中根據類型載入不同的佈局文件:
@Override
public BaseViewHolderHelper onCreateViewHolder(ViewGroup parent, int viewType) {
...
if(viewType == TYPE_FOOTER){
mFooterView = LayoutInflater.from(mContext).inflate(R.layout.custom_footerview, parent,false);
return new FooterViewHolder(mFooterView);
}
View view = LayoutInflater.from(parent.getContext()).inflate(mLayoutResId, parent, false);
return new BaseViewHolderHelper(view);
}
最後在onBindViewHolder方法中來展示我們的數據吧
@Override
public void onBindViewHolder(BaseViewHolderHelper holder, int position) {
if(getItemViewType(position) == TYPE_HEADER){
return;
}else if(getItemViewType(position) == TYPE_FOOTER){
FooterViewHolder footViewHolder=(FooterViewHolder)holder;
footViewHolder.footView.setText("上拉載入更多...");
} else{
...
}
}
ok,完成,來看看結果吧
總結下,在給RecyclerView添加HeaderView和FooterView時,只要利用好getItemViewType這個方法,返回相對應的ViewType,並且在onCreateViewHolder方法中根據ViewType類型載入不同的佈局就完全可是實現我們的需求了。說起來就是這麼簡單。好了,今天就講到這裡吧,祝大家學習愉快。
更多資訊請關註微信平臺,有博客更新會及時通知。愛學習愛技術。