目标
本篇是RecyclerView
的重构之路系列的第七篇, 讲解IDouban项目中RecylcerView.ViewHolder
, RecylcerView.Adapter
的重构。目的是 干掉功能以及代码类似的BookAdpater
,MoviesAdapter
。抱歉,到现在才切入正题,我承认是标题党
代码分析
在做重构之前,回顾下,所写的代码功能: 获取书籍并展示,获取电影并展示。
代码目录结构
有同学会说,木丁老师,重构代码是越写越多啊? 文件也还增加了。注意,小智同学,重构的目的是解耦!是让废代码、重复代码消失,让扩展性好一些。不能以文件数量多少,代码多少为标准。IDouban除了RecyclerView
之外,还有很多废代码。
如图所示,右边添加了新包common
,存放通用类, 暂时添加了Adapter
, ViewHolder
2个类。
关键类继承图
本次重构需要干掉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 操作一样了。
您要是觉得好,请点个赞,加个星!