Android RecyclerView使用ListAdapter高效重新整理資料

2022-10-23 18:01:15

原文:Android RecyclerView使用ListAdapter高效重新整理資料 - Stars-One的雜貨小窩

我們都知道,當RecyclerView資料來源更新後,還需要通過adapter呼叫對應的方法,從而讓RecyclerView重新繪製頁面

本次也是介紹了用另外一種方法來實現RecyclerView高效重新整理資料的功能

問題

首先,預設各位是有使用RecyclerView的經驗的,

對於資料的更新,我們一般可以使用adapter的下面四個方法:

  • notifyDataSetChanged() 整個資料改變
  • notifyItemInserted() 往某個下標插入資料,並觸發動畫
  • notifyItemChanged() 更新某個下標的資料,並觸發動畫
  • notifyItemRangeRemoved() 移除某個下標的資料,並觸發動畫

但是,其中下面的三個方法傳參需要給個position下標,這個有時候每次由我們去計算獲取,很麻煩,而且我們還要處理對應的增刪改的邏輯

所以之後Android官方也是出了一個新的工具DiffUtils

DiffUtils使用

DiffUtil主要提供了一個靜態方法供我們呼叫calculateDiff(),其中的引數為一個Callback靜態抽象類,我們需要先寫一個類,繼承並實現其中的方法

class DiffCallBack(val oldList: ArrayList<Person>, val newList: ArrayList<Person>) :DiffUtil.Callback() {

    //判斷兩個物件是否相同
    override fun areItemsTheSame(oldItemPosition: Int, newItemPosition: Int): Boolean {
        
        return oldList[oldItemPosition].id == newList[newItemPosition].id
    }

    override fun getOldListSize(): Int {
        return oldList.size
    }

    override fun getNewListSize(): Int {
        return newList.size
    }

    //判斷兩個物件內容是否相同
    override fun areContentsTheSame(oldItemPosition: Int, newItemPosition: Int): Boolean {
        val newItem = newList[newItemPosition]
        val oldItem = oldList[oldItemPosition]

        //如果新資料和舊資料的名稱和年齡相同,則視為兩個item的內容相同
        return oldItem.age == newItem.age && oldItem.name == newItem.name
    }

}

實際上,此類就是用來比較兩個List的不同之處,定義區分兩個同類的物件,是否相同,從上面的兩個方法也是能夠看得出來

首先,areItemsTheSame()方法先判斷兩個item是否為同個物件

這裡我是選用了id作為唯一標識來區分是否為同一物件,當然,也可以用記憶體地址來比對,如果是記憶體地址來比對,則涉及淺拷貝和深拷貝的問題,這裡不擴充套件講解了

其次,再通過areContentsTheSame()方法來判斷兩個item內容是否相同

現在,我們有了一個Callback類,可以使用calculateDiff()方法了:

val oldList = adapter.getData()
//深拷貝oldList得到newList,然後對newList按照業務進行增刪改的操作,這裡程式碼就省略了..

//計算不同之處
val diffResult = DiffUtil.calculateDiff(DiffCallBack(oldList,newList))
//adapter設定新資料
adapter.setData(newList)
//將變更操作分發給adapter
diffResult.dispatchUpdatesTo(adapter)

上面給的程式碼可能不是太全,因為這種方法不是我們推薦的寫法,更推薦使用ListAdapter來實現此功能,具體可看下文

實際上,DiffUtil演演算法還是耗時間的,如果資料更多,估計時間也會隨之增多,所以,官方推薦開啟個非同步執行緒來處理計算,之後分發操作再切換UI執行緒進行資料的更新操作

ListAdapter使用

ListAdapter其實就是對上面的DiffUtil的一個封裝類,以往,我們的Adapter都是繼承了RecyclerView.Adapter,並在其中寫了個List去裝載資料,十分麻煩

ListAdapter裡面維護著執行緒池並且還會為我們將檢視修改操作移到主執行緒,這樣我們就可以很方便的使用DiffUtil了

如果我們將此Adapter替換成繼承與ListAdapter,那麼都不需要我們在類中寫上個List,程式碼範例如下:

class RvAdapter() : ListAdapter<Person, RvAdapter.ViewHolder>(diffCallback) {

    companion object {
        val diffCallback = object : DiffUtil.ItemCallback<Person>() {
            override fun areItemsTheSame(oldItem: Person, newItem: Person): Boolean {
                return oldItem.id == newItem.id
            }

            override fun areContentsTheSame(oldItem: Person, newItem: Person): Boolean {
                //如果新資料和舊資料的名稱和年齡相同,則視為兩個item的內容相同
                return oldItem.age == newItem.age && oldItem.name == newItem.name
            }

        }
    }

    class ViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) {
        var tvAge: TextView = itemView.findViewById(R.id.tvAge)
        var tvName: TextView = itemView.findViewById(R.id.tvUserName)
    }

    override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder {
        val itemView = View.inflate(parent.context, R.layout.rv_item_person, null)
        return ViewHolder(itemView)
    }


    override fun onBindViewHolder(holder: ViewHolder, position: Int) {
        val item = getItem(position)
        holder.tvName.text = item.name
        holder.tvAge.text = item.age.toString()
    }
}

ListAdapter<T,ViewHolder>第一個泛型即為你的資料實體類,第二個引數為ViewHolder類

注意: 之後的資料增刪改查都需要呼叫adapter提供的submitList()方法即可

val oldList = adapter.currentList

val newList = oldList.map { it }.toMutableList()
newList.removeAt(10)
//下標2加個新資料
newList.add(2, Person(90, "我的", 72))
adapter.submitList(list)

效果:

參考