目标
本篇是RecyclerView的重构之路系列的第七篇, 讲解IDouban项目中RecylcerView.ViewHolder, RecylcerView.Adapter的重构。目的是 干掉功能以及代码类似的BookAdpater,MoviesAdapter。抱歉,到现在才切入正题,我承认是标题党

代码分析
在做重构之前,回顾下,所写的代码功能: 获取书籍并展示,获取电影并展示。
代码目录结构

有同学会说,木丁老师,重构代码是越写越多啊? 文件也还增加了。注意,小智同学,重构的目的是解耦!是让废代码、重复代码消失,让扩展性好一些。不能以文件数量多少,代码多少为标准。IDouban除了RecyclerView之外,还有很多废代码。
如图所示,右边添加了新包common,存放通用类, 暂时添加了Adapter, ViewHolder2个类。
关键类继承图

本次重构需要干掉BookAdapter, MoviesAdapter, 细看上图, 红框代表重构之前的ViewHolder & xxxAdapter 类继承关系。篮框代表重构之后的ViewHolder & Adapter, 注意其中,BookAdapter, MoviesAdapter不见了,取代他们是类Adapter, 并且使用了泛型。
关键代码
从2个方面展开, 1. ViewHolder; 2. Adapter
ViewHolder
public abstract class ViewHolder<T> extends RecyclerView.ViewHolder {
protected T itemContent;
public ViewHolder(@NonNull View itemView) {
super(itemView);
}
public void updateItem(T itemContent) {
this.itemContent = itemContent;
onBindItem(itemContent);
}
protected abstract void onBindItem(T itemContent);
public interface Builder<VH> {
VH build(View itemView);
}
}
ViewHolder类继承自RecyclerView.ViewHolder并且使用泛型, 其中T表示泛型,这里理解为Book, Movies... ...
-
ViewHolder构造方法必须写 -
updateItem(T itemContent) {...}提供给Adapter使用 -
onBindItem是在ViewHolder具体子类中使用。因为,不同的ViewHolder绑定的布局内容是不一致的 -
interface Builder<VH>获得传入Adapter中的是哪种ViewHolder实例
Adapter
public class Adapter<T, VH extends ViewHolder<T>> extends RecyclerView.Adapter<VH> {
List<T> data;
ViewHolder.Builder<VH> builder;
@LayoutRes
int layoutResId;
public Adapter(@NonNull List<T> data, @LayoutRes int layoutResId, ViewHolder.Builder<VH> builder) {
this.data = data;
this.layoutResId = layoutResId;
this.builder = builder;
}
@NonNull
@Override
public VH onCreateViewHolder(ViewGroup parent, int viewType) {
View itemView = LayoutInflater.from(parent.getContext()).inflate(layoutResId, parent, false);
return builder.build(itemView);
}
@Override
public void onBindViewHolder(VH holder, final int position) {
if (holder == null) return;
holder.updateItem(data.get(position));
}
@Override
public int getItemCount() {
return data.size();
}
public void setData(List<T> data) {
this.data = data;
notifyDataSetChanged();
}
public T getItem(int pos) {
return data.get(pos);
}
}
public class Adapter<T, VH extends ViewHolder<T>> extends RecyclerView.Adapter<VH>
Adapter的是一个桥, 链接 数据 和ViewHolder, 这里的Adapter是泛型类,其中的T表示Book,Movies等,VH是ViewHolder<T>的子类,这里约束了VH必须是我们自定义的ViewHolder。
简单来说,Adapter是RecyclerView.Adapter<VH>的子类,并且,Adapter是泛型类。List<T> data;存放外界传入的数据列表ViewHolder.Builder<VH> builder;用于onCreateViewHolder方法中获取特定的ViewHolder子类int layoutResId;传入RecyclerView的itemview所需布局idpublic Adapter(@NonNull List<T> data, @LayoutRes int layoutResId, ViewHolder.Builder<VH> builder)
构造方法,传入关键性参数。onCreateViewHolder必须实现的方法, 难点在于, 无法直接return 出所需要的VH, 因为VH是泛型化,这里没法直接通过return new VH(itemview)方式获得实例,需要在某个调用点的地方才知道传入的是哪种ViewHolder的子类。此处借鉴Builder模式,在使用到的时候,才建造对应的ViewHolder子类对象。onBindViewHolder必须覆盖的方法,其中关键是holder.updateItem()方法。getItemCount必须覆盖的方法, 用于确定有多少数据。setData外界更新数据列表getItem让外界获取对应pos的数据, 暂未使用!
上述代码, 其中, 第6点不好理解, 其他不难。
如何使用
关键点是Adpater的初始化。
mBookAdapter = new Adapter<>(mBookList, R.layout.recyclerview_book_item, new ViewHolder.Builder<BookViewHolder>() {
@Override
public BookViewHolder build(View itemView) {
return new BookViewHolder(itemView);
}
});
mMovieAdapter = new Adapter<>(mMoviesList, R.layout.recyclerview_movies_item, new ViewHolder.Builder<MoviesViewHolder>() {
@Override
public MoviesViewHolder build(View itemView) {
return new MoviesViewHolder(itemView);
}
});
github代码
本篇代码已经上传github, 并且添加的tag release_02版本,查看第一次重构后的代码直接使用如下方法:
- **git clone https://github.com/tancolo/IDouban.git **
- ** git checkout release_02 就可以取得 tag 对应的代码了。**
但是这时候 git 可能会提示你当前处于一个“detached HEAD" 状态,因为 tag 相当于是一个快照,是不能更改它的代码的,如果要在 tag 代码的基础上做修改,你需要一个分支:
git checkout -b your_branch_name release_02
这样会从 tag 创建一个分支,然后就和普通的 git 操作一样了。
您要是觉得好,请点个赞,加个星!