一、作用
-
DiffUtil.calculateDiff()
方法运行在主线程中,当新旧 List 比较大时,该方法会阻塞主线程。 - 计算出 DiffUtil.Result 后,需要将新数据集先设置给 Adapter,然后调用
DiffUtil.diapatchUpdatesTo(adapter)
刷新 UI,但很多人会忘记更新数据集。
DiffUtil
已经挺好用了,但由于上述两个问题的存在,Google 工程师又推出了AsyncListDiff
。
二、概念
AsyncListDiff
通过DiffUtil
在后台线程计算两个 List 之间的差异。它和RecyceleView.Adapter
相连接,可以将提交的 List 的变化通知给 Adapter。
它会在收到新的 List 后,开启后台线程使用DiffUtil
计算新老数据集的差异。
1. 构造函数
public AsyncListDiffer(@NonNull RecyclerView.Adapter adapter,
@NonNull DiffUtil.ItemCallback < T > diffCallback)
2. public 方法
(1)List<T> getCurrentList()
该方法可得到当前 List,通过该方法可安全地访问列表项和总大小。
如果 List 为null
,或未提交 List,那么返回一个empty List
,这句话也说明,通过该方法返回的 List 不需要进行判 null,但需要判断是否是 empty。
(2)void submitList(List<T> newList)
传递新的 List 到
AdapterHelper
如果 List 已经存在并在展示,将开启线程,在后台计算新老数据集差异,计算完成后回调ListUpdateCallback
,用新的 List 交换旧的。
三、使用
1. 创建 Adapter Item 对应的实体类
2. 改造型 Adapter
public class AsyncListDiffAdapter extends RecyclerView.Adapter < AsyncListDiffAdapter.MyViewHolder > {
private AsyncListDiffer < User > mDiffer;
public AsyncListDiffAdapter() {
// 初始化AsyncListDiffer
mDiffer = new AsyncListDiffer < User > (this, new AsyncListDiffCallback());
}
@NonNull
@Override
public MyViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
View view = LayoutInflater.from(parent.getContext()).inflate(R.layout.user_item, parent, false);
return new MyViewHolder(view);
}
@Override
public void onBindViewHolder(@NonNull MyViewHolder holder, int position) {
holder.setData(mDiffer.getCurrentList().get(position));
}
@Override
public int getItemCount() {
// mDiffer.getCurrentList()得到的数据集不会为null,但可能未empty
return mDiffer.getCurrentList().size();
}
// 向 Adapter 提交新的数据源
public void submitList(List < User > data) {
mDiffer.submitList(data);
}
// 得到数据源中的第position项
public User getItem(int position) {
return mDiffer.getCurrentList().get(position);
}
// 得到数据源
public List < User > getData() {
return mDiffer.getCurrentList();
}
class MyViewHolder extends RecyclerView.ViewHolder {
private TextView nameTv;
private TextView ageTv;
private TextView profileTv;
private TextView idTv;
public MyViewHolder(View view) {
super(view);
idTv = view.findViewById(R.id.id_tv);
nameTv = view.findViewById(R.id.name_tv);
ageTv = view.findViewById(R.id.age_tv);
profileTv = view.findViewById(R.id.profile_tv);
}
public void setData(User user) {
// 绑定数据
}
}
}
有以下几个点需要注意
(1)自实现 DiffUtil.ItemCallback,给出 item 差异性计算条件
写法①
areContentsTheSame()
中,默认 User 的内容是相同,逐个比较 User 的属性,只要有一项不同,则认为是不同的。
public class AsyncListDiffCallback extends DiffUtil.ItemCallback < User > {
@Override
public boolean areItemsTheSame(@NonNull User oldItem, @NonNull User newItem) {
// User的属性可能会变化,但ID is fixed(来自 Android官方文档)
return oldItem.getId() == newItem.getId();
}
@Override
public boolean areContentsTheSame(@NonNull User oldItem, @NonNull User newItem) {
// 默认内容是相同的,只要有一项不同,则返回false
// name
if (!oldItem.getName().equals(newItem.getName())) {
return false;
}
// age
if (oldItem.getAge() != newItem.getAge()) {
return false;
}
// profile
if (!oldItem.getProfile().equals(newItem.getProfile())) {
return false;
}
return true;
}
}
写法②
该写法参见 Android 官方文档。areContentsTheSame()
中,如果使用了对象的equals()
方法来比较二者是否相同,那 User 中必须重写euqals()
方法。
public class AsyncListDiffCallback extends DiffUtil.ItemCallback < User > {
@Override
public boolean areItemsTheSame(@NonNull User oldItem, @NonNull User newItem) {
// User的属性可能会变化,但ID is fixed(来自 Android官方文档)
return oldItem.getId() == newItem.getId();
}
@Override
public boolean areContentsTheSame(@NonNull User oldItem, @NonNull User newItem) {
return oldItem.equals(newItem);
}
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
User user = (User) o;
return age == user.age &&
Objects.equals(name, user.name) &&
Objects.equals(profile, user.profile);
}
@Override
public int hashCode() {
return Objects.hash(name, age, profile);
}
(2)将所有对数据的操作代理给 AsyncListDiffer,这个 Adapter 是没有 List 数据的
@Override
public int getItemCount() {
// mDiffer.getCurrentList()得到的数据集不会为null,但可能未empty
return mDiffer.getCurrentList().size();
}
// 向 Adapter 提交新的数据源
public void submitList(List < User > data) {
mDiffer.submitList(data);
}
// 得到数据源中的第position项
public User getItem(int position) {
return mDiffer.getCurrentList().get(position);
}
// 得到数据源
public List < User > getData() {
return mDiffer.getCurrentList();
}
(3)为 Adapter 创建构造函数,进行必要的初始化
- Adapter 需要的上下文,可传入可不传入
写法①
private Context mContext;
private LayoutInflater mLayoutInflater;
public AsyncListDiffAdapter(Context context) {
mContext = context;
mLayoutInflater = LayoutInflater.from(context);
}
@NonNull
@Override
public MyViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
View view = mLayoutInflater.inflate(R.layout.user_item, parent, false);
return new MyViewHolder(view);
}
写法②
@NonNull
@Override
public MyViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
View view = LayoutInflater.from(parent.getContext()).inflate(R.layout.user_item, parent, false);
return new MyViewHolder(view);
}
- 初始化 AsyncListDiff 实例
该实例的创建需要传入 Adapter 和 DiffUtil.ItemCallback 的实例
3. 首先给 Adapter 设置数据,让它先跑起来
private void initViews() {
mRecyclerView = findViewById(R.id.user_rv);
mRefreshBtn = findViewById(R.id.btn_refresh);
mRecyclerView.setLayoutManager(new LinearLayoutManager(this));
// 1,创建Adapter
mAdapter = new AsyncListDiffAdapter();
// 2,为RecyclerView设置适配器
mRecyclerView.setAdapter(mAdapter);
// 3,为Adapter提交数据源
List < User > data = initData();
mAdapter.submitList(data);
}
private List < User > initData() {
List < User > data = new ArrayList < > ();
data.add(new User(1, "福子", 10, "adfada"));
data.add(new User(2, "大牛", 10, "adfada"));
data.add(new User(1, "栓子", 10, "adfada"));
data.add(new User(4, "铁柱", 10, "adfada"));
data.add(new User(5, "钢蛋", 10, "adfada"));
return data;
}
4. AsyncListDiff 实现定向刷新
private void refreshData() {
// 新的数据源
List < User > oldData = mAdapter.getData();
List < User > newData = new ArrayList < > ();
for (int i = 0; i < oldData.size(); i++) {
newData.add(oldData.get(i).clone());
}
// 模拟新增数据
newData.add(new User(6, "赵子龙", 100, "一个神人"));
// 模拟数据修改
newData.get(0).setName("福子222");
newData.get(0).setProfile("这是一个有福的女子");
// 模拟数据移位
User user = newData.get(1);
newData.remove(user);
newData.add(user);
// 将新数据集设置给Adapter
mAdapter.submitList(newData);
}
四、评价
相比于普通的 Adapter,上述改造型 Adapter 中所有对数据的操作代理给 AsycListDiff,不是自己维护数据源的,调用
submitList()
更新数据源并刷新 UI。AsyncListDiffer 解决了 DiffUtil 的痛点,好用程度更上一层楼。
网友评论