version:2.8.5
更多分享請(qǐng)看:http://cherylgood.cn
今天我們來學(xué)習(xí)下BaseRecyclerViewAdapterHelpler開源項(xiàng)目中是如何實(shí)現(xiàn)分組想過的。
首先今天的學(xué)習(xí)我們還是按照前面的學(xué)習(xí)思路,根據(jù)getItemViewType->onCreateDefViewHolder->onBindViewHolder,即從確認(rèn)viewholder類型->根據(jù)類型值創(chuàng)建viewholder->根據(jù)數(shù)據(jù)源類型綁定數(shù)據(jù)到viewholder上。
第一步:我們看一下BaseSectionQuickAdapter這個(gè)類的定義
public abstract class BaseSectionQuickAdapter extends BaseQuickAdapter {
跟前面分析的多類型BaseMultiItemQuickAdapter差不多,只是我們的數(shù)據(jù)源需要繼承自SetionEntity。那么這個(gè)SetionEntity做了什么事呢,我們來看下源碼:
package com.chad.library.adapter.base.entity;
/**
* https://github.com/CymChad/BaseRecyclerViewAdapterHelper
*/
public abstract class SectionEntity {
public boolean isHeader;
public T t;
public String header;
public SectionEntity(boolean isHeader, String header) {
this.isHeader = isHeader;
this.header = header;
this.t = null;
}
public SectionEntity(T t) {
this.isHeader = false;
this.header = null;
this.t = t;
}
}
從源碼可以看出,他是一個(gè)抽象類,可能你會(huì)問,為什么要定義成抽象類呢,為什么不定義成接口或者普通類呢。
以下理由僅由我意想得出,大家也可以發(fā)表下自己的看法:
1、我們定義SectionEntity這個(gè)類,目的自然是希望用戶的bean都具有某些規(guī)范,而我們的BaseSectionQuickAdapter將根據(jù)該規(guī)范進(jìn)行數(shù)據(jù)的處理。雖然使用普通類一樣能達(dá)到相同的效果,但是不推薦,我覺得這讓有可能會(huì)讓用戶忽略我們所需要讓用戶知道的規(guī)范。
2、接口類,接口類其實(shí)是特殊的抽象類,上次分析的MultiItemEntity為什么又定義成接口類型呢,
public interface MultiItemEntity {
int getItemType();
}
根據(jù)實(shí)際需求而定,因?yàn)槲覀冊(cè)趯?shí)現(xiàn)多類型時(shí),只需要用戶的數(shù)據(jù)源提供一個(gè)類型值給我們即可,所以此時(shí)定義成接口類是最為合適的,因?yàn)橛脩魯?shù)據(jù)源只要實(shí)現(xiàn)了該接口,他必須實(shí)現(xiàn)接口的方法,而我們需要的恰恰是在使用時(shí)調(diào)用該接口即可。
但是在SetionEntity中,我們幫用戶多做點(diǎn)事,為其提供兩個(gè)構(gòu)造方法,一個(gè)時(shí)分組頭,一個(gè)是分組體。而此時(shí)如果是定義成接口類,是不符合需求的,因?yàn)榻涌陬惖姆椒ú荒苡蟹椒w等。
SectionEntity代碼分析:從源碼可以看出,假如我們當(dāng)前數(shù)據(jù)是分組頭,那么我們?cè)趧?chuàng)建bean時(shí)使用
public SectionEntity(boolean isHeader, String header) {
this.isHeader = isHeader;
this.header = header;
this.t = null;
}
即可,當(dāng)前定義分組頭只有個(gè)string類型的分組頭名字,你在繼承時(shí)可以根據(jù)實(shí)際需求進(jìn)行擴(kuò)展,內(nèi)部調(diào)用父類的該構(gòu)造方法即可。
如果時(shí)普通的數(shù)據(jù)bean:調(diào)用以下構(gòu)造方法即可,當(dāng)然你也可以進(jìn)行擴(kuò)展,根據(jù)個(gè)人需求而定。
public SectionEntity(T t) {
this.isHeader = false;
this.header = null;
this.t = t;
}
看完了SectionEntity。我們來看BaseSectionQuickAdapter的源碼:
package com.chad.library.adapter.base;
import android.view.ViewGroup;
import com.chad.library.adapter.base.entity.SectionEntity;
import java.util.List;
/**
* https://github.com/CymChad/BaseRecyclerViewAdapterHelper
*/
public abstract class BaseSectionQuickAdapter extends BaseQuickAdapter {
protected int mSectionHeadResId;
protected static final int SECTION_HEADER_VIEW = 0x00000444;
/**
* Same as QuickAdapter#QuickAdapter(Context,int) but with
* some initialization data.
*
* @param sectionHeadResId The section head layout id for each item
* @param layoutResId? ? ? The layout resource id of each item.
* @param data? ? ? ? ? ? A new list is created out of this one to avoid mutable list
*/
public BaseSectionQuickAdapter( int layoutResId, int sectionHeadResId, List data) {
super(layoutResId, data);
this.mSectionHeadResId = sectionHeadResId;
}
@Override
protected int getDefItemViewType(int position) {
return? mData.get(position).isHeader ? SECTION_HEADER_VIEW : 0;
}
@Override
protected K onCreateDefViewHolder(ViewGroup parent, int viewType) {
if (viewType == SECTION_HEADER_VIEW)
return createBaseViewHolder(getItemView(mSectionHeadResId, parent));
return super.onCreateDefViewHolder(parent, viewType);
}
@Override
public void onBindViewHolder(K holder, int positions) {
switch (holder.getItemViewType()) {
case SECTION_HEADER_VIEW:
setFullSpan(holder);
convertHead(holder, mData.get(holder.getLayoutPosition() - getHeaderLayoutCount()));
break;
default:
super.onBindViewHolder(holder, positions);
break;
}
}
protected abstract void convertHead(BaseViewHolder helper, T item);
}
大家可以看到,源碼比較少,跟BaseMultiItemQuickAdapter是一樣的思路。
字段解析:
protected int mSectionHeadResId;
mSectionHeadResId用來保存我們分組頭的布局資源ids;
protected static final int SECTION_HEADER_VIEW = 0x00000444;
定義了一個(gè)默認(rèn)的分組頭類型。思想與實(shí)現(xiàn)多類型一致;
我們?cè)趯?shí)例化BaseSectionQuickAdapter時(shí)需要多傳遞一個(gè)分組頭的資源ids過來,所以構(gòu)造方法是這樣的:
/**
* Same as QuickAdapter#QuickAdapter(Context,int) but with
* some initialization data.
*
* @param sectionHeadResId The section head layout id for each item
* @param layoutResId? ? ? The layout resource id of each item.
* @param data? ? ? ? ? ? A new list is created out of this one to avoid mutable list
*/
public BaseSectionQuickAdapter( int layoutResId, int sectionHeadResId, List data) {
super(layoutResId, data);
this.mSectionHeadResId = sectionHeadResId;
}
構(gòu)造好之后,我們也是利用來adapter的生命周期方法:
1、重寫getDefItemViewType方法,前面也解釋過為什么不是重寫Recycler.adapter的getItemViewType方法,以為我們的BaseQuickAdapter對(duì)其進(jìn)行來包裝。最終在getItemViewType方法中會(huì)調(diào)用我們的getDefItemViewType方法。
重寫該方法所做的事不多:
@Override
protected int getDefItemViewType(int position) {
return? mData.get(position).isHeader ? SECTION_HEADER_VIEW : 0;
}
根據(jù)我們當(dāng)前位置的數(shù)據(jù)bean,判斷當(dāng)前節(jié)點(diǎn)的數(shù)據(jù)bean是不是分組頭bean,如果是,返回SECTION_HEADER_VIEW告訴BaseQuickAdapter,你要?jiǎng)?chuàng)建的viewholder是分組頭類型的viewholder。否則返回0(0時(shí)RecyclerView.Adapter的缺省值,前面有分析)
接下來,我們也同樣是重寫了onCreateDefViewHolder方法。
@Override
protected K onCreateDefViewHolder(ViewGroup parent, int viewType) {
if (viewType == SECTION_HEADER_VIEW)
return createBaseViewHolder(getItemView(mSectionHeadResId, parent));
return super.onCreateDefViewHolder(parent, viewType);
}
根據(jù)返回的類型值,如果是SECTION_HEADER_VIEW 那么我們就創(chuàng)建一個(gè)分組頭viewholder返回。否則調(diào)用父類的方法按原流程走。
在這里我們還需要重寫onBindViewHolder方法,因?yàn)槲覀円嘧鰞杉虑椋?/p>
1、對(duì)我們的分組頭進(jìn)行特殊處理;
2、增加一個(gè)分組頭數(shù)據(jù)綁定的抽象方法的調(diào)用;
@Override
public void onBindViewHolder(K holder, int positions) {
switch (holder.getItemViewType()) {
case SECTION_HEADER_VIEW:
setFullSpan(holder);
convertHead(holder, mData.get(holder.getLayoutPosition() - getHeaderLayoutCount()));
break;
default:
super.onBindViewHolder(holder, positions);
break;
}
}
里面有個(gè)很有趣的方法。setFullSpan,從字面上理解是設(shè)置充滿空間,我們來看下代碼:
/**
* When set to true, the item will layout using all span area. That means, if orientation
* is vertical, the view will have full width; if orientation is horizontal, the view will
* have full height.
* if the hold view use StaggeredGridLayoutManager they should using all span area
*
* @param holder True if this item should traverse all spans.
*/
protected void setFullSpan(RecyclerView.ViewHolder holder) {
if (holder.itemView.getLayoutParams() instanceof StaggeredGridLayoutManager.LayoutParams) {
StaggeredGridLayoutManager.LayoutParams params = (StaggeredGridLayoutManager.LayoutParams) holder
.itemView.getLayoutParams();
params.setFullSpan(true);
}
}
里面原來是對(duì)Layoutmanager為StaggeredGridLayoutManager類型時(shí)做特殊處理,大家可以去了解下StaggeredGridLayoutManager這種類型的LayoutManager。
最后會(huì)調(diào)用一個(gè)方法
params.setFullSpan(true);
繼續(xù)看該方法源碼:
/**
* When set to true, the item will layout using all span area. That means, if orientation
* is vertical, the view will have full width; if orientation is horizontal, the view will
* have full height.
*
* @param fullSpan True if this item should traverse all spans.
* @see #isFullSpan()
*/
public void setFullSpan(boolean fullSpan) {
mFullSpan = fullSpan;
}
該方法是StaggeredGridLayoutManager提供的,英文說明的大意是:
如果你設(shè)置true,當(dāng)前item將使用布局的所有空間。如果是垂直的,會(huì)沾滿水平方向的寬度空間,如果是水平,會(huì)占滿垂直方向的高度空間。
然后將holder和當(dāng)前節(jié)點(diǎn)的數(shù)據(jù)bean作為參數(shù)調(diào)用convertHead函數(shù)。
所以當(dāng)你實(shí)現(xiàn)的是帶分組頭的adapter時(shí),會(huì)多出一個(gè)數(shù)據(jù)綁定的回調(diào)接口:
protected abstract void convertHead(BaseViewHolder helper, T item);
可能你還會(huì)看到以下代碼:
convertHead(holder, mData.get(holder.getLayoutPosition() - getHeaderLayoutCount()));
里面有一句holder.getLayoutPosition()。
getLayoutPosition是干什么用的呢,因?yàn)镽ecyclerView的item的布局和渲染其實(shí)是交給layoutManager來完成的,所以adapter中的item的位置可能跟data的index匹配不上,而getLayoutPosition將返回給我們當(dāng)前viewholder在recyclerView中的最新位置信息。
總結(jié):分析思路還是老套路,根據(jù)一個(gè)組件的生命周期及業(yè)務(wù)流程進(jìn)行分析,掌握一個(gè)控件的執(zhí)行流程是理解一個(gè)控件的實(shí)現(xiàn)的一個(gè)較好的方法,本人是這么認(rèn)為的,也是這么做的,大家有更好的學(xué)習(xí)方法可以多多留言,多多交流,沒有最好,只有更好!以上即為本次的代碼學(xué)習(xí),希望對(duì)大家有幫助。后面會(huì)繼續(xù)分析其他功能的源碼,歡迎一起學(xué)習(xí)!