前言
现在在实际开发中,越来越多的人选择RecyclerView来实现列表布局,而RecyclerView写多了,每次都要直接继承Adapter实现onCreateViewHolder
、onBindViewHolder
、getItemCount
这三个方法,虽然代码量不算很大,但每个XXXAdapter其实都长得差不多,这种重复性的代码,开发者是最不想写的了,所以网上就出现了很多封装Adapter的开源库。所以本篇文章也介绍自己封装的一个Adapter,帮你快速高效的添加一个列表(包括单Item列表和多Item列表)。
预览
先简单看一下最终效果:
而Adapter的代码量极少,感受一下:
public class MyMultiAdapter extends BaseMultiAdapter {
@Override
public void bind(BaseViewHolder holder, int layoutRes) {
}
}
嗯,没错,你只需要实现bind
方法就可以了,而bind
方法是用来设置View
的一些一次性设置的,例如开启响应点击事件,长按事件等。所以上面我就什么都没写。
总体思路
- 实现一个通用的Adapter模版,避免写Adapter中大量的重复代码,抽象出几个接口。
- 通过让数据类实现
IMultiItem
接口,把部分Adapter中的代码转移到具体的数据类中,而不用在Adapter去判断数据类型和ViewType。这样很容易添加新的Item(ViewType)类型,减少耦合,Adapter不用去感知IMultiItem
的具体类型。 - 高内聚,低耦合,方便扩展。
- 封装
ViewHolder
,将对View的常用操作都加上去。
实现
我们先看一下BaseViewHolder
:BaseViewHolder
封装了我们一些常用的操作,例如获取子View,设置item的点击事件,设置item的子View
响应点击事件等。获取子View
我用了Object[]
数组进行缓存,没有用SparseArray
来缓存View,主要是我之前看了Agera的源码,所以才用这种方式来缓存的,这里按下不表,下面是BaseAdapter
的部分代码:
public class BaseViewHolder extends RecyclerView.ViewHolder {
private Object[] mIdsAndViews = new Object[0];
/**
* 设置响应点击事件,如果设置了clickable为true的话,在{@link BaseAdapter#setOnItemClickListener(OnItemClickListener)}
* 中会得到响应事件的回调,详情参考{@link BaseAdapter#setOnItemClickListener(OnItemClickListener)}
* @param id 响应点击事件的View Id
* @param clickable true响应点击事件,false不响应点击事件
*/
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;
}
/**
* 根据当前id查找对应的View控件
* @param viewId View id
* @param <T> 子View的具体类型
* @return 返回当前id对应的子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);
}
}
是不是发现这里面也很少代码,因为很大一部分代码都在BaseAdapter
中实现了,
这里我们发现了一个IMultiItem
,我们看一下它俩的源代码:
public interface IMultiItem {
/**
* 不同类型的item请使用不同的布局文件,
* 即使它们的布局是一样的,也要copy多一份出来。
* @return 返回item对应的布局id
*/
@LayoutRes int getLayoutRes();
/**
* 进行数据处理,显示文本,图片等内容
* @param holder Holder Helper
*/
void convert(BaseViewHolder holder);
/**
* 在布局为{@link android.support.v7.widget.GridLayoutManager}时才有用处,
* 返回当前布局所占用的SpanSize
* @return 如果返回的SpanSize <= 0 或者 > {@link GridLayoutManager#getSpanCount()}
* 则{@link BaseAdapter} 会在{@link BaseAdapter#onAttachedToRecyclerView(RecyclerView)}
* 自适应为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) {
//数据布局
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);
/**
* 在这里设置显示
*/
public abstract void convert(BaseViewHolder holder, T data, int index);
/**
* 开启子view的点击事件,或者其他监听
*/
public abstract void bind(BaseViewHolder holder,int layoutRes);
}
看到这里我们就能发现了,BaseAdapter已经写了大部分的代码,就留下getLayoutRes
,convert
,bind
给子类去实现,而它的子类BaseMultiAdapter
直接把getLayoutRes
和convert
丢给了IMultiItem
去实现。
getLayoutRes
是返回item对应的布局文件id,同时它在BaseAdapter
也作为ViewType来使用,所以如果是不同类型的item,不建议共用同个布局文件。
所以,我们的数据类只要实现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
处理的好处就是实现多布局列表变得很简单,数据各自对应自己的布局文件,自己在convert
方法中显示数据。
源码
上面的具体全部代码在都在我的开源库里,一个封装了RecyclerView.Adapter一些常用功能的库:SherlockAdapter
文章写得有点简单了点,更好的学习方式是阅读源码,如果您喜欢的话,给我的github加个star吧,或者能提出建议更好。