編輯:關於Android編程
谷歌最近更新了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 ArrayListdata; 3 4 ArrayList getData() { 5 return data; 6 } 7 8 void setData(ArrayList 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 ArrayListdata; 3 4 ArrayList getData() { 5 return data; 6 } 7 8 void setData(ArrayList 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 ArrayListold_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 ArrayListold_students, new_students; 3 4 MyCallback(ArrayList data, ArrayList 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的問題,可以開啟單獨的線程進行計算。
面向切面編程(AOP,Aspect-oriented programming)需要把程序邏輯分解成『關注點』(concerns,功能的內聚區域)。這意味著,在 AOP 中
Android Studio目前已經成為開發Android的主要工具,用熟了可謂相當順手。作為開發者,調試並發現bug,進而解決,可是我們的看家本領。正所謂,工欲善其事必
屏幕切換指的是在同一個Activity內屏幕間的切換,ViewFlipper繼承了Framelayout類,ViewAnimator類的作用是為FrameLayout裡面
創建一個新項目是很簡單的,只要你安裝了Eclipse插件,並且你的Eclipse軟件版本在3.2或3.3,你就可以開始開發了。 首先, 看一下要創建"Hell