前言
现在在实际开发中,越来越多的人选择RecyclerView来实现列表布局,而RecyclerView写多了,每次都要直接继承Adapter实现onCreateViewHolder
、onBindViewHolder
、getItemCount
这三个方法,虽然代码量不算很大,但每个XXXAdapter其实都长得差不多,这种重复性的代码,开发者是最不想写的了,所以网上就出现了很多封装Adapter的开源库。所以本篇文章也介绍自己封装的一个Adapter,帮你快速高效的添加一个列表(包括单Item列表和多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吧,或者能提出建议更好。
作者:zpayh