Android開發學習之路-DiffUtil使用教程

来源:http://www.cnblogs.com/Fndroid/archive/2016/08/20/5789470.html
-Advertisement-
Play Games

谷歌最近更新了Support Library 24.2.0,而DiffUtil就是在這個版本添加的一個工具類。 DiffUtil是一個查找集合變化的工具類,是搭配RecyclerView一起使用的,如果你還不瞭解RecyclerView,可以閱讀一些資料或者我的博客:RecyclerView使用初探 ...


谷歌最近更新了Support Library 24.2.0,而DiffUtil就是在這個版本添加的一個工具類。

DiffUtil是一個查找集合變化的工具類,是搭配RecyclerView一起使用的,如果你還不瞭解RecyclerView,可以閱讀一些資料或者我的博客:RecyclerView使用初探

根據慣例,先放效果圖:

可以看到,當我們點擊按鈕的時候,這個RecyclerView所顯示的集合發生了改變,有的元素被增加了(8.Jason),也有的元素被移動了(3.Rose),甚至是被修改了(2.Fndroid)。RecyclerView對於每個Item的動畫是以不同方式刷新的:

  • notifyItemInserted
  • notifyItemChanged
  • notifyItemMoved
  • notifyItemRemoved

而對於連續的幾個Item的刷新,可以調用:

  • notifyItemRangeChanged
  • notifyItemRangeInserted
  • notifyItemRangeRemoved

而由於集合發生變化的時候,只可以調用notifyDataSetChanged方法進行整個界面的刷新,並不能根據集合的變化為每一個變化的元素添加動畫。所以這裡就有了DiffUtil來解決這個問題。

DiffUtil的作用,就是找出集合中每一個Item發生的變化,然後對每個變化給予對應的刷新。

這個DiffUtil使用的是Eugene Myers的差別演算法,這個演算法本身不能檢查到元素的移動,也就是移動只能被算作先刪除、再增加,而DiffUtil是在演算法的結果後再進行一次移動檢查。假設在不檢測元素移動的情況下,演算法的時間複雜度為O(N + D2),而檢測元素移動則複雜度為O(N2)。所以,如果集合本身就已經排好序,可以不進行移動的檢測提升效率。

 

下麵我們一起來看看這個工具怎麼用。

首先對於每個Item,數據是一個Student對象:

 1 class Student {
 2     private String name;
 3     private int num;
 4 
 5     public Student(String name, int num) {
 6         this.name = name;
 7         this.num = num;
 8     }
 9 
10     public String getName() {
11         return name;
12     }
13 
14     public void setName(String name) {
15         this.name = name;
16     }
17 
18     public int getNum() {
19         return num;
20     }
21 
22     public void setNum(int num) {
23         this.num = num;
24     }
25 }

接著我們定義佈局(省略)和適配器:

 1     class MyAdapter extends RecyclerView.Adapter {
 2         private ArrayList<Student> data;
 3 
 4         ArrayList<Student> getData() {
 5             return data;
 6         }
 7 
 8         void setData(ArrayList<Student> data) {
 9             this.data = new ArrayList<>(data);
10         }
11 
12         @Override
13         public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
14             View itemView = LayoutInflater.from(RecyclerViewActivity.this).inflate(R.layout.itemview, null);
15             return new MyViewHolder(itemView);
16         }
17 
18         @Override
19         public void onBindViewHolder(RecyclerView.ViewHolder holder, int position) {
20             MyViewHolder myViewHolder = (MyViewHolder) holder;
21             Student student = data.get(position);
22             myViewHolder.tv.setText(student.getNum() + "." + student.getName());
23         }
24 
25         @Override
26         public int getItemCount() {
27             return data.size();
28         }
29 
30         class MyViewHolder extends RecyclerView.ViewHolder {
31             TextView tv;
32 
33             MyViewHolder(View itemView) {
34                 super(itemView);
35                 tv = (TextView) itemView.findViewById(R.id.item_tv);
36             }
37         }
38     }

初始化數據集合:

 1     private void initData() {
 2         students = new ArrayList<>();
 3         Student s1 = new Student("John", 1);
 4         Student s2 = new Student("Curry", 2);
 5         Student s3 = new Student("Rose", 3);
 6         Student s4 = new Student("Dante", 4);
 7         Student s5 = new Student("Lunar", 5);
 8         students.add(s1);
 9         students.add(s2);
10         students.add(s3);
11         students.add(s4);
12         students.add(s5);
13     }

接著實例化Adapter並設置給RecyclerView:

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_recycler_view);
        initData();
        recyclerView = (RecyclerView) findViewById(R.id.rv);
        recyclerView.setLayoutManager(new LinearLayoutManager(this));
        adapter = new MyAdapter();
        adapter.setData(students);
        recyclerView.setAdapter(adapter);
    }

這些內容都不是本篇的內容,但是,需要註意到的一個地方是Adapter的定義:

 1     class MyAdapter extends RecyclerView.Adapter {
 2         private ArrayList<Student> data;
 3 
 4         ArrayList<Student> getData() {
 5             return data;
 6         }
 7 
 8         void setData(ArrayList<Student> data) {
 9             this.data = new ArrayList<>(data);
10         }
11 
12         // 省略部分代碼
13          ......  
14     }   

這裡的setData方法並不是直接將ArrayList的引用保存,而是重新的建立一個ArrayList,先記著,後面會解釋為什麼要這樣做


DiffUtil的使用方法:

當滑鼠按下時,修改ArrayList的內容:

 1     public void change(View view) {
 2         students.set(1, new Student("Fndroid", 2));
 3         students.add(new Student("Jason", 8));
 4         Student s2 = students.get(2);
 5         students.remove(2);
 6         students.add(s2);
 7 
 8         ArrayList<Student> old_students = adapter.getData();
 9         DiffUtil.DiffResult result = DiffUtil.calculateDiff(new MyCallback(old_students, students), true);
10         adapter.setData(students);
11         result.dispatchUpdatesTo(adapter);
12     }

2-6行是對集合進行修改,第8行先獲取到adapter中的集合為舊的數據。

重點看第9行調用DiffUtil.calculateDiff方法來計算集合的差別,這裡要傳入一個CallBack介面的實現類(用於指定計算的規則)並且把新舊數據都傳遞給這個介面的實現類,最後還有一個boolean類型的參數,這個參數指定是否需要進行Move的檢測,如果不需要,如果有Item移動了,會被認為是先remove,然後insert。這裡指定為true,所以就有了動圖顯示的移動效果。

第10行重新將新的數據設置給Adapter。

第11行調用第9行得到的DiffResult對象的dispatchUpdatesTo方法通知RecyclerView刷新對應發生變化的Item。

這裡回到上面說的setData方法,因為我們在這裡要區分兩個集合,如果在setData方法中直接保存引用,那麼在2-6行的修改就直接修改了Adapter中的集合了(Java知識)。

如果設置不檢查Item的移動,效果如下:

 

接著我們看看CallBack介面的實現類如何定義:

 1     private class MyCallback extends DiffUtil.Callback {
 2         private ArrayList<Student> old_students, new_students;
 3 
 4         MyCallback(ArrayList<Student> data, ArrayList<Student> students) {
 5             this.old_students = data;
 6             this.new_students = students;
 7         }
 8 
 9         @Override
10         public int getOldListSize() {
11             return old_students.size();
12         }
13 
14         @Override
15         public int getNewListSize() {
16             return new_students.size();
17         }
18 
19         // 判斷Item是否已經存在
20         @Override
21         public boolean areItemsTheSame(int oldItemPosition, int newItemPosition) {
22             return old_students.get(oldItemPosition).getNum() == new_students.get(newItemPosition).getNum();
23         }
24 
25         // 如果Item已經存在則會調用此方法,判斷Item的內容是否一致
26         @Override
27         public boolean areContentsTheSame(int oldItemPosition, int newItemPosition) {
28             return old_students.get(oldItemPosition).getName().equals(new_students.get(newItemPosition).getName());
29         }
30     }

這裡根據學號判斷是否同一個Item,根據姓名判斷這個Item是否有被修改。

 

這裡註意:如果RecyclerView中載入了大量數據,那麼演算法可能不會馬上完成,要註意ANR的問題,可以開啟單獨的線程進行計算。


您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • 為什麼需要這些?主要是因為this,來看看this乾的好事。 box.onclick = function(){ function fn(){ alert(this); } fn();}; 我們原本以為這裡面的this指向的是box,然而卻是Window。一般我們這樣解決: box.onclick ...
  • 近年來隨著操作系統的升級以及各種新技術的開發普及,拋棄低版本IE已經是大勢所趨,這對於前端人員來時是個好消息,可以不用花費太多的時間來做低版本的相容,很多站點採用給予低版本IE以提示的方式(恩,很友好很人道)給游客,一般是在header上給一個提示,腳本檢測如下: 這裡的重點是使用了jsBOM的na ...
  • 現在有一個需求如下圖: 產品經理說Card Number只能讓輸入數字(中間的空格是格式自加的,也是用js實現的),有時候我腦海中出現了個聲音,啥玩意,加個type=number不就行了,事實發現圖樣圖森破了,先不說type=number後面會有個上下標(雖然用css可幹掉),但是這個類型是支持科學 ...
  • 寫在前面 最近在工作中接觸到angular模塊化打包載入的一些內容,感覺中間踩了一些坑,在此標記一下. 項目背景: 項目主要用到angularJs作為前端框架,項目之前發佈的時候會把所有的前端腳本打包壓縮到一個文件中,在頁面初次訪問的時候載入,造成頁面初始載入緩慢,在此基礎上,提出按需載入,即只有當 ...
  • 在 葉小釵 的博客中看到一段關於遍歷的代碼 jQuery 中的each 不僅僅是用來遍歷jQuery對象,還可以用來作為合併介面。 其中就利用了$.each(fn)的特性,jQuery 源碼中 : 為obj 執行回調函數 callback。 裡面的巧妙之處在於: 在為obj執行回調函數的時候,回調函 ...
  • Javascript框架在處理seo方面存在問題,因為爬蟲在檢索seo信息的時候會讀不了js給其賦的值,導致搜索引擎收錄不了或者收錄了無效的信息,比如收錄的可能是title={{title}}這樣的,下麵先說如何在路由跳轉時修改頁面的seo信息,現在spa跳轉一般用route-ui了,就以這個為基礎 ...
  • 1. 閉包的概念 據我們所知,局部變數在函數退出之後就不占據記憶體空間,但存在一種特殊的函數,能使局部變數在函數退出之後繼續占據記憶體,為外部函數所調用,這個特殊的函數就是 。那麼閉包是怎麼做到將局部函數一直占據記憶體的呢?看看下麵的例子。 function a(){ var Aitem=2;//定義局部 ...
  • 第一種: 第二種: 第三種: ...
一周排行
    -Advertisement-
    Play Games
  • 移動開發(一):使用.NET MAUI開發第一個安卓APP 對於工作多年的C#程式員來說,近來想嘗試開發一款安卓APP,考慮了很久最終選擇使用.NET MAUI這個微軟官方的框架來嘗試體驗開發安卓APP,畢竟是使用Visual Studio開發工具,使用起來也比較的順手,結合微軟官方的教程進行了安卓 ...
  • 前言 QuestPDF 是一個開源 .NET 庫,用於生成 PDF 文檔。使用了C# Fluent API方式可簡化開發、減少錯誤並提高工作效率。利用它可以輕鬆生成 PDF 報告、發票、導出文件等。 項目介紹 QuestPDF 是一個革命性的開源 .NET 庫,它徹底改變了我們生成 PDF 文檔的方 ...
  • 項目地址 項目後端地址: https://github.com/ZyPLJ/ZYTteeHole 項目前端頁面地址: ZyPLJ/TreeHoleVue (github.com) https://github.com/ZyPLJ/TreeHoleVue 目前項目測試訪問地址: http://tree ...
  • 話不多說,直接開乾 一.下載 1.官方鏈接下載: https://www.microsoft.com/zh-cn/sql-server/sql-server-downloads 2.在下載目錄中找到下麵這個小的安裝包 SQL2022-SSEI-Dev.exe,運行開始下載SQL server; 二. ...
  • 前言 隨著物聯網(IoT)技術的迅猛發展,MQTT(消息隊列遙測傳輸)協議憑藉其輕量級和高效性,已成為眾多物聯網應用的首選通信標準。 MQTTnet 作為一個高性能的 .NET 開源庫,為 .NET 平臺上的 MQTT 客戶端與伺服器開發提供了強大的支持。 本文將全面介紹 MQTTnet 的核心功能 ...
  • Serilog支持多種接收器用於日誌存儲,增強器用於添加屬性,LogContext管理動態屬性,支持多種輸出格式包括純文本、JSON及ExpressionTemplate。還提供了自定義格式化選項,適用於不同需求。 ...
  • 目錄簡介獲取 HTML 文檔解析 HTML 文檔測試參考文章 簡介 動態內容網站使用 JavaScript 腳本動態檢索和渲染數據,爬取信息時需要模擬瀏覽器行為,否則獲取到的源碼基本是空的。 本文使用的爬取步驟如下: 使用 Selenium 獲取渲染後的 HTML 文檔 使用 HtmlAgility ...
  • 1.前言 什麼是熱更新 游戲或者軟體更新時,無需重新下載客戶端進行安裝,而是在應用程式啟動的情況下,在內部進行資源或者代碼更新 Unity目前常用熱更新解決方案 HybridCLR,Xlua,ILRuntime等 Unity目前常用資源管理解決方案 AssetBundles,Addressable, ...
  • 本文章主要是在C# ASP.NET Core Web API框架實現向手機發送驗證碼簡訊功能。這裡我選擇是一個互億無線簡訊驗證碼平臺,其實像阿裡雲,騰訊雲上面也可以。 首先我們先去 互億無線 https://www.ihuyi.com/api/sms.html 去註冊一個賬號 註冊完成賬號後,它會送 ...
  • 通過以下方式可以高效,並保證數據同步的可靠性 1.API設計 使用RESTful設計,確保API端點明確,並使用適當的HTTP方法(如POST用於創建,PUT用於更新)。 設計清晰的請求和響應模型,以確保客戶端能夠理解預期格式。 2.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...