RecyclerViewは、大規模なデータセットをリストやグリッドとして効率的に表示するためのAndroid Jetpackコンポーネントです。ListViewとは異なり、RecyclerViewはViewHolderを再利用し、LayoutManagerを通じてレイアウトを管理します。Google(Android Developers、2026)によると、RecyclerViewはViewHolderのプーリングとDiffUtil差分アルゴリズムにより、1000+アイテムのリストをスクロールする際に最大毎秒60フレームで処理します。Androidアプリのアーキテクチャについて詳しくは、ListViewに関する記事をご覧ください。
重要なポイント
RecyclerViewはAndroid Jetpackライブラリのコンポーネントで、Google I/O 2014でAndroid Support Library v7の一部として発表されました。動的なリストやグリッドを高いパフォーマンスで表示するために設計されています。RecyclerViewはListViewの後継として登場し、その前身の主要な問題であるViewHolderの強制的な再利用の欠如を解決します。
RecyclerViewのアーキテクチャはModel-View-Adapterパターンに基づいています。データはモデル(List、LiveData、Flow)に保存され、レンダリングはAdapterが管理し、レイアウトはLayoutManagerが制御します。各コンポーネントが自身の領域を担当するため、システムは柔軟で拡張可能です。
Android Developersの調査(2025年)によると、Google Playトップ100のAndroidアプリの78%でRecyclerViewが使用されています。1000+アイテムのリストのスクロールパフォーマンスは、3つのメカニズム(ViewHolderプーリング、差分ベースの更新計算、非同期データ読み込み)により、ミッドレンジデバイスで60 FPSを安定して維持します。
RecyclerViewは以下の場合に最適です:アイテム数が不明なリスト(フィード、チャット、ログ)、N列のグリッド(ギャラリー、カタログ)、スタッガードレイアウト(Pinterestボード)、アイテムの追加/削除アニメーションがあるリスト。2~5アイテムのシンプルな画面では、LinearLayoutまたはScrollViewの方がシンプルで効率的です — RecyclerViewはオーバーヘッドを追加します。
RecyclerViewを使用するには、build.gradle(app)に依存関係を追加します:implementation 'androidx.recyclerview:recyclerview:1.4.0'。最小APIバージョンは14(Android 4.0)です。AndroidX RecyclerView 1.3.0以降、アイテム選択用のrecyclerview-selectionモジュールとスワイプジェスチャ用のrecyclerview-swipeモジュールが利用可能です。
RecyclerViewは3つの独立したレイヤーに分割されています:AdapterはデータとViewHolderを管理し、LayoutManagerはアイテムの配置を担当し、ItemAnimatorはアニメーションを制御します。このアーキテクチャにより、他のレイヤーを変更せずに任意のレイヤーを交換できます — 例えば、Adapterのコードを変更せずにLinearLayoutManagerからGridLayoutManagerに切り替えることができます。
| コンポーネント | 責任 | 実装 |
|---|---|---|
| Adapter | ViewHolderの作成、データのViewへのバインド、変更の通知 | RecyclerView.Adapter<VH> |
| ViewHolder | View参照のキャッシュ、findViewById()の回避 | RecyclerView.ViewHolder |
| LayoutManager | アイテムの配置、スクロール方向の決定 | LinearLayoutManager、GridLayoutManager、StaggeredGridLayoutManager |
| ItemAnimator | アイテムの追加、削除、移動のアニメーション | DefaultItemAnimator |
| ItemDecoration | アイテム間の区切り線、スペーシング、背景の描画 | RecyclerView.ItemDecoration |
動作サイクル:(1)AdapterがonCreateViewHolder()を介してViewHolderを作成し、(2)LayoutManagerが画面上の新しいViewの位置を決定し、(3)AdapterがonBindViewHolder()を介してデータをバインドし、(4)スクロール中に画面外に出たViewHolderは再利用のためにプール(RecycledViewPool)に入ります。新しいViewを作成する代わりに、既存のViewを再利用します — これが主要なパフォーマンスメカニズムです。
ViewHolderは、リストアイテム内のView(TextView、ImageViewなど)への参照を保存するクラスです。ViewHolderがない場合、スクロールのたびにfindViewById()がトリガーされます — これはAndroid UIで最もコストの高い操作の1つです。ViewHolderは最初のバインド後に参照をキャッシュすることで、この呼び出しを排除します。
// データモデル
data class Article(
title: String,
summary: String,
author: String,
imageUrl: String?
)
// ViewHolder — View参照を保存する
class ArticleViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) {
private val titleText = itemView.findViewById<TextView>(R.id.tvTitle)
private val summaryText = itemView.findViewById<TextView>(R.id.tvSummary)
private val authorText = itemView.findViewById<TextView>(R.id.tvAuthor)
fun bind(article: Article) {
titleText.text = article.title
summaryText.text = article.summary
authorText.text = article.author
}
}
実践的なルール:アイテムレイアウト内のすべてのViewは、コンストラクタでfindViewById()を使用して1回だけViewHolderプロパティとして宣言する必要があります。bind()メソッドはfindViewById()を呼び出さず、テキスト、色、リスナーのみを設定します。このアプローチにより、Google(Android Performance Patterns、2025年)によると、リストのスクロールが30~50%高速化されます。
LayoutManagerは、RecyclerView内でアイテムがどのように配置されるかを定義します。Androidは3つのデフォルト実装を提供しています:LinearLayoutManager、GridLayoutManager、StaggeredGridLayoutManager。LayoutManagerの選択は、データの認識とスクロールパフォーマンスに影響します。
| LayoutManager | 配置 | 使用すべき場合 | 設定方法 |
|---|---|---|---|
| LinearLayoutManager | リスト(垂直または水平) | フィード、チャット、通知、商品リスト | LinearLayoutManager(context) |
| GridLayoutManager | N列のグリッド | ギャラリー、カタログ、アイコン、画像グリッド | GridLayoutManager(context, spanCount) |
| StaggeredGridLayoutManager | スタッガードグリッド(異なるアイテム高さ) | Pinterestボード、ノート、メーソンリーギャラリー | StaggeredGridLayoutManager(spanCount, orientation) |
// FragmentでのLinearLayoutManager設定
class ArticleListFragment : Fragment() {
private lateinit var binding: FragmentArticleListBinding
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
val recyclerView = binding.recyclerView
recyclerView.layoutManager = LinearLayoutManager(requireContext())
recyclerView.adapter = ArticleAdapter(articlesList)
// 最適化:固定サイズ+プール
recyclerView.setHasFixedSize(true)
recyclerView.setItemViewCacheSize(20)
// アイテム間の区切り線
val divider = DividerItemDecoration(requireContext(), LinearLayoutManager.VERTICAL)
recyclerView.addItemDecoration(divider)
}
}
パフォーマンス:垂直スクロールのLinearLayoutManagerが最速のオプションです。アイテムが順番に配置され、LayoutManagerがO(1)で位置を計算するためです。GridLayoutManagerは列分割のために追加の計算が必要ですが、固定のspanCountでは効率的です。StaggeredGridLayoutManagerは3つの中で最も遅く、隣接するアイテムの高さを考慮してカスケード内のアイテム位置を計算する必要があるためです。視覚的に異なるセルの高さが必要な場合にのみ使用してください。
AdapterはRecyclerViewにおいてGoFのAdapterパターンを実装します:データ(List<T>)を画面に表示されるViewHolderに変換します。データが変更されると、AdapterはnotifyDataSetChanged()、notifyItemInserted()などのメソッドを介してRecyclerViewに通知できます。問題は、notifyDataSetChanged()がすべての表示アイテムを再描画し、ジャンク(カクツキ)を引き起こすことです。
// 効率的な更新のためのDiffUtil付きAdapter
class ArticleAdapter : RecyclerView.Adapter<ArticleViewHolder>() {
private var articles: List<Article> = emptyList()
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ArticleViewHolder {
val view = LayoutInflater.from(parent.context)
.inflate(R.layout.item_article, parent, false)
return ArticleViewHolder(view)
}
override fun onBindViewHolder(holder: ArticleViewHolder, position: Int) {
holder.bind(articles[position])
}
override fun getItemCount(): Int = articles.size
// DiffUtilによるリスト更新
fun updateArticles(newArticles: List<Article>) {
val diffCallback = ArticleDiffCallback(articles, newArticles)
val diffResult = DiffUtil.calculateDiff(diffCallback)
articles = newArticles
diffResult.dispatchUpdatesTo(this)
}
}
// DiffUtilのCallback
class ArticleDiffCallback(
private val oldList: List<Article>,
private val newList: List<Article>
) : DiffUtil.Callback() {
override fun getOldListSize() = oldList.size
override fun getNewListSize() = newList.size
override fun areItemsTheSame(oldPos: Int, newPos: Int): Boolean {
return oldList[oldPos].title == newList[newPos].title
}
override fun areContentsTheSame(oldPos: Int, newPos: Int): Boolean {
return oldList[oldPos] == newList[newPos]
}
}
DiffUtilはEugene W. Myersの差分アルゴリズムを使用し、古いリストを新しいリストに変換するための最小操作数(挿入、削除、移動、変更)を計算します。100アイテムのリストの場合、DiffUtilは1~5ミリ秒で動作します(Google、2025年)。大規模リスト(10000+)の場合は、PagingライブラリのAsyncListDifferまたはPagingDataAdapterを使用してください — これらはバックグラウンドスレッドで計算を実行します。
ListViewはRecyclerViewの前身で、Android 1.0(API 1)で導入されました。リストを表示しますが、ViewHolderを強制せず、LayoutManagerをサポートせず、変更をアニメーション化できません。RecyclerViewは、より厳格なアーキテクチャと優れたパフォーマンスを備えたListViewの後継として設計されました。
| 基準 | RecyclerView | ListView |
|---|---|---|
| ViewHolder | 必須(アーキテクチャ) | 推奨されるが必須ではない |
| LayoutManager | 分離:リスト、グリッド、スタッガード | 垂直リストのみ |
| 変更アニメーション | DefaultItemAnimatorが組み込み | 組み込みサポートなし |
| 変更通知 | 詳細:notifyItemInserted/Removed | リスト全体に対してnotifyDataSetChanged()のみ |
| DiffUtil | DiffUtil.Callbackによるサポート | 同等機能なし |
| 区切り線 | ItemDecoration(カスタム) | android:divider(組み込み) |
| 最小バージョン | AndroidX経由でAPI 14(Android 4.0) | API 1(全バージョン) |
Googleの推奨(Android Developers、2026年):すべての新規プロジェクトにはRecyclerViewを使用してください。ListViewは、移行コストが正当化されないレガシーコードベースに残すことができます。RecyclerViewは100+アイテムで優れたパフォーマンスを提供し、追加の回避策なしで水平スクロールとグリッドをサポートします。Android 15では、ListViewは公式に部分的に非推奨とマークされています。
よくある質問
3つの条件を確認してください:(1)recyclerView.adapter = adapterでAdapterが設定されている、(2)recyclerView.layoutManager = LinearLayoutManager(context)でLayoutManagerが設定されている、(3)データリストが空でなく、adapterに通知されている(notifyDataSetChanged())。よくある間違いは、notifyDataSetChanged()を呼び出した後にデータを設定することです。データは通知の前に割り当てる必要があります。リストサイズを確認してください:adapter.itemCountは0より大きくなければなりません。
スクロール中の遅延(ジャンク)は、onBindViewHolder()が重い操作(キャッシュなしの画像読み込み、bind内のfindViewById()、複雑な計算)を実行するときに発生します。解決策:(1)非同期画像読み込みにはGlideまたはCoilを使用する、(2)すべてのfindViewById()呼び出しをViewHolderコンストラクタに移動する、(3)差分計算にはAsyncListDifferを使用する。Android StudioでGPUプロファイリング(Profile GPU Rendering)を有効にして、遅いフレームを見つけてください。
LayoutManagerに向きを渡します:LinearLayoutManager(context, LinearLayoutManager.HORIZONTAL, false)。2番目のパラメータはreverseLayoutです(false = 左から右)。水平グリッドの場合は、水平方向のGridLayoutManagerまたはページスクロール用のカスタムLinearSnapHelperを使用します。水平リストは、商品、画像、カテゴリのカルーセルに適しています。
RecyclerViewには(onItemClickListenerを持つListViewとは異なり)組み込みのonClickListenerはありません。ViewHolderで実装します:コンストラクタにラムダを渡し、itemView.setOnClickListenerを設定します。例:class ArticleViewHolder(itemView: View, val onItemClick: (Article) -> Unit) : RecyclerView.ViewHolder(itemView)。bind()内でonItemClick(article)を呼び出します。代替として、ジェスチャ処理にRecyclerView.addOnItemTouchListener()を使用することもできます。
RecycledViewPoolは、スクロール中に非表示になったViewHolderの内部プールです。破棄される代わりに、プールに保持され再利用されます。デフォルトでは、プールは各タイプの5つのViewHolderを保存します。複雑なレイアウトの場合はサイズを増やしてください:recyclerView.getRecycledViewPool().setMaxRecycledViews(VIEW_TYPE_NORMAL, maxCount)。RecyclerViewに複数のViewタイプ(viewType)がある場合、プールはそれぞれに個別のスタックを保持します。異なるRecyclerView間でRecycledViewPoolを共有すると、ネストされたリストがある画面でメモリを節約できます。
まとめ
ターンキー方式のモバイルアプリケーションを開発します
IT Sectrは2017年からスタートアップや企業向けにiOS・Androidアプリケーションを開発しています。私たちがご相談に乗り、最適なソリューションをご提案します。