已经有好几天么有更新了,为考试准备了几天时间,现在考完了可以重操旧业了哈哈!
虽然之前已经写过这篇文章了,但是刚刚翻看了一下自己写过的旧文章,发现这篇写的非常的烂头-。=,而且里面有一个问题没有说的明白,所以决定重新写一下,也为了自己能够再熟悉一下。
下面进入正题:
可复用适配器基本原理
相信大家都是用过RecyclerView,作为一个很常用的数据展示类控件,大家都知道其最为核心的地方就在于适配器的编写了。(之后我会在博客中补充一篇RecyclerView的详细使用的博客),编写适配器的步骤主要分为以下几个步骤:
- 创建View布局
- 绑定View布局中的控件
- 添加数据
而在必要的时候,我们会通过ViewHolder去实现以上步骤。
其中1对应的是RecyclerView中的onCreateViewHolder方法,2对应的是RecyclerView的onBindViewHolder方法。当然RecyclerView还有一个getItemCount方法需要重写,但是不在本篇重点考虑中。
在以往我们每次写一个RecyclerView,都需要写一个适配器,其实就是因为我们每次的RecyclerView条目(也就是需要在RecyclerView中创建的布局)不同,因为是已经写死了的,所以无法更改。
@Override
public MyViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
View view = LayoutInflater.from(mContext).inflate(R.layout.rv_layout, parent, false);
return new MyViewHolder(view);
}
class static MyViewHolder extends RecyclerView.ViewHolder {
TextView mTv;
public MyViewHolder(View itemView) {
super(itemView);
mTv = itemView.findViewById(R.id.name);
}
}
例如上面,就是一个典型的RecyclerView适配器的写法。在适配器中,我们固定了布局id和控件类型,所以只能给一种适配。
假如说一个适配器类没有写死布局,那么他就可以对各种布局中的控件进行绑定(findViewById)了。这就是可复用适配器的基本原理。
现在我们知道了基本原理,接下试一下如何实现吧。
实现步骤
在以前,我们都是在适配器中传入源数据引用(List等)、上下文(Context),而现在实现可复用,所以需要传入一个很重要的参数:布局ID。
private List<T> mList;
private int mResId;
public MyBaseAdapter(List<T> mList, int mResId) {
this.mList = mList;
this.mResId = mResId;
}
1.修改ViewHolder
public static class MyViewHolder extends RecyclerView.ViewHolder {
public static MyViewHolder getHolder(){
return null;
}
private MyViewHolder(View itemView) {
super(itemView);
}
......
}
与之前一同,现在我们把MyViewHolder创建实例的方法改成了静态工厂创建。具体里面的逻辑和添加参数和其他方法的操作我们等会儿再说。现在只需要知道,创建MyViewHolder实例只需要通过这个这个getHolder方法就好。
@Override
public MyViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
return MyViewHolder.getHolder();
}
RecyclerView中的onCreateViewHolder方法需要一个MyViewHolder对象,所以在这里调用MyViewHolder.getHolder方法(当然肯定是需要参数的,等会儿再添加啦)。
2.看一看onBindViewHolder方法
我们之前在onBindViewHolder方法中处理控件赋值的事务,虽然现在我们不知道是哪个布局,是什么控件,但是我们只需要知道,在onBindViewHolder方法中,就是处理这一个事务,我们假定其他的都处理好了。
但是不同的布局也有不同的控件,我们还是不知道该对什么控件进行操作,但是注意到在Adapter构造器中就传入了布局ID。所以我们需要一个抽象方法,然后把这些乱七八糟的事情扔给调用者(一般都是在Activity或者Fragment中)。
@Override
public void onBindViewHolder(MyViewHolder holder, int position) {
bindView(holder, position);
}
public abstract void bindView(MyViewHolder holder,int position);
当然,这个适配器也需要变成抽象类了。
public abstract class MyBaseAdapter<T> extends RecyclerView.Adapter<MyBaseAdapter.MyViewHolder> ;
3.补充
大体的框架就是这个样子,接下来我们需要对之前的方法参数、方法之类的进行一下补充。
首先添加给getHolder方法完善一下参数吧:
我们知道getHolder方法为了获取获取到一个Holder对象,所以,
public static MyViewHolder getHolder(int mResId, ViewGroup parent, int viewType) {
MyViewHolder holder;
View view = LayoutInflater.from(parent.getContext()).inflate(mResId, parent, false);
holder = new MyViewHolder(view);
return holder;
}
在onCreateViewHolder中调用:
@Override
public MyViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
Log.e(TAG, "onCreateViewHolder: ");
return MyViewHolder.getHolder(mResId, parent, viewType);
}
然后我们需要完善一下MyViewHolder的属性:
private SparseArray<View> views = new SparseArray<>();
最简单的就是在只需要在MyViewHolder中添加一个SparseArray属性,制定他的泛型为View,这个属性用来存储布局中的控件,通过id来获取。
接下来需要在MyViewHolder中写几个辅助方法:
private <T extends View> T getView(int id) {
T t = (T) views.get(id);
if (t == null) {
t = this.itemView.findViewById(id);
views.put(id, t);
}
return t;
}
这个方法就是通过id来从SparseArray实例中获取控件,如果已经存储过,那么就直接获取,如果为空,就调用itemView来获取。
这里要说一下itemView这个属性:我们并没有在MyViewHolder中创建这样一个属性,它是在RecyclerView.ViewHolder中的一个final属性,
public abstract static class ViewHolder {
public final View itemView;
}
这个itemView在ViewHolder的构造器中被赋值,我们点击super(itemView)看一下:
public ViewHolder(View itemView) {
if (itemView == null) {
throw new IllegalArgumentException("itemView may not be null");
}
this.itemView = itemView;
}
好了,如果想继续深入研究的朋友可以下去自己研究,我们继续完善MyViewHolder。
其实到现在为止我们的部署任务已经完成了,就差一点——给控件赋值,由于Android中有各式各样的控件,我们不可能做到给每个控件都赋值,所以最好是用到什么的地方然后再去添加,不过这样也比重写一个适配器要好很多了吧。
我们先写一个给TextView添加数据的方法吧:
public MyViewHolder setText(int id, String text) {
TextView textView = getView(id);
textView.setText(text);
return this;
}
然后再来一个CheckBox数据添加的方法:
public MyViewHolder setChecked(int id, boolean isChecked) {
CheckBox checkBox = getView(id);
checkBox.setChecked(isChecked);
return this;
}
这里都是用到了刚刚写的getView方法,先获取到对应id的控件,然后进行赋值。
由于给item的控件进行赋值,所以可能会有很多的控件,为了调用方便我们把这一系列函数都写成了链式编程。
然后这些方法使用是在什么地方呢?还记得我们之前写的抽象方法bindView吗?这个抽象函数实在调用者的地方实现的:
public abstract void bindView(MyViewHolder holder, int position);
在这个方法中有一个holder对象,这个holder就是我们之前进行一系列加工的holder,所以刚才写的一系列方法都可以使用。
recyclerView.setAdapter(new MyBaseAdapter<Bean>(list, R.layout.rv_layout) {
@Override
public void bindView(MyViewHolder holder, int position) {
holder.setText(R.id.name, list.get(position).getName());
}
});
这个就是在调用者中实现的bindView方法了,可以看到我们直接调用holder的setText方法,然后将需要添加数据的控件ID传入,然后再将数据传入就好了。当然如果有很多控件需要添加数据,这时候链式编程的优势就展现出来啦。
holder.setText(R.id.name, list.get(position).getName())
.setPicture(R.id.head, R.drawable.ic_launcher_background)
.setChecked(R.id.isOver, false);
下面一起来看一下效果吧:
源码如下:https://github.com/gfzy9876/RecyclerView-.git
今天的文章就到这里了,喜欢的朋友希望点赞或关注一下,有不同想法的朋友们欢迎欢迎下方留言一起探讨,感谢大家的支持!!