android局部更新(RecyclerView+ DiffUtil)

5,147 阅读3分钟

在android中listView/RecyclerView是最常用的ui控件,最常见的交互多是list的刷新,刷新和加载的交互涉及到adapter的刷新—-即notifyDataSetChanged()全局刷新
但是notifychaged有一些不可避免的弊端:
1.不会触发 RecyclerView 的局部更新的动画。
2.性能低,会刷新整个 RecyclerView 可视区域。
之前google有发布一个list中item局部刷新的Api:DiffUtil

DiffUtil完美解决了全局刷新的弊端;提高了UI 和data交互的效率和性能.下面 简单介绍下DiffUtil:
主要涉及两个类:
DiffUtil.Callback:具体用于限定数据集比对规则。
DiffUtil.DiffResult:比对数据集之后,返回的差异结果。

1.DiffUtil.Callback(对比新旧数据)

其中: 在 Callback 中,其实只需要实现 4 个方法:

getOldListSize():旧数据集的长度。
getNewListSize():新数据集的长度
areItemsTheSame():判断是否是同一个Item。
areContentsTheSame():如果是通一个Item,此方法用于判断是否同一个 Item 的内容也相同。

前两个是获取数据集长度的方法,这没什么好说的。但是后两个方法,主要是为了对应多布局的情况产生的,也就是存在多个 viewType 和多个 ViewHodler 的情况。首先需要使用 areItemsTheSame() 方法比对是否来自同一个 viewType(也就是同一个 ViewHolder ) ,然后再通过 areContentsTheSame() 方法比对其内容是否也相等。

其实 Callback 还有一个 getChangePayload() 的方法,它可以在 ViewType 相同,但是内容不相同的时候,用 payLoad 记录需要在这个 ViewHolder 中,具体需要更新的View。

areItemsTheSame()、areContentsTheSame()、getChangePayload() 分别代表了不同量级的刷新。

首先会通过 areItemsTheSame() 判断当前 position 下,ViewType 是否一致,如果不一致就表明当前 position 下,从数据到 UI 结构上全部变化了,那么就不关心内容,直接更新就好了。如果一致的话,那么其实 View 是可以复用的,就还需要再通过 areContentsTheSame() 方法判断其内容是否一致,如果一致,则表示是同一条数据,不需要做额外的操作。但是一旦不一致,则还会调用 getChangePayload() 来标记到底是哪个地方的不一样,最终标记需要更新的地方,最终返回给 DiffResult 。

当然,对性能要是要求没那么高的情况下,是可以不使用 getChangedPayload() 方法的。

2.DiffUtil.DiffResult(属性adapter 配合RecyclerView使用)

通过DiffUtil.Callback计算出差异然后直接作用域RecyclerView的adapter进行刷新

3代码实现

首先来看自己封装的一个RecyDiffCallback(继承自DiffUtil.Callback)

package com.example.diffutil.widget;

import android.support.v7.util.DiffUtil;

import java.util.List;

/**
 * Created by houruixiang on 2017/8/21.
 */

public class RecyDiffCallback extends DiffUtil.Callback {

    private List<String> newList;
    private List<String> oldList;

    public RecyDiffCallback(List<String> oldList, List<String> newList) {
        this.oldList = oldList;
        this.newList = newList;
    }

    @Override
    public int getOldListSize() {
        return oldList.size();
    }

    @Override
    public int getNewListSize() {
        return newList.size();
    }

    @Override
    public boolean areItemsTheSame(int oldItemPosition, int newItemPosition) {
        return oldList.get(oldItemPosition).getClass().equals(newList.get(newItemPosition).getClass());
    }

    @Override
    public boolean areContentsTheSame(int oldItemPosition, int newItemPosition) {
        String oldStr = oldList.get(oldItemPosition);
        String newStr = newList.get(newItemPosition);
        return oldStr.equals(newStr);
    }
}

以上代码是自己封装的,在构造中传入新旧数据,然后对比data的长度,接着对比item的type是否相同;最后对比内容; 下面看MainActivity中的核心代码

@Override
    public void onClick(View view) {
        mOld = mData.get(index);
        index++;
        index = index % mData.size();
        mNew = mData.get(index);
        Log.i("==onClick",String.valueOf(index));
        recyAdapter.setData(mData.get(index));
        DiffUtil.DiffResult diffResult = DiffUtil
                .calculateDiff(new RecyDiffCallback(mOld, mNew),true);

        diffResult.dispatchUpdatesTo(recyAdapter);

        //mList.setAdapter(recyAdapter);

    }
    在代码中setData到adapter中
recyAdapter.setData(mData.get(index));
    然后进行新旧data的对比
  DiffUtil.DiffResult diffResult = DiffUtil
                .calculateDiff(new RecyDiffCallback(mOld, mNew),true);

进行新旧数据集的对比 ,若三重对比(上面有介绍),然后进行diff的局部刷新:

diffResult.dispatchUpdatesTo(recyAdapter);

到这里RecyClerView+DiffUtil的局部刷新就介绍完了 是不是好用到飞起 ; 希望共同进步,欢迎大家宝贵的意见;