目錄:

  ListAdapter 簡介及優勢

  開始使用

  DiffUtil 

  areContentsTheSame 不能更新條目的原因

前言:

  listAdapter?? 是的你沒有聽錯...  算了不解釋了,都1202年了;  它是    androidx.recyclerview.widget  包下為RecycleView服務的類;

ListAdapter 的優勢:

  1. 重新整理列表只需要 submitList 這一個方法;  而避免原Adapter 增刪改 的各種 notifyItem.. 操作;

  2. AsyncListDiffer 非同步計算新舊資料差異, 並通知 Adapter 重新整理資料

  3. 真正的資料驅動, 無論增刪改查 我們只需要關心並操作資料集.

使用:

1.新建Adapter 繼承  ListAdapter;  泛型提供 資料集實體類 和 自定義的 ViewHolder;  建構函式提供了 自定義的 DiffCallback()

DiffCallback 繼承自 DiffUtil.ItemCallback  稍安勿躁後面會講到;  跳轉

class TestAdapter : ListAdapter<DynamicTwo, NewViewHolder>(DiffCallback()) {
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): NewViewHolder {
TODO("Not yet implemented")
} override fun onBindViewHolder(holder: NewViewHolder, position: Int) {
TODO("Not yet implemented")
}
}

2. onCreateViewHolder 的程式碼比較簡單, MVVM模式, 直接返回 用 ViewDataBinding 構造的 ViewHolder; 只有一個可變引數, 即佈局資原始檔ID;

onBindViewHolder 的程式碼更簡單,  拿到資料實體 並交給 ViewHolder 處理即可;

   override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): NewViewHolder {
return NewViewHolder(
DataBindingUtil.inflate(
LayoutInflater.from(parent.context),
R.layout.item_dynamic_img, parent, false
)
)
} override fun onBindViewHolder(holder: NewViewHolder, position: Int) {
holder.bind(getItem(position))
}

3. ViewHolder 的程式碼:  

open class NewViewHolder(val binding: ViewDataBinding) : RecyclerView.ViewHolder(binding.root){
fun bind(item: BaseItem?) {
binding.setVariable(BR.item, item)
binding.executePendingBindings()
}
}

4. DiffCallback 程式碼:

class DiffCallback : DiffUtil.ItemCallback<DynamicTwo>() {
override fun areItemsTheSame(oldItem: DynamicTwo, newItem: DynamicTwo): Boolean {
return oldItem === newItem
}

override fun areContentsTheSame(oldItem: DynamicTwo, newItem: DynamicTwo): Boolean {
return !oldItem.hasChanged
}
}

5. 操作資料:   Adapter 新建跟以往沒有區別.

mAdapter = TestAdapter()
mDataBind.rvRecycle.let {
it.layoutManager = LinearLayoutManager(mActivity)
it.adapter = mAdapter
}

5.1 增刪操作比較簡單, 只需要更改資料集, 並 submitList 即可

注意:  這裡需要用新資料集物件操作!!! 切記

fun addData(){
val data = mutableListOf<DynamicTwo>() //currentList 不需要判空, 它有預設的空集合
data.addAll(mAdapter.currentList)
repeat(10){
data.add(DynamicTwo())
}
mAdapter.submitList(data)
} fun deleteItem(position: Int){
val data = mutableListOf<DynamicTwo>()
data.addAll(mAdapter.currentList)
data.removeAt(position)
mAdapter.submitList(data)
} fun updateItem(position: Int){
//TODO 暫放
}

為什麼這裡必須要用新集合物件操作?  我們來看一下 submitList 的原始碼

  public void submitList(@Nullable List<T> list) {
mDiffer.submitList(list);
}   public void submitList(@Nullable final List<T> newList,
@Nullable final Runnable commitCallback) {
// incrementing generation means any currently-running diffs are discarded when they finish
final int runGeneration = ++mMaxScheduledGeneration; if (newList == mList) {
// nothing to do (Note - still had to inc generation, since may have ongoing work)
if (commitCallback != null) {
commitCallback.run();
}
return;
} final List<T> previousList = mReadOnlyList; // fast simple remove all
if (newList == null) {
//noinspection ConstantConditions
int countRemoved = mList.size();
mList = null;
mReadOnlyList = Collections.emptyList();
// notify last, after list is updated
mUpdateCallback.onRemoved(0, countRemoved);
onCurrentListChanged(previousList, commitCallback);
return;
} // fast simple first insert
if (mList == null) {
mList = newList;
mReadOnlyList = Collections.unmodifiableList(newList);
// notify last, after list is updated
mUpdateCallback.onInserted(0, newList.size());
onCurrentListChanged(previousList, commitCallback);
return;
}
    .....
  }
mList 為舊集合物件; 紅字部分(JAVA 程式碼) 可以看出, 當新舊資料為同一物件時return 就不再往下執行了. 
ListAdapter 認為新舊陣列為同一物件時, nothing to do.
我們可以認為這是 ListAdapter 的一個特性. 也許它只是提醒我們 不要做無效重新整理操作;
當然我們也可以重寫
submitList 方法, 然後自動新建資料集.

5.2 DiffUtil
講更新操作前, 需要先講 DiffUtil

引用官方話術:
DiffUtil 是 ListAdapter 能夠高效改變元素的奧祕所在。DiffUtil 會比較新舊列表中增加、移動、刪除了哪些元素,然後輸出更新操作的列表將原列表中的元素高效地轉換為新的元素。

簡單理解:
ListAdpater 就是通過 DiffUtil 計算前後集合的差異, 得出增刪改的結果. 通知Adapter做出對應操作;
5.2.1 areItemsTheSame(): 比較兩個物件是否是同一個 Item;
常見的比較方式: 可自行根據情況或個人習慣選用
  1.比較記憶體地址: java(==) kotlin(===)
  2.比較兩個物件的 Id; 一般物件在庫表中都有主鍵 ID 引數; 相同的情況下,必定為同一條記錄;
  3.equals: java(obj.
equals(other)) kotlin(==)
5.2.2 areContentsTheSame(): 在已經確定同一 Item 的情況下, 再確定是否有內容更新;
網上給出的比較方式幾乎全是
equals; 但 equals 運用不當根本重新整理不了 Item;
  1.當 areItemsTheSame() 選用 比較記憶體地址 的方式時, areContentsTheSame() 不能用equals方式;
  2.當某個具體的 Item 更新時, 必定會替換為一個新實體物件時. 可以用 equals 方式;
    也就是說,當我給某個動態條目點贊時, 必須要 copy 一個新的動態物件, 給新物件設定點贊狀態為 true; 然後再用新物件替換掉資料集中的舊物件.
equals 重新整理才能奏效;
  3.當更新某個Item, 不確定是否為新Item物件實體時, 不能用
equals 方式;

  總結: 同一個記憶體地址的物件
equals 有個雞兒用? 有個雞兒用??

  狀態標記方式:
  實體物件中增加: hasChanged: Boolean 欄位; 當物件內容變化時,設定 hasChanged 為true; ViewHolder.bind()時,置為false;
 
給最終的 ViewHolder  DiffCallback
class DiffCallback : DiffUtil.ItemCallback<DynamicTwo>() {
override fun areItemsTheSame(oldItem: DynamicTwo, newItem: DynamicTwo): Boolean {
return oldItem === newItem
} override fun areContentsTheSame(oldItem: DynamicTwo, newItem: DynamicTwo): Boolean {
return !oldItem.hasChanged
}
} open class NewViewHolder(val binding: ViewDataBinding) : RecyclerView.ViewHolder(binding.root){
fun bind(item: BaseItem?, index: Int) {
item?.hasChanged = false
binding.setVariable(BR.item, item)
binding.executePendingBindings()
}
}

5.3 更新操作:

fun updateItem(position: Int){
val data = mutableListOf<DynamicTwo>()
data.addAll(mAdapter.currentList)
data[position].let {
it.title = "變變變 我是百變小魔女"
it.hasChanged = true
}
mAdapter.submitList(data)
}

 最後:

  ListAdapter 可完美的 由資料驅動 UI,  增刪改可以放到 ViewModel中, 請求成功後直接操作資料集合 更新列表即可.  

回到頂部