孟老板 ListAdapter封裝, 告別Adapter代碼 (一)


ListAdapter封裝- 告別Adapter代碼(一) 入門

listAdapter? 是的你沒有聽錯... 但它不是ListView那個Adapter
它是 androidx.recyclerview.widget 包下為 RecycleView 服務的類;

ListAdapter 的優勢:

  1. 原Adapter增刪改 有各種 notifyItem.. 操作, 而 ListAdapter 一個 submitList 方法即可滿足所有操作;
  2. AsyncListDiffer 異步計算新舊數據差異, 並通知 Adapter 刷新數據
  3. 由數據驅動, 無論增刪改查 我們只需要關心並操作數據集. 這很 MVVM

推薦文章:

  1. BaseAdapter封裝
  2. ListAdapter封裝, 告別Adapter代碼 (一)
  3. ListAdapter封裝, 告別Adapter代碼 (二)
  4. ListAdapter封裝, 告別Adapter代碼 (三)
  5. ListAdapter封裝, 告別Adapter代碼 (四)
  6. Paging3 官方分頁加載工具

開始使用

1.Adapter

  • 我們不再繼承 RecycleView.Adapter. 而是繼承 androidx.recyclerview.widget.ListAdapter
  • 泛型提供數據實體類自定義的 ViewHolder
  • onCreateViewHolder() 使用的 MVVM模式;

好的直接上代碼:

class TestAdapter : ListAdapter<DynamicTwo, NewViewHolder>(DiffCallback()) {
	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))
    }
}

2.NewViewHolder

接收一個 ViewDataBinding 對象, bind時, 直接通知綁定數據.

open class NewViewHolder(val binding: ViewDataBinding) : RecyclerView.ViewHolder(binding.root){
    fun bind(item: DynamicTwo?) {
    	item?.hasChanged = false	//狀態標記方式時, 重置狀態
        binding.setVariable(BR.item, item)
        binding.executePendingBindings()
    }
}

3.DiffCallback

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

簡單理解:
ListAdpater 就是通過 DiffUtil 計算前后集合的差異, 得出增刪改的結果. 通知Adapter做出對應更新;

3.1 areItemsTheSame():

它比較兩個對象是否是同一個 Item;
常見的比較方式: 可自行根據使用場景或個人習慣選用

  1. 比較內存地址: java( ==) kotlin( ===) 不推薦. 在paging中,對象可能被重建
  2. 比較兩個對象的 Id; 一般對象在庫表中都有主鍵 ID 參數; 相同的情況下,認定為同一條記錄;
  3. equals: java(obj.equals(other)); kotlin(==)

3.2 areContentsTheSame()

在已經確定同一 Item 的情況下, 再確定是否有內容更新;
網上給出的比較方式幾乎全是 equals; 但 equals 運用不當根本刷新不了 Item;

  1. 當 areItemsTheSame() 選用 比較內存地址 的方式時, areContentsTheSame() 不能用equals方式;
  2. 當某個具體的 Item 更新時, 必定會替換為一個新實體對象時. 可以用 equals 方式; 也就是說,當我給某個動態條目點贊時, 必須要 copy 一個新的動態對象, 給新對象設置點贊狀態為 true; 然后再用新對象替換掉數據集中的舊對象. equals 刷新才能奏效;
  3. 當更新某個Item, 不確定是否為新Item對象實體時, 不能用 equals 方式;

總結:
同一個內存地址的對象 equals 有個雞兒用? 有個雞兒用??
在paging+Room項目中, 應使用 Equals 方式; 否則建議使用: 狀態標記方式

狀態標記方式:
  實體對象中增加: hasChanged: Boolean 字段; 當對象內容變化時,設置 hasChanged 為true; ViewHolder.bind()時,置為false;

所以 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
    }
}

4.Activity 使用

Adapter的初始化 跟以往沒有區別. 然后就是操作數據 然后 submitList 提交數據

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

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)
}

細心小伙伴應該可以看出新增或刪除時, 這個 data 是一個新集合對象;
為什么這里必須要用新集合對象操作? 我們來看一下 submitList 的源碼:

public void submitList(@Nullable List<T> list) {
    mDiffer.submitList(list);
}

//AsyncListDiffer mDiffer
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;
	// ** java代碼,  判斷是否同一內存地址.  這里同一對象時會 return
    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 為舊集合對象; 可以看出, 當新舊數據為同一對象時 return, 就不再往下執行了.
ListAdapter 認為新舊數組為同一對象時, nothing to do.
我們可以認為這是 ListAdapter 的一個特性. 也許它只是提醒我們 不要做無效刷新操作;
當然我們也可以重寫 submitList 方法, 然后自動新建數據集.

更新操作:

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中, 請求成功后直接操作數據集合 更新列表即可.


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM