前言
現(xiàn)在在實(shí)際開發(fā)中,越來越多的人選擇RecyclerView來實(shí)現(xiàn)列表布局,而RecyclerView寫多了,每次都要直接繼承Adapter實(shí)現(xiàn)onCreateViewHolder
、onBindViewHolder
、getItemCount
這三個(gè)方法,雖然代碼量不算很大,但每個(gè)XXXAdapter其實(shí)都長(zhǎng)得差不多,這種重復(fù)性的代碼,開發(fā)者是最不想寫的了,所以網(wǎng)上就出現(xiàn)了很多封裝Adapter的開源庫(kù)。所以本篇文章也介紹自己封裝的一個(gè)Adapter,幫你快速高效的添加一個(gè)列表(包括單Item列表和多Item列表)。
預(yù)覽
先簡(jiǎn)單看一下最終效果:
而Adapter的代碼量極少,感受一下:
public class MyMultiAdapter extends BaseMultiAdapter {
@Override
public void bind(BaseViewHolder holder, int layoutRes) {
}
}
嗯,沒錯(cuò),你只需要實(shí)現(xiàn)bind
方法就可以了,而bind
方法是用來設(shè)置View
的一些一次性設(shè)置的,例如開啟響應(yīng)點(diǎn)擊事件,長(zhǎng)按事件等。所以上面我就什么都沒寫。
總體思路
- 實(shí)現(xiàn)一個(gè)通用的Adapter模版,避免寫Adapter中大量的重復(fù)代碼,抽象出幾個(gè)接口。
- 通過讓數(shù)據(jù)類實(shí)現(xiàn)
IMultiItem
接口,把部分Adapter中的代碼轉(zhuǎn)移到具體的數(shù)據(jù)類中,而不用在Adapter去判斷數(shù)據(jù)類型和ViewType。這樣很容易添加新的Item(ViewType)類型,減少耦合,Adapter不用去感知IMultiItem
的具體類型。 - 高內(nèi)聚,低耦合,方便擴(kuò)展。
- 封裝
ViewHolder
,將對(duì)View的常用操作都加上去。
實(shí)現(xiàn)
我們先看一下BaseViewHolder
:BaseViewHolder
封裝了我們一些常用的操作,例如獲取子View,設(shè)置item的點(diǎn)擊事件,設(shè)置item的子View
響應(yīng)點(diǎn)擊事件等。獲取子View
我用了Object[]
數(shù)組進(jìn)行緩存,沒有用SparseArray
來緩存View,主要是我之前看了Agera的源碼,所以才用這種方式來緩存的,這里按下不表,下面是BaseAdapter
的部分代碼:
public class BaseViewHolder extends RecyclerView.ViewHolder {
private Object[] mIdsAndViews = new Object[0];
/**
* 設(shè)置響應(yīng)點(diǎn)擊事件,如果設(shè)置了clickable為true的話,在{@link BaseAdapter#setOnItemClickListener(OnItemClickListener)}
* 中會(huì)得到響應(yīng)事件的回調(diào),詳情參考{@link BaseAdapter#setOnItemClickListener(OnItemClickListener)}
* @param id 響應(yīng)點(diǎn)擊事件的View Id
* @param clickable true響應(yīng)點(diǎn)擊事件,false不響應(yīng)點(diǎn)擊事件
*/
public BaseViewHolder setClickable(@IdRes int id, boolean clickable){
View view = find(id);
if (view != null){
if (clickable){
view.setOnClickListener(mOnClickListener);
}else{
view.setOnClickListener(null);
}
}
return this;
}
/**
* 根據(jù)當(dāng)前id查找對(duì)應(yīng)的View控件
* @param viewId View id
* @param <T> 子View的具體類型
* @return 返回當(dāng)前id對(duì)應(yīng)的子View控件,如果沒有,則返回null
*/
@CheckResult
public <T extends View> T find(@IdRes int viewId){
int indexToAdd = -1;
for (int i = 0; i < mIdsAndViews.length; i+=2) {
Integer id = (Integer) mIdsAndViews[i];
if (id != null && id == viewId){
return (T) mIdsAndViews[i+1];
}
if (id == null){
indexToAdd = i;
}
}
if (indexToAdd == -1){
indexToAdd = mIdsAndViews.length;
mIdsAndViews = Arrays.copyOf(mIdsAndViews,
indexToAdd < 2 ? 2 : indexToAdd * 2);
}
mIdsAndViews[indexToAdd] = viewId;
mIdsAndViews[indexToAdd+1] = itemView.findViewById(viewId);
return (T) mIdsAndViews[indexToAdd+1];
}
}
接下來我們來看一下BaseMultiAdapter
里面做了什么?
public abstract class BaseMultiAdapter extends BaseAdapter<IMultiItem> {
@Override
public int getLayoutRes(int index) {
final IMultiItem data = mData.get(index);
return data.getLayoutRes();
}
@Override
public void convert(BaseViewHolder holder, IMultiItem data, int index) {
data.convert(holder);
}
}
是不是發(fā)現(xiàn)這里面也很少代碼,因?yàn)楹艽笠徊糠执a都在BaseAdapter
中實(shí)現(xiàn)了,
這里我們發(fā)現(xiàn)了一個(gè)IMultiItem
,我們看一下它倆的源代碼:
public interface IMultiItem {
/**
* 不同類型的item請(qǐng)使用不同的布局文件,
* 即使它們的布局是一樣的,也要copy多一份出來。
* @return 返回item對(duì)應(yīng)的布局id
*/
@LayoutRes int getLayoutRes();
/**
* 進(jìn)行數(shù)據(jù)處理,顯示文本,圖片等內(nèi)容
* @param holder Holder Helper
*/
void convert(BaseViewHolder holder);
/**
* 在布局為{@link android.support.v7.widget.GridLayoutManager}時(shí)才有用處,
* 返回當(dāng)前布局所占用的SpanSize
* @return 如果返回的SpanSize <= 0 或者 > {@link GridLayoutManager#getSpanCount()}
* 則{@link BaseAdapter} 會(huì)在{@link BaseAdapter#onAttachedToRecyclerView(RecyclerView)}
* 自適應(yīng)為1或者{@link GridLayoutManager#getSpanCount()},詳情參考{@link BaseAdapter#onAttachedToRecyclerView(RecyclerView)}
*/
int getSpanSize();
}
public abstract class BaseAdapter<T> extends RecyclerView.Adapter<BaseViewHolder> {
protected final List<T> mData = new ArrayList<>();
private BaseViewHolder.OnItemClickListener mOnItemClickListener;
@Override
public BaseViewHolder onCreateViewHolder(ViewGroup parent, int layoutRes) {
BaseViewHolder baseViewHolder = new BaseViewHolder(LayoutInflater.from(parent.getContext())
.inflate(layoutRes, parent, false));
bindData(baseViewHolder,layoutRes);
return baseViewHolder;
}
@Override
public final void onBindViewHolder(BaseViewHolder holder, int position) {
//數(shù)據(jù)布局
final T data = mData.get(position);
convert(holder, data, position);
}
@Override
public final int getItemCount() {
return mData.size();
}
@Override
public int getItemViewType(int position) {
return getLayoutRes(position);
}
@Override
public void onAttachedToRecyclerView(RecyclerView recyclerView) {
super.onAttachedToRecyclerView(recyclerView);
RecyclerView.LayoutManager manager = recyclerView.getLayoutManager();
if (manager == null || !(manager instanceof GridLayoutManager)) return;
final GridLayoutManager gridLayoutManager = (GridLayoutManager) manager;
gridLayoutManager.setSpanSizeLookup(new GridLayoutManager.SpanSizeLookup() {
@Override
public int getSpanSize(int position) {
final T data = getData(position);
if (data != null && data instanceof IMultiItem){
int spanSize = ((IMultiItem)data).getSpanSize();
return spanSize <= 0 ? 1 :
spanSize > gridLayoutManager.getSpanCount()?
gridLayoutManager.getSpanCount():spanSize;
}
return 1;
}
});
}
protected void bindData(BaseViewHolder baseViewHolder, int layoutRes) {
baseViewHolder.setOnItemClickListener(new OnItemClickListener() {
@Override
public void onItemClick(@NonNull View view, int adapterPosition) {
if (mOnItemClickListener != null){
mOnItemClickListener.onItemClick(view, adapterPosition);
}
}
});
bind(baseViewHolder, layoutRes);
}
/**
* 返回布局layout
*/
@LayoutRes
public abstract int getLayoutRes(int index);
/**
* 在這里設(shè)置顯示
*/
public abstract void convert(BaseViewHolder holder, T data, int index);
/**
* 開啟子view的點(diǎn)擊事件,或者其他監(jiān)聽
*/
public abstract void bind(BaseViewHolder holder,int layoutRes);
}
看到這里我們就能發(fā)現(xiàn)了,BaseAdapter已經(jīng)寫了大部分的代碼,就留下getLayoutRes
,convert
,bind
給子類去實(shí)現(xiàn),而它的子類BaseMultiAdapter
直接把getLayoutRes
和convert
丟給了IMultiItem
去實(shí)現(xiàn)。
getLayoutRes
是返回item對(duì)應(yīng)的布局文件id,同時(shí)它在BaseAdapter
也作為ViewType來使用,所以如果是不同類型的item,不建議共用同個(gè)布局文件。
所以,我們的數(shù)據(jù)類只要實(shí)現(xiàn)IMultiItem
接口即可,例如上面的文本類item:
public class Text implements IMultiItem{
public String mText;
private int mSpanSize;
public Text(String text,int spanSize) {
mText = text;
mSpanSize = spanSize;
}
@Override
public int getLayoutRes() {
return R.layout.item_text;
}
@Override
public void convert(BaseViewHolder holder) {
holder.setText(R.id.text,mText);
}
@Override
public int getSpanSize() {
return mSpanSize;
}
}
把getLayoutRes
跟convert
交給IMultiItem
處理的好處就是實(shí)現(xiàn)多布局列表變得很簡(jiǎn)單,數(shù)據(jù)各自對(duì)應(yīng)自己的布局文件,自己在convert
方法中顯示數(shù)據(jù)。
源碼
上面的具體全部代碼在都在我的開源庫(kù)里,一個(gè)封裝了RecyclerView.Adapter一些常用功能的庫(kù):SherlockAdapter
文章寫得有點(diǎn)簡(jiǎn)單了點(diǎn),更好的學(xué)習(xí)方式是閱讀源碼,如果您喜歡的話,給我的github加個(gè)star吧,或者能提出建議更好。