Android開發Diffutils打造不一樣的recyclerview
簡述
DiffUtil是recyclerview support library v7 24.2.0版本中新增的類,根據Google官方文檔的介紹,DiffUtil的作用是比較兩個數據列表并能計算出一系列將舊數據表轉換成新數據表的操作。這個概念比較抽象,換一種方式理解,DiffUtil是一個工具類,當你的RecyclerView需要更新數據時,將新舊數據集傳給它,它就能快速告知adapter有哪些數據需要更新。就相當于如果改變了就對某個item刷新,沒改變就沒刷新,可以簡稱為局部刷新。
無腦刷新VS局部刷新
首先我們需要知道DiffUtil使用Eugene W. Myers的Difference算法來計算出將一個數據集轉換為另一個的最小更新量,也就是用最簡單的方式將一個數據集轉換為另一個。DiffUtil還可以識別一項數據在數據集中的移動。但該算法不能檢測移動的item,所以Google在其基礎上改進支持檢測移動項目,但是檢測移動項目,會更耗性能。 下面是谷歌官網給出的在Nexus 5X M系統上進行運算的時長:
- 100項數據,10處改動:平均值0.39ms,中位數:0.35ms。
- 100項數據,100處改動:
- 打開了移位識別時:平均值:3.82ms,中位數:3.75ms。
- 關閉了移位識別時:平均值:2.09ms,中位數:2.06ms。
- 1000項數據,50處改動:
- 打開了移位識別時:平均值:4.67ms,中位數:4.59ms。
- 關閉了移位識別時:平均值:3.59ms,中位數:3.50ms。 </ul> </li>
- 1000項數據,200處改動:
- 打開了移位識別時:平均值:27.07ms,中位數:26.92ms。
- 關閉了移位識別時:平均值:13.54ms,中位數:13.36ms。 </ul> </li> </ul>
使用姿勢
首先,我們得學會如何使用它,第二,我們需要知道用什么姿勢來使用它,姿勢不對,全都白費。
Diffutils.Callback
我們先看下Diffutils的callback的源碼:
/**
* A Callback class used by DiffUtil while calculating the diff between two lists. * 當使用Diffutils的時候,這是一個計算2個list不同的回調函數 */ public abstract static class Callback { /** * Returns the size of the old list. * 得到老的數據源大小 */ public abstract int getOldListSize(); /** * Returns the size of the new list. * 得到新的數據源大小 */ public abstract int getNewListSize(); /** * Called by the DiffUtil to decide whether two object represent the same Item. * For example, if your items have unique ids, this method should check their id equality. * <p> * 被DiffUtil調用,用來判斷 兩個對象是否是相同的Item。 * 例如,如果你的Item有唯一的id字段,這個方法就判斷id是否相等。 * @param oldItemPosition The position of the item in the old list * 舊數據的item * @param newItemPosition The position of the item in the new list * 新數據的item * @return True if the two items represent the same object or false if they are different. * true代表著2個item內容相同,否則,不同 */ public abstract boolean areItemsTheSame(int oldItemPosition, int newItemPosition); /** * Called by the DiffUtil when it wants to check whether two items have the same data. * DiffUtil uses this information to detect if the contents of an item has changed. * DiffUtil uses this method to check equality instead of {@link Object#equals(Object)} * so that you can change its behavior depending on your UI. * For example, if you are using DiffUtil with a * {@link android.support.v7.widget.RecyclerView.Adapter RecyclerView.Adapter}, you should * return whether the items' visual representations are the same. * This method is called only if {@link #areItemsTheSame(int, int)} returns * {@code true} for these items. * 被DiffUtil調用,用來檢查 兩個item是否含有相同的數據 * DiffUtil用返回的信息(true/false)來檢測當前item的內容是否發生了變化 * 所以你可以根據你的UI去改變它的返回值 * DiffUtil 用這個方法替代equals方法去檢查是否相等。 * 例如,如果你用RecyclerView.Adapter 配合DiffUtil使用,你需要返回Item的視覺表現是否相同。 * 這個方法僅僅在areItemsTheSame()返回true時,才調用。 * @param oldItemPosition The position of the item in the old list * 舊數據的item * @param newItemPosition The position of the item in the new list which replaces the * oldItem * 新數據某個替換了舊數據的item * @return True if the contents of the items are the same or false if they are different. * true代表著2個item內容相同,否則,不同 */ public abstract boolean areContentsTheSame(int oldItemPosition, int newItemPosition); /** * When {@link #areItemsTheSame(int, int)} returns {@code true} for two items and * {@link #areContentsTheSame(int, int)} returns false for them, DiffUtil * calls this method to get a payload about the change. * <p> * For example, if you are using DiffUtil with {@link RecyclerView}, you can return the * particular field that changed in the item and your * {@link android.support.v7.widget.RecyclerView.ItemAnimator ItemAnimator} can use that * information to run the correct animation. * <p> * Default implementation returns {@code null}. * * 當areItemsTheSame(int, int)返回true,且areContentsTheSame(int, int)返回false時,DiffUtils會回調此方法, * 去得到這個Item(有哪些)改變的payload。 * 例如,如果你用RecyclerView配合DiffUtils,你可以返回 這個Item改變的那些字段,可以用那些信息去執行正確的動畫 * 默認的實現是返回null * @param oldItemPosition The position of the item in the old list * 在老數據源的postion * @param newItemPosition The position of the item in the new list * 在新數據源的position * @return A payload object that represents the change between the two items. * 返回 一個 代表著新老item的改變內容的 payload對象, */ @Nullable public Object getChangePayload(int oldItemPosition, int newItemPosition) { return null; } }</code></pre>
上面是整個Callback的說明,我已標注中文,可以先理解下,我們接下來在看。
以正確姿勢使用
上面我簡單介紹了Callback的注解。現在我們通過繼承來實現自己的。
public class SWDiffCallBack extends DiffUtil.Callback { private List<String> olddatas; private List<String> newDatas; public SWDiffCallBack(List<String> olddatas, List<String> newDatas) { this.olddatas = olddatas; this.newDatas = newDatas; } public int getOldListSize() { return olddatas.size(); } public int getNewListSize() { return newDatas.size(); } public boolean areItemsTheSame(int oldItemPosition, int newItemPosition) { return olddatas.get(oldItemPosition).equals(newDatas.get(newItemPosition)); } public boolean areContentsTheSame(int oldItemPosition, int newItemPosition) { return olddatas.get(oldItemPosition).equals(newDatas.get(newItemPosition)); } }
簡單的自定義Callback我們已經實現了,下面我們來看看,是如何使用的呢。
newlist = new ArrayList<>(); for (int i = 1; i < list.size(); i++) { newlist.add(list.get(i) + ""); } newlist.add(5,list.size() + j + ""); j++; //普通刷新 // list=newlist; // adapter.setList(newlist); // adapter.notifyDataSetChanged(); //強大的局部刷新 DiffUtil.DiffResult diffResult = DiffUtil.calculateDiff(new SWDiffCallBack(list, newlist), true); //利用DiffUtil.DiffResult對象的dispatchUpdatesTo()方法,傳入RecyclerView的Adapter //別忘了將新數據給Adapter list = newlist; adapter.setList(list); diffResult.dispatchUpdatesTo(adapter);
看起來比全局刷新代碼多好多?不要緊,我們來看看它的效果圖在說。
效果圖
一波666,還有自帶的動畫。我們在看看正常的全局刷新
看起來比Diffutils簡單很多,不過當你真正在網絡請求使用的時候,會發現完全不一樣,整個屏幕會閃一下~沒錯,就是閃一下。之前測試讓我改這種bug,我也是有點蒙,沒法改啊- - 除非重寫。。所以,Diffutils還是很強大的,demo會在文末放出,你們可以自己下載跑跑看。
姿勢進階使用
我們看了之前的使用,是不是發現,我之前解決Callback的時候,和我寫demo的時候,少了一個方法,沒錯,就是getChangePayload。這個是做什么的呢?就是我整個列表的某個item只有一個數據改變的時候,我只要去替換那一個數據,而不需要替換整個item。
好了,我們直接寫個測試的demo。代碼很簡單:
public class SWLoadDiffCallBack extends SWDiffCallBack{ // private List<Bean> oldList; // private List<Bean> newList; public SWLoadDiffCallBack(List<String> olddatas, List<String> newDatas) { super(olddatas, newDatas); } public Object getChangePayload(int oldItemPosition, int newItemPosition) { // Bean oldItem = oldList.get(oldItemPosition); // Bean newItem = newList.get(newItemPosition); // Bundle diffBundle = new Bundle(); // if (!newItem.header.equals(oldItem.header)) { // diffBundle.putString(KEY_HEEDER, newItem.header); // } // if (!newItem.footer.equals(oldItem.footer)) { // diffBundle.putString(KEY_FOOTER, newItem.footer); // } // if (diffBundle.size() == 0) return null; // return diffBundle; }
這個我們是寫個Bean,里面我們放入header和footer,然后進行逐個對比,這邊我就不寫demo了。相信這種寫法你們應該能看懂。代碼我已上傳到csdn:點擊下載demo
總結
1.Diffutils很適合各種刷新操作,我已經把他整合到我的開源中,你們可以自己去看。
2.Diffutils實現的局部刷新內部含有他自帶的動畫效果,所以我們無需去處理,而且看起來也比較美觀~
3.DiffUtil可用于高效進行RecyclerView的數據更新,但DiffUtil本身的作用是計算數據集的最小更新。DiffUtil有強大的算法支撐,可以利用DiffUtil完成許多其他功能。
來自:http://blog.csdn.net/sw950729/article/details/70052693