RecyclerView各种更新功能总结

  • Post author:
  • Post category:其他


前言

RecyclerView提升性能很重要的一点就是支持局部更新效果,以前的ListView如果修改了数据通常要调用notifyDatasetChanged导致整个ListView内的布局都要重新刷新,现在RecyclerView除了全部刷新的方法之外还提供了单项、多条数据更新的回调方法。

增删改交换

方法 意义
notifyItemInserted(int item) 通知增加了单条数据
notifyItemRemoved(int item) 通知删除了单条数据
notifyItemChanged(int item) 通知修改了单条数据
notifyItemMoved(int from, int to) 通知移动了两条数据
notifyItemRangeInserted(int pos, int count) 通知增加了一批数据
notifyItemRangeRemoved(int pos, int count) 通知删除了一批数据
notifyDataSetChanged() 通知所有的数据发生了变化,前面的更新方式都有动画效果(如果用户设置了),这种更新方式没有动画效果

DiffUtils整体绑定

DiffUtils是Android7.0新引入的工具,用来对比当前的旧数据和新得到的数据之间的差异,最后通过上面的各种通知操作完成更新,这种实现优点在数据基本上相同的情况下只更新修改的少量数据,不必对整体的列表数据做刷新操作,而且还支持数据的刷新的各种动画效果。实现时首先需要定义对比新旧数据的回调接口,然后工具计算会返回差异结果,最后将差异结果提交个Adapter来刷新RecyclerView,刷新的时候发现数据有变化的条目所有的控件都会被重新设置内容,所以也就是整体绑定,如果用户希望只设置内容改变的控件可以使用后面提到的部分绑定。

// RecyclerView.Adapter的子类方法,负责更新所有数据
public void replace() {
    this.data = userList;
    DiffUtil.DiffResult diffResult = DiffUtil.calculateDiff(new DiffUtil.Callback() {
        // 旧数据的数量
        @Override
        public int getOldListSize() {
            return users.size();
        }

        // 新数据数量
        @Override
        public int getNewListSize() {
            return userList.size();
        }

        // 数据条目是否是同一条数据,这里使用名字来判断是否是同一条数据
        @Override
        public boolean areItemsTheSame(int oldItemPosition, int newItemPosition) {
            User user = users.get(oldItemPosition);
            User newUser = userList.get(newItemPosition);
            return user.getName().equalsIgnoreCase(newUser.getName());
        }

        // 同一条数据的内容是否发生过更改,这里对比User里的所有内容
        @Override
        public boolean areContentsTheSame(int oldItemPosition, int newItemPosition) {
            User user = users.get(oldItemPosition);
            User newUser = userList.get(newItemPosition);
            return user.equals(newUser);
        }
    }, true);

    // 得到差异结果之后发布到Adapter并更新界面
    diffResult.dispatchUpdatesTo(this);
}

public void replaceAll() {
    this.data = userList;
    notifyDataSetChanged();
}

调用这种方式实现的批量更新效果如下:

这里写图片描述

同时在使用前面提到的notifyDatasetChanged方法来更新数据,再看下这种更新方式的实现效果:

这里写图片描述

对比发现DiffUtils的实现效果用户体验更好,而且性能更好,不过计算过程有时候非常的耗时,通常用户获取数据都是在子线程中,可以先在子线程中计算完结果之后再到主线程中更新界面。

DiffUtils部分绑定

上面的虽然已经做到不需要整体数据全部刷新,但是对于更改的数据条目整条都需要更新以便,加入条目里很多地方都没改,只改了一个文案,其实刷新其他控件就没有必要了,DiffUtils的回调接口里提供了一个getPayload的方法返回实际被更新的部分,然后在Adapter.onBindViewHolder(position, viewHolder, payload)里用这个方法里只刷新改变了的控件内容,实现更加高效的部分绑定。

@Nullable
@Override
public Object getChangePayload(int oldItemPosition, int newItemPosition) {
    User user = users.get(oldItemPosition);
    User newUser = userList.get(newItemPosition);

    // 将相同的数据条目里不同的部分记录下来
    Map<Integer, String> map = new HashMap<>();
    if (!user.getName().equalsIgnoreCase(newUser.getName())) {
        map.put(R.id.name_attr, newUser.getName());
    }

    if (!user.getDesc().equalsIgnoreCase(newUser.getDesc())) {
        map.put(R.id.desc_attr, newUser.getDesc());
    }

    if (user.getPortrait() != newUser.getPortrait()) {
        map.put(R.id.portrait_attr, String.valueOf(newUser.getPortrait()));
    }

    // 将记录不同数据的map返回作为payload对象
    return map;
}

覆盖Adapter的onBindViewHolder方法,记得需要是三个参数的那个方法,最后那个参数是一个List的参数,如果当时payload对应的View没有被attach到RecyclerView上,这个对象列表就是空的,当然如果默认用户没返回payload它一样是空的但不是null。在这里获取返回的payload对象并且遍历它内部记录的不同属性值,更新对应的控件内容。

@Override
public void onBindViewHolder(RecyclerViewHolder holder, int position, List<Object> payloads) {
    // 如果没有payload对象,那么直接采用整体绑定
    if (payloads.isEmpty()) {
        super.onBindViewHolder(holder, position, payloads);
    } else {
        // 如果有payload对象那么根据记录的更改内容更新对应的控件内容
        Map<Integer, String> map = (Map<Integer, String>) payloads.get(0);
        for (int strId : map.keySet()) {
            switch (strId) {
                case R.id.name_attr:
                    holder.name.setText(map.get(R.id.name_attr));
                    break;
                case R.id.desc_attr:
                    holder.desc.setText(map.get(R.id.desc_attr));
                    break;
                case R.id.portrait_attr:
                    holder.portrait.setImageResource(Integer.parseInt(map.get(R.id.portrait_attr)));
                    break;
            }
        }
    }
}



版权声明:本文为xingzhong128原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。