孟老闆 BaseAdapter封裝(五) ListAdapter

孟老闆發表於2021-06-09

目錄:

  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  稍安勿躁後面會講到;  跳轉

孟老闆 BaseAdapter封裝(五) ListAdapter
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")
    }
}
View Code

 

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

引用官方話術:
DiffUtilListAdapter 能夠高效改變元素的奧祕所在。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中, 請求成功後直接運算元據集合 更新列表即可.  

回到頂部
 

 

相關文章