効率的なRecyclerViewアプリケーション

19242 ワード

RecyclerViewとは?

  • RecyclerViewはListViewの不足を補い、Android 5.0から
  • を適用する.
  • 既存のListViewは、プロジェクトを作成するたびにビューをバインドし、パフォーマンスが
  • 低下する.
  • RecyclerViewは、パフォーマンスの低下を防ぐために最初にバインドされた後にビューを再使用するViewの概念を採用しています.
  • RecyclerViewの主なカテゴリと説明

  • ViewHolder
    各ビューのHolderオブジェクトを保存
  • は、各要素を格納および使用してプロジェクトビューを回収するために使用される
  • LayoutManager
    物品の配置を担当する
    LinearLayoutManager:リストを横または縦にスクロール
    GridLayoutManager:グリッド形式リスト
    StaggerdGridLayoutManager:フォークグリッド形式リスト
  • Adapter
    従来のListViewで使用されているAdapterと同様の概念で、データとプロジェクトのビューを作成します.
  • ベースレコードビュー


    これに基づいて、RecyclerViewを使用してAdapterクラスを作成し、RecyclerViewクラスのAdapterを継承して実装します.
    class UserRecyclerViewAdapter : RecyclerView.Adapter<UserRecyclerViewAdapter.MyViewHolder>() {
        private val userList = ArrayList<User>()
        class MyViewHolder(private val binding:ItemListBinding) : RecyclerView.ViewHolder(binding.root){
            fun bind(user:User){
                binding.apply {
                    binding.user = user
                }
            }
        }
        override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): MyViewHolder {
            return MyViewHolder(
                ItemListBinding.inflate(
                    LayoutInflater.from(parent.context),
                    parent,
                    false
                )
            )
        }
        override fun onBindViewHolder(holder: MyViewHolder, position: Int) {
            holder.apply { bind(userList[position]) }
        }
        override fun getItemCount(): Int = userList.size
        fun setItem(items: ArrayList<User>) {
            userList.clear()
            userList.addAll(items)
            notifyDataSetChanged()
        }
    }
    これをfragmentなどに使用する方法は以下の通りです.
    val adapter = UserRecyclerViewAdapter()
    // 구현한 Adapter를 등록한다.
    binding.recyclerView.adapter = adapter
    // Item으로 사용될 List(userList)를 넘겨준다.
    adapter.setItem(userList)
    // RecyclerView 갱신을 위해 사용
    adapter.notifyDataSetChanged()
    このように使用する場合、Recyclerviewでデータが変更された場合は、notifyDataSetChangedメソッドを使用してViewHolderコンテンツを更新する必要があります.
    欠点は、データが変更されるたびにnotifyDataSetChangedが呼び出されることです.これは面倒で、更新する必要のないアイテムもすべて更新され、メモリリソースが大量に消費されます.また、更新時に画面が点滅する話題もあります.
    これはDiffUtilで解決できます.

    DiffUtil


    https://developer.android.com/reference/androidx/recyclerview/widget/DiffUtil
    DiffUtilは、2つのデータセットを受信することによって差異を計算するクラスである.
    DiffUtilを使用すると、2つのデータセットを比較し、その変更のみを理解してRecyclerViewに反映できます.
    参考までに、DiffUtilはEugene W.MyersのDifferenceアルゴリズムを用いてO(N+D^2)時間内にリストの比較を行う.
    (N:追加と削除の項目数、D:スクリプト長)
    DiffUtilでは、次の手順に従います.
  • DiffUtil.ItemCalback()を継承し、2つのオブジェクトをareItemsTheSameとして比較する
  • areContentsTheSameを使用して、2つのプロジェクトに同じデータがあるかどうかを確認します.
    DiffUtil.ItemCallback実施
  • object TermsListDiffCallback : DiffUtil.ItemCallback<User>() {
        override fun areItemsTheSame(oldItem: User, newItem: User): Boolean {
            return oldItem == newItem
        }
        override fun areContentsTheSame(oldItem: User, newItem: User): Boolean {
            return oldItem.id == newItem.id
        }
    }
    このDiffUtilはO(N+D^2)の時間的複雑さを持つため,リストの数が多いほど比較が必要なオブジェクトが多くなるため,MainThreadでは書きにくい.
    Recyclerviewアダプタを作成する場合は、ListAdapterが継承され、このDiffUtilが使用されていることを確認します.Calbackオブジェクトを受信することで、バックグラウンドThreadで実行する効率的なRecyclerViewを実現できます.

    ListAdapter


    上のUserRecyclerViewAdapter.ktを再包装したソースです.
    package com.example.recyclerview2.adapter
    import android.util.Log
    import android.view.LayoutInflater
    import android.view.ViewGroup
    import androidx.recyclerview.widget.DiffUtil
    import androidx.recyclerview.widget.ListAdapter
    import androidx.recyclerview.widget.RecyclerView
    import com.example.recyclerview2.data.User
    import com.example.recyclerview2.databinding.ItemListBinding
    class UserRecyclerViewAdapter :
        ListAdapter<User, UserRecyclerViewAdapter.MyViewHolder>(TermsListDiffCallback) {
        class MyViewHolder(private val binding: ItemListBinding) :
            RecyclerView.ViewHolder(binding.root) {
            fun bind(user: User) {
                binding.user = user
            }
        }
        override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): MyViewHolder {
            return MyViewHolder(
                ItemListBinding.inflate(
                    LayoutInflater.from(parent.context),
                    parent,
                    false
                )
            )
        }
        override fun onBindViewHolder(holder: MyViewHolder, position: Int) {
            holder.apply {
                bind(getItem(position))
            }
        }
        object TermsListDiffCallback : DiffUtil.ItemCallback<User>() {
            override fun areItemsTheSame(oldItem: User, newItem: User): Boolean {
                return oldItem == newItem
            }
            override fun areContentsTheSame(oldItem: User, newItem: User): Boolean {
                return oldItem.id == newItem.id
            }
        }
    }
    まず変わったのは.
    1.UserRecyclerView AdapterクラスはRecyclerViewです.Adapterを継承するのではなく、ListAdapterを継承します.
    2.パラメータとしてDiffUtilを実現するTermsListDiffCallbackオブジェクトを渡す.
    3.Itemのsizeを返すgetItemCount()メソッドと、Itemsを設定するsetItem(items:ArrayList)メソッドがなくなりました.
    以下のように使用します.
    val adapter = UserRecyclerViewAdapter()
    binding.recyclerView. = adapter
    adapter.submitList(userList)
    以前単独で実装されたsetItems法ではなく,ListAdapterクラスのSubmitList法が用いられていることが分かる.
    これにより、DiffUtilおよびListAdapterを使用すると、RecyclerViewをより効率的に実現することができる.

    追加(RecyclerViewで更新できない問題解決策のクリーンアップ)


    BindingAdapterを使用してRecyclerViewAdapterを次の場所に接続すると、画面がリフレッシュできないという問題が発生しました.ホットスポットを解析することにより,submitList法が原因である.
    submitList(gitUserList)
    submitListメソッドで、AsyncListDifferに入ります.Javaクラスでは、次のような論理が表示されます.

    newListを既存のListと比較し,同一であれば論理を返す.
    最初にsubmitListに渡されたオブジェクト(ここではgitUserList:ArrayList)を変更値のみを同じオブジェクトに入れ、同じアドレスを参照して低論理で同じと判断する.
    そのため、解決策は次のとおりです.
    1.最初にサブミットリストを作成するときは、新しいオブジェクトを追加するか
    2.submitList(GitUserList.toMutableList)のように新しいオブジェクトに入れればよい.