ListAdapter封裝- 告別Adapter代碼(一) 入門
listAdapter? 是的你沒有聽錯... 但它不是ListView那個Adapter
它是 androidx.recyclerview.widget 包下為 RecycleView 服務的類;
ListAdapter 的優勢:
- 原Adapter增刪改 有各種 notifyItem.. 操作, 而 ListAdapter 一個 submitList 方法即可滿足所有操作;
- AsyncListDiffer 異步計算新舊數據差異, 並通知 Adapter 刷新數據
- 由數據驅動, 無論增刪改查 我們只需要關心並操作數據集. 這很 MVVM
推薦文章:
- BaseAdapter封裝
- ListAdapter封裝, 告別Adapter代碼 (一)
- ListAdapter封裝, 告別Adapter代碼 (二)
- ListAdapter封裝, 告別Adapter代碼 (三)
- ListAdapter封裝, 告別Adapter代碼 (四)
- 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
引用官方話術:
DiffUtil 是 ListAdapter 能夠高效改變元素的奧秘所在。DiffUtil 會比較新舊列表中增加、移動、刪除了哪些元素,然后輸出更新操作的列表將原列表中的元素高效地轉換為新的元素。
簡單理解:
ListAdpater 就是通過 DiffUtil 計算前后集合的差異, 得出增刪改的結果. 通知Adapter做出對應更新;
3.1 areItemsTheSame():
它比較兩個對象是否是同一個 Item;
常見的比較方式: 可自行根據使用場景或個人習慣選用
比較內存地址: java( ==) kotlin( ===)不推薦. 在paging中,對象可能被重建- 比較兩個對象的 Id; 一般對象在庫表中都有主鍵 ID 參數; 相同的情況下,認定為同一條記錄;
- equals: java(obj.equals(other)); kotlin(==)
3.2 areContentsTheSame()
在已經確定同一 Item 的情況下, 再確定是否有內容更新;
網上給出的比較方式幾乎全是 equals; 但 equals 運用不當根本刷新不了 Item;
- 當 areItemsTheSame() 選用 比較內存地址 的方式時, areContentsTheSame() 不能用equals方式;
- 當某個具體的 Item 更新時, 必定會替換為一個新實體對象時. 可以用 equals 方式; 也就是說,當我給某個動態條目點贊時, 必須要 copy 一個新的動態對象, 給新對象設置點贊狀態為 true; 然后再用新對象替換掉數據集中的舊對象. equals 刷新才能奏效;
- 當更新某個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中, 請求成功后直接操作數據集合 更新列表即可.