这一节我们来讲一下RecyclerView.Adapter的简易封装,相信大家都会经常逛开源平台或者社区每每看到好多大神分享的干货写的那么叼CV之后即可直接使用是不是非常开心?那你有没有想过自己也可以来造一个轮子让别人也来使用你造的呢?下面我们就开始造轮子吧(重在封装的过程,对封装的理解)。
一: 我们先来看下默认的Adapter长啥样
public class ItemAdapter extends RecyclerView.Adapter<ItemAdapter.ViewHolder> {
@Override
public ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
return null;
}
@Override
public void onBindViewHolder(ViewHolder holder, int position) {
}
@Override
public int getItemCount() {
return 0;
}
public class ViewHolder extends RecyclerView.ViewHolder {
public ViewHolder(View itemView) {
super(itemView);
}
}
}
从上面代码中可以看出要写出一个RecyclerView.Adapter必须自己写一个ViewHolder且一定要继承RecyclerView.ViewHolder;然后重写
onCreateViewHolder
、onBindViewHolder
、getItemCount
这三个函数,然而我们最关心的也就是onBindViewHolder
函数对数据进行绑定。
而其他几个函数都基本上都是固定格式了,所以我们就可以将这些千篇一律的代码进行封装了。Adapter中我们最终要的一个就是数据源了,所以我们需要先定义我们的数据源但是你会发现在不同的地方
我们的数据也是不一样的那我们怎么定义呢?这个时候我们就需要引入泛型了。
/**
* 泛型:广泛的类型
* 在编写代码的过程中,使用集合强转的时候可能会出现ClassCastException(类型转换异常)
* 因为在集合中存放的对象是任意的,使用对象的时候会有安全隐患
* JDK1.5之后出现了泛型
* 1.泛型好处在于:将程序运行时的异常转换成编译时的异常
* 方便程序员编写代码,提高代码的安全性
* 2.避免了强转操作
* 泛型的格式:<类型>
* 泛型的使用:在集合中使用的比较多,其实泛型的定义就是指定接收某一类型
*
* 泛型出现之前与出现之后的对比:
* 早期:定义Object去接收未知类型
* 现在:当需要使用的引用数据类型不确定的时候,使用泛型去作为一个规则去接收数据(提高代码的扩展性)
* 一旦这么定义,该类中所有使用到了该泛型的方法都必须遵守这个规则
* 现在我想打破这个规则:
* 1.之前都是泛型类
* 2.可以使用泛型方法打破这个规则
* 当方法需要使用未知类型的引用数据的时候,如果在类上定义泛型,会造成使用的局限性,为了打破这个局限性,
* 我们在方法中单独定义泛型,让该方法使用的泛型脱离类的控制
* 格式:修饰符 <泛型> 返回值 方法名(){}
* 静态方法使用泛型:必须使用自己定义的泛型
*
* 泛型的高级应用:
* 占位符: ?
* 泛型限定: ? extends Person 定义上限,向下扩展
* : ? super Person 定义下限,向上延伸
*
*/
二: 可以看出下面出现的D(是一个实体类)和VH分别代表了我们的数据源、视图管理者,这样我们封装的时候就可以用来当作真实数据操作了。
public abstract class BaseRecyclerAdapter<D, VH extends RecyclerView.ViewHolder> extends RecyclerView.Adapter<VH> {
@Override
public VH onCreateViewHolder(ViewGroup parent, int viewType) {
return null;
}
@Override
public void onBindViewHolder(VH holder, int position) {
}
@Override
public int getItemCount() {
return 0;
}
}
这里还需要一个ViewHolder我们来写一个BaseViewHolder
class BaseViewHolder extends RecyclerView.ViewHolder {
public BaseViewHolder(View itemView) {
super(itemView);
}
}
三: 现在我们就可以来实现Adapter里面的逻辑了,写一个构造函数用来初始化数据源和布局id同时写一个抽象方法(这也就是为什么好多Base都是抽象类了)用来显示数据
public abstract class BaseRecyclerAdapter<D, VH extends RecyclerView.ViewHolder> extends RecyclerView.Adapter<VH> {
/**
* 数据源
*/
private List<D> data;
/**
* 布局资源id
*/
private int layoutResId;
public BaseRecyclerAdapter(int layoutResId, List<D> data) {
this.data = data == null ? new ArrayList<D>() : data;
if (layoutResId != 0) {
this.layoutResId = layoutResId;
} else {
throw new NullPointerException("请设置Item资源id");
}
}
@Override
public VH onCreateViewHolder(ViewGroup parent, int viewType) {
View view = LayoutInflater.from(parent.getContext()).inflate(layoutResId, parent, false);
return (VH) new BaseViewHolder(view);
}
@Override
public void onBindViewHolder(VH holder, int position) {
bindTheData(holder, data.get(position));
}
@Override
public int getItemCount() {
return data.size();
}
/**
* 绑定数据
*
* @param holder 视图管理者
* @param data 数据源
*/
protected abstract void bindTheData(VH holder, D data);
class BaseViewHolder extends RecyclerView.ViewHolder {
public BaseViewHolder(View itemView) {
super(itemView);
}
}
}
四: 封装了这么多我们写一个适配器试验一下
public class TestAdapter extends BaseRecyclerAdapter<DataBean, BaseRecyclerAdapter.BaseViewHolder> {
public TestAdapter(int layoutResId, List<DataBean> data) {
super(layoutResId, data);
}
@Override
protected void bindTheData(BaseRecyclerAdapter.BaseViewHolder holder, DataBean data) {
}
}
如果报了如下错误: TestAdapter不是抽象的, 并且未覆盖BaseRecyclerAdapter中的抽象方法bindTheData(BaseRecyclerAdapter.BaseViewHolder,DataBean,int)
只需要将bindTheData
函数修改为如上BaseRecyclerAdapter.BaseViewHolder
即可
可以看到我们在实现一个适配器就只需要实现一个构造函数和一个绑定数据了是不是简便多了,当然到现在我们的
BaseViewHolder
还是
有问题的同样需要将他封装一下
class BaseViewHolder extends RecyclerView.ViewHolder {
/**
* 集合类,layout里包含的View,以view的id作为key,value是view对象
*/
private SparseArray<View> mViews;
public BaseViewHolder(View itemView) {
super(itemView);
mViews = new SparseArray<>();
}
public <T extends View> T findViewById(int viewId) {
View view = mViews.get(viewId);
if (view == null) {
view = itemView.findViewById(viewId);
mViews.put(viewId, view);
}
return (T) view;
}
/**
* 设置文本资源
*
* @param viewId view id
* @param s 字符
*/
public TextView setText(int viewId, CharSequence s) {
TextView view = findViewById(viewId);
view.setText(s);
return view;
}
//......
}
从上面代码可以看出使用了SparseArray
(这个和HashMap用法是一致的,Google对HashMap的一个加强版)用来保存已经找过的view,这里也是很简单的封装但确非常的实用只需要一个id便可以做你想做的事,后续有时间将继续封装上拉、下拉操作;最后附上源码地址。
于2017年4月5日增加了添加头尾布局的功能,使用方法如下:
//添加头布局
adapter.addHeadView(R.layout.head_view);
//添加尾布局
adapter.addFootView(R.layout.foot_view);