編輯:關於Android編程
寫在前面的話
DiffUtil是一個查找集合變化的工具類,是搭配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對象:
class Student { private String name; private int num; public Student(String name, int num) { this.name = name; this.num = num; } public String getName() { return name; } public void setName(String name) { this.name = name; } public int getNum() { return num; } public void setNum(int num) { this.num = num; } }
接著我們定義布局(省略)和適配器:
class MyAdapter extends RecyclerView.Adapter { private ArrayList<Student> data; ArrayList<Student> getData() { return data; } void setData(ArrayList<Student> data) { this.data = new ArrayList<>(data); } @Override public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) { View itemView = LayoutInflater.from(RecyclerViewActivity.this).inflate(R.layout.itemview, null); return new MyViewHolder(itemView); } @Override public void onBindViewHolder(RecyclerView.ViewHolder holder, int position) { MyViewHolder myViewHolder = (MyViewHolder) holder; Student student = data.get(position); myViewHolder.tv.setText(student.getNum() + "." + student.getName()); } @Override public int getItemCount() { return data.size(); } class MyViewHolder extends RecyclerView.ViewHolder { TextView tv; MyViewHolder(View itemView) { super(itemView); tv = (TextView) itemView.findViewById(R.id.item_tv); } } }
初始化數據集合:
private void initData() { students = new ArrayList<>(); Student s1 = new Student("John", 1); Student s2 = new Student("Curry", 2); Student s3 = new Student("Rose", 3); Student s4 = new Student("Dante", 4); Student s5 = new Student("Lunar", 5); students.add(s1); students.add(s2); students.add(s3); students.add(s4); students.add(s5); }
接著實例化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的定義:
class MyAdapter extends RecyclerView.Adapter { private ArrayList<Student> data; ArrayList<Student> getData() { return data; } void setData(ArrayList<Student> data) { this.data = new ArrayList<>(data); } // 省略部分代碼 ...... }
這裡的setData
方法並不是直接將ArrayList的引用保存,而是重新的建立一個ArrayList,先記著,後面會解釋為什麼要這樣做。
DiffUtil的使用方法:
當鼠標按下時,修改ArrayList的內容:
public void change(View view) { students.set(1, new Student("Fndroid", 2)); students.add(new Student("Jason", 8)); Student s2 = students.get(2); students.remove(2); students.add(s2); ArrayList<Student> old_students = adapter.getData(); DiffUtil.DiffResult result = DiffUtil.calculateDiff(new MyCallback(old_students, students), true); adapter.setData(students); result.dispatchUpdatesTo(adapter); }
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接口的實現類如何定義:
private class MyCallback extends DiffUtil.Callback { private ArrayList<Student> old_students, new_students; MyCallback(ArrayList<Student> data, ArrayList<Student> students) { this.old_students = data; this.new_students = students; } @Override public int getOldListSize() { return old_students.size(); } @Override public int getNewListSize() { return new_students.size(); } // 判斷Item是否已經存在 @Override public boolean areItemsTheSame(int oldItemPosition, int newItemPosition) { return old_students.get(oldItemPosition).getNum() == new_students.get(newItemPosition).getNum(); } // 如果Item已經存在則會調用此方法,判斷Item的內容是否一致 @Override public boolean areContentsTheSame(int oldItemPosition, int newItemPosition) { return old_students.get(oldItemPosition).getName().equals(new_students.get(newItemPosition).getName()); } }
這裡根據學號判斷是否同一個Item,根據姓名判斷這個Item是否有被修改。
實際上,這個Callback抽象類還有一個方法getChangePayload()
,這個方法的作用是我們可以通過這個方法告訴Adapter對這個Item進行局部的更新而不是整個更新。
先要知道這個payload是什麼?payload是一個用來描述Item變化的對象,也就是我們的Item發生了哪些變化,這些變化就封裝成一個payload,所以我們一般可以用Bundle來充當。
接著,getChangePayload()
方法是在areItemsTheSame()
返回true,而areContentsTheSame()
返回false時被回調的,也就是一個Item的內容發生了變化,而這個變化有可能是局部的(例如微博的點贊,我們只需要刷新圖標而不是整個Item)。所以可以在getChangePayload()
中封裝一個Object來告訴RecyclerView進行局部的刷新。
假設上例中學號和姓名用不同的TextView顯示,當我們修改了一個學號對應的姓名時,局部刷新姓名即可(這裡例子可能顯得比較多余,但是如果一個Item很復雜,用處就比較大了):
先是重寫Callback中的該方法:
@Nullable @Override public Object getChangePayload(int oldItemPosition, int newItemPosition) { Student newStudent = newStudents.get(newItemPosition); Bundle diffBundle = new Bundle(); diffBundle.putString(NAME_KEY, newStudent.getName()); return diffBundle; }
返回的這個對象會在什麼地方收到呢?實際上在RecyclerView.Adapter
中有兩個onBindViewHolder
方法,一個是我們必須要重寫的,而另一個的第三個參數就是一個payload的列表:
@Override public void onBindViewHolder(RecyclerView.ViewHolder holder, int position, List payloads) {}
所以我們只需在Adapter中重寫這個方法,如果List為空,執行原來的onBindViewHolder進行整個Item的更新,否則根據payloads的內容進行局部刷新:
@Override public void onBindViewHolder(RecyclerView.ViewHolder holder, int position, List payloads) { if (payloads.isEmpty()) { onBindViewHolder(holder, position); } else { MyViewHolder myViewHolder = (MyViewHolder) holder; Bundle bundle = (Bundle) payloads.get(0); if (bundle.getString(NAME_KEY) != null) { myViewHolder.name.setText(bundle.getString(NAME_KEY)); myViewHolder.name.setTextColor(Color.BLUE); } } }
這裡的payloads不會為null,所以直接判斷是否為空即可。
這裡注意:如果RecyclerView中加載了大量數據,那麼算法可能不會馬上完成,要注意ANR的問題,可以開啟單獨的線程進行計算。
總結
Android中DiffUtil的使用就介紹到這了,希望這篇文章能對Android開發者們有所幫助,如果有疑問大家可以留言交流。
效果圖效果圖中我們實現了一個簡單的隨手指滑動的二階貝塞爾曲線,還有一個復雜點的,穿越所有已知點的貝塞爾曲線。學會使用貝塞爾曲線後可以實現例如QQ紅點滑動刪除啦,360動態
前言新版本ShareSDK的分享和短信驗證,按官網的文檔,都需要添加一個標簽,而分享和短息驗證的這個標簽內容都一樣。會沖突。解決辦法:分享用舊版本,短信驗證用新版本。後面
最近項目裡面需要支付功能,boos一致決定用微信支付,所以在網上查了很多資料,說的不全,完了就找以前的同事指教。算是成功集成上去了。在這裡做個總結記錄。1、在APP上集成
1 背景其實這篇文章可能有些小題大作,但回過頭想想還是很有必要的,有點陰溝裡翻船的感覺。相信大家都知道Android API Level 8開始提供了為應用程序備份和恢復