- 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中, 請求成功後直接操作資料集合 更新列表即可.
回到頂部