DiffUtil

1咬展、前言

DIffUtils 是 Support-v7:24:2.0 中耸峭,更新的工具類澜躺。

它主要是為了配合 RecyclerView 使用蝉稳,通過比對(duì)新、舊兩個(gè)數(shù)據(jù)集的差異掘鄙,生成舊數(shù)據(jù)到新數(shù)據(jù)的最小變動(dòng)耘戚,然后對(duì)有變動(dòng)的數(shù)據(jù)項(xiàng),進(jìn)行局部刷新操漠。

當(dāng)然收津,DiffUtil 不僅只能配合 RecyclerView 使用,它實(shí)際上可以單獨(dú)用于比對(duì)兩個(gè)數(shù)據(jù)集,然后如何操作是可以定制的撞秋,那么在什么場景下使用长捧,就全憑我們自己發(fā)揮了。

2吻贿、DiffUtil

DiffUtil 在使用起來串结,主要需要關(guān)注幾個(gè)類:

  • DiffUtil.Callback:具體用于限定數(shù)據(jù)集比對(duì)規(guī)則。
  • DiffUtil.DiffResult:比對(duì)數(shù)據(jù)集之后舅列,返回的差異結(jié)果肌割。

2.1、DiffUtil.Callback

DiffUtil.Callback 主要就是為了限定兩個(gè)數(shù)據(jù)集中剧蹂,子項(xiàng)的比對(duì)規(guī)則声功。畢竟開發(fā)者面對(duì)的數(shù)據(jù)結(jié)構(gòu)多種多樣,既然沒法做一套通用的內(nèi)容比對(duì)方式宠叼,那么就將比對(duì)的規(guī)則先巴,交還給開發(fā)者來實(shí)現(xiàn)即可。

在 Callback 中冒冬,其實(shí)只需要實(shí)現(xiàn) 4 個(gè)方法:

  • getOldListSize():舊數(shù)據(jù)集的長度伸蚯。
  • getNewListSize():新數(shù)據(jù)集的長度
  • areItemsTheSame():判斷是否是同一個(gè)Item。
  • areContentsTheSame():如果是通一個(gè)Item简烤,此方法用于判斷是否同一個(gè) Item 的內(nèi)容也相同剂邮。

前兩個(gè)是獲取數(shù)據(jù)集長度的方法,這沒什么好說的横侦。但是后兩個(gè)方法挥萌,主要是為了對(duì)應(yīng)多布局的情況產(chǎn)生的,也就是存在多個(gè) viewType的情況枉侧。首先需要使用 areItemsTheSame() 方法比對(duì)是否來自同一個(gè) viewType 引瀑,然后再通過 areContentsTheSame() 方法比對(duì)其內(nèi)容是否也相等。

其實(shí) Callback 還有一個(gè) getChangePayload() 的方法榨馁,它可以在 ViewType 相同憨栽,但是內(nèi)容不相同的時(shí)候,用 payLoad 記錄需要在這個(gè) ViewHolder 中翼虫,然后去更新具體的View屑柔。

areItemsTheSame()、areContentsTheSame()珍剑、getChangePayload() 分別代表了不同量級(jí)的刷新掸宛。

首先會(huì)通過 areItemsTheSame()判斷當(dāng)前 position 下的ViewType 是否一致,如果不一致就表明當(dāng)前 position 下招拙,從數(shù)據(jù)到 UI 結(jié)構(gòu)上全部變化了旁涤,那么就不關(guān)心內(nèi)容(areContentsTheSame()翔曲、getChangePayload()不會(huì)被調(diào)用),直接更新就好了劈愚。如果一致的話瞳遍,那么其實(shí) View 是可以復(fù)用的,就還需要再通過 areContentsTheSame() 方法判斷其內(nèi)容是否一致菌羽,如果一致掠械,則表示是同一條數(shù)據(jù),不需要做額外的操作注祖。但是一旦不一致猾蒂,則還會(huì)調(diào)用 getChangePayload() 來標(biāo)記到底是哪個(gè)地方的不一樣,最終標(biāo)記需要更新的地方是晨,最終返回給 DiffResult 肚菠。

當(dāng)然,對(duì)性能要是要求沒那么高的情況下罩缴,是可以不使用 getChangedPayload() 方法的蚊逢。

2.2、DiffUtil.DiffResult

DiffUtil.DiffResult 其實(shí)就是 DiffUtil 通過 DiffUtil.Callback 計(jì)算出來箫章,兩個(gè)數(shù)據(jù)集的差異烙荷。它是可以直接使用在 RecyclerView 上的。如果有必要檬寂,也是可以通過實(shí)現(xiàn) ListUpdateCallback 接口终抽,來比對(duì)這些差異的。

3桶至、使用DiffUtil

介紹了 Callback 和 DiffResult 之后昼伴,其實(shí)就可以正常使用 DiffUtil 來進(jìn)行數(shù)據(jù)集的比對(duì)了。

在這個(gè)過程中镣屹,其實(shí)真的很簡單圃郊,只需要調(diào)用兩個(gè)方法:

DiffUtil.DiffResult diffResult = DiffUtil.calculateDiff(new DiffCallBack(oldDatas, newDatas), true);
diffResult.dispatchUpdatesTo(mAdapter);

calculateDiff 方法主要是用于通過一個(gè)具體的 DiffUtils.Callback 實(shí)現(xiàn)對(duì)象,來計(jì)算出兩個(gè)數(shù)據(jù)集差異的結(jié)果野瘦,得到 DiffUtil.DiffResult 描沟。
而 calculateDiff 的另外一個(gè)參數(shù)飒泻,用于標(biāo)記是否需要檢測 Item 的移動(dòng)鞭光。
dispatchUpdatesTo() 就是將這個(gè)數(shù)據(jù)集差異的結(jié)果,通過 Adapter 更新到 RecyclerView 上面泞遗。
實(shí)際上 dispatchUpdatesTo(Adapter) 惰许,也是使用的 ListUpdateCallback 這個(gè)接口,在其中獲得差異史辙,然后調(diào)用 Adapter 的對(duì)應(yīng)方法汹买。

public void dispatchUpdatesTo(final RecyclerView.Adapter adapter) {
    dispatchUpdatesTo(new ListUpdateCallback() {
        @Override
        public void onInserted(int position, int count) {
            adapter.notifyItemRangeInserted(position, count);
        }

        @Override
        public void onRemoved(int position, int count) {
            adapter.notifyItemRangeRemoved(position, count);
        }

        @Override
        public void onMoved(int fromPosition, int toPosition) {
            adapter.notifyItemMoved(fromPosition, toPosition);
        }

        @Override
        public void onChanged(int position, int count, Object payload) {
            adapter.notifyItemRangeChanged(position, count, payload);
        }
    });
}

4佩伤、實(shí)例

既然已經(jīng)說清楚了,那么我們開始上例子了晦毙。

功能很簡單生巡,有四個(gè)數(shù)據(jù)集,使用 RecyclerView 承載见妒,然后有一個(gè)按鈕孤荣,用于輪換的切換數(shù)據(jù)集。

4.1须揣、實(shí)現(xiàn) DiffUtil.Callback

public class AdapterDiffCallBack extends DiffUtil.Callback {
    private ArrayList<Bean> oldData;
    private ArrayList<Bean> newData;

    public AdapterDiffCallBack(ArrayList<Bean> oldData, ArrayList<Bean> newData) {
        this.oldData = oldData;
        this.newData = newData;
    }

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

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

    @Override
    public boolean areItemsTheSame(int oldItemPosition, int newItemPosition) {
        return oldData.get(oldItemPosition).getType() == newData.get(newItemPosition).getType();
    }

    @Override
    public boolean areContentsTheSame(int oldItemPosition, int newItemPosition) {
        return oldData.get(oldItemPosition).equals(newData.get(newItemPosition));
    }

    @Nullable
    @Override
    public Object getChangePayload(int oldItemPosition, int newItemPosition) {
        return super.getChangePayload(oldItemPosition, newItemPosition);
    }
}

4.2盐股、切換數(shù)據(jù)集

既然已經(jīng)有了 DiffUtil.Callback 的實(shí)現(xiàn)之后,我們就需要對(duì)切換數(shù)據(jù)集的點(diǎn)擊事件進(jìn)行處理了耻卡。

public void changeData(View view) {
    final ArrayList<Bean> oldData = dataArray[index];
    index++;
    index = index % dataArray.length;
    final ArrayList<Bean> newData = dataArray[index];
    mDatas1 = newData;

    DiffUtil.DiffResult result = DiffUtil.calculateDiff(new AdapterDiffCallBack(oldData,newData), false);
    result.dispatchUpdatesTo(recyclerView.getAdapter());
}

注意:Google 官方同時(shí)也指出疯汁,如果是對(duì)大數(shù)據(jù)集的比對(duì),最好是方在子線程中去完成計(jì)算卵酪,也就是其實(shí)是存在堵塞 UI 的情況的幌蚊。所以如果你遇見了使用 DiffUtil 之后,每次刷新有卡頓的情況凛澎,可以考慮是否數(shù)據(jù)集太大霹肝,是否應(yīng)該在子線程中完成計(jì)算。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末塑煎,一起剝皮案震驚了整個(gè)濱河市沫换,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌最铁,老刑警劉巖讯赏,帶你破解...
    沈念sama閱讀 207,113評(píng)論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異冷尉,居然都是意外死亡漱挎,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,644評(píng)論 2 381
  • 文/潘曉璐 我一進(jìn)店門雀哨,熙熙樓的掌柜王于貴愁眉苦臉地迎上來磕谅,“玉大人,你說我怎么就攤上這事雾棺〔布校” “怎么了?”我有些...
    開封第一講書人閱讀 153,340評(píng)論 0 344
  • 文/不壞的土叔 我叫張陵捌浩,是天一觀的道長放刨。 經(jīng)常有香客問我,道長尸饺,這世上最難降的妖魔是什么进统? 我笑而不...
    開封第一講書人閱讀 55,449評(píng)論 1 279
  • 正文 為了忘掉前任助币,我火速辦了婚禮,結(jié)果婚禮上螟碎,老公的妹妹穿的比我還像新娘眉菱。我一直安慰自己,他們只是感情好掉分,可當(dāng)我...
    茶點(diǎn)故事閱讀 64,445評(píng)論 5 374
  • 文/花漫 我一把揭開白布倍谜。 她就那樣靜靜地躺著,像睡著了一般叉抡。 火紅的嫁衣襯著肌膚如雪尔崔。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 49,166評(píng)論 1 284
  • 那天褥民,我揣著相機(jī)與錄音季春,去河邊找鬼。 笑死消返,一個(gè)胖子當(dāng)著我的面吹牛载弄,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播撵颊,決...
    沈念sama閱讀 38,442評(píng)論 3 401
  • 文/蒼蘭香墨 我猛地睜開眼宇攻,長吁一口氣:“原來是場噩夢(mèng)啊……” “哼!你這毒婦竟也來了倡勇?” 一聲冷哼從身側(cè)響起逞刷,我...
    開封第一講書人閱讀 37,105評(píng)論 0 261
  • 序言:老撾萬榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎妻熊,沒想到半個(gè)月后夸浅,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 43,601評(píng)論 1 300
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡扔役,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,066評(píng)論 2 325
  • 正文 我和宋清朗相戀三年帆喇,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片亿胸。...
    茶點(diǎn)故事閱讀 38,161評(píng)論 1 334
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡坯钦,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出侈玄,到底是詐尸還是另有隱情婉刀,我是刑警寧澤,帶...
    沈念sama閱讀 33,792評(píng)論 4 323
  • 正文 年R本政府宣布拗馒,位于F島的核電站路星,受9級(jí)特大地震影響溯街,放射性物質(zhì)發(fā)生泄漏诱桂。R本人自食惡果不足惜洋丐,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,351評(píng)論 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望挥等。 院中可真熱鬧友绝,春花似錦、人聲如沸肝劲。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,352評(píng)論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽辞槐。三九已至掷漱,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間榄檬,已是汗流浹背卜范。 一陣腳步聲響...
    開封第一講書人閱讀 31,584評(píng)論 1 261
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留鹿榜,地道東北人海雪。 一個(gè)月前我還...
    沈念sama閱讀 45,618評(píng)論 2 355
  • 正文 我出身青樓,卻偏偏與公主長得像舱殿,于是被迫代替她去往敵國和親奥裸。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 42,916評(píng)論 2 344

推薦閱讀更多精彩內(nèi)容