美文网首页
Android——AsyncListDiffer

Android——AsyncListDiffer

作者: 四喜汤圆 | 来源:发表于2019-08-25 01:33 被阅读0次

    AsyncListDiffer-RecyclerView最好的伙伴的学习笔记

    一、作用

    1. DiffUtil.calculateDiff()方法运行在主线程中,当新旧 List 比较大时,该方法会阻塞主线程。
    2. 计算出 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 对应的实体类

    Android——DiffUtil

    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 的痛点,好用程度更上一层楼。

    参考文献

    Android Dev-AsyncListDiffer

    相关文章

      网友评论

          本文标题:Android——AsyncListDiffer

          本文链接:https://www.haomeiwen.com/subject/yyrvsctx.html