- BaseAdapter封裝(一) 簡單封裝
- BaseAdapter封裝(二) Header,footer
- BaseAdapter封裝(三) 空資料佔點陣圖
- BaseAdapter封裝(四) PageHelper
- BaseAdapter封裝(五) ListAdapter
- BaseAdapter封裝(六) Healer,footer for List
- BaseAdapter封裝(七) ConcatAdapter 改建頭尾
- BaseAdapter封裝(八) Paging 分頁
目錄:
前言:
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中, 請求成功後直接運算元據集合 更新列表即可.
回到頂部