RecyclerViewは子Viewを並べて表示するコンテナタイプ(ConstraintLayoutと同じ)のViewです。
複数のデータをスクリーン上に一覧表示したい時、例えば電話帳のような「氏名+住所+電話番号」の一覧を表示する場合などに最適です。
アプリを開発していると一覧表示したいデータが多いことに気付きます。
なのでRecyclerViewはとても重要で重宝するViewです。
しかし、思い通りの表示を行わせるためのテクニックが多すぎて、使いこなしが難しいです。
今まで調べたテクニックを忘れないように、整理して書き残そうと思います。
今回は基本の「RecyclerViewの実装」です。
RecyclerViewとは
RecyclerViewは子Viewを並べて表示するコンテナタイプ(ConstraintLayoutと同じ)のViewです。
表示範囲内に収まらなかった子Viewはスクロールすることで表示出来ます。
Android APIにListViewならびにGridViewというViewがあります。こちらも子Viewを並べて表示するコンテナタイプのViewです。
RecyclerViewはこのListViewとGridViewの後継として登場してきました。
「Recycler」という名前がついている通り、スクロールした時に表示範囲外へ出た子Viewを表示範囲内に入った子Viewで再利用(Recycle)する仕組みを備えています。
これにより、余分な子Viewを作らなくて済むため、メモリーの節約とパフォーマンス(スクロールの反応速度など)の向上が望めます。
この点がListViewやGridViewとの大きな違いです。
現在はListViewとGridViewに代わってRecyclerViewの使用が主流になっています。
※LsitViewとGridViewはレガシー扱い
環境設定
RecyclerViewはAndroid APIではなく外部ライブラリー(AndroidX)で提供されます。
RecyclerViewを使うためにライブラリの依存リストへ次の一行が必要です。
dependencies { .... implementation 'androidx.recyclerview:recyclerview:1.1.0' ... }
この指定は、Android StudioのレイアウトエディタでRecyclerViewを配置すると、自動的に行われるかも知れません。
自動で指定されない場合はbuild.gradleファイルへ追加してください。
RecyclerViewの構成
表示の構成
表示の構成は、一覧表示されるデータの集まりをアイテム(Item、項目)といい、アイテムを構成する個々のデータをエレメント(Element、要素)といいます。
プログラムの構成
プログラムの構成は、RecyclerView・LayoutManager・Adapterの3つに分けられます。
各々は図に書いた役割があり、RecyclerViewの指示により連携して動作します。
この中で最もカスタマイズの必要な部分がAdapterです。
アイテムの内容(要素の内容と数と配置)はアプリケーション毎に異なります。
なので、Adapterはアイテムデータに合わせて個別に作成しなければなりません。
RecyclerViewの実装の例
RecyclerViewの簡単な例をあげて実装方法を紹介します。
アイテムデータ
アイテムデータはエレメントとして文字列のデータ(TextView)を1つ持ちます。
アイテムデータは配列にまとめられています。
val _items = arrayOf( "Item 0", "Item 1", "Item 2", "Item 3", "Item 4", "Item 5", "Item 6", "Item 7", "Item 8", "Item 9", "Item A", "Item B", "Item C", "Item D", "Item E", "Item F" )
アイテムレイアウト
アイテムレイアウトは文字列のエレメントを表示するTextViewのみです。
アイテムを一覧表示したとき、アイテムの境界が分かるように余白(margin)を持たせています。
<?xml version="1.0" encoding="utf-8"?> <androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:app="http://schemas.android.com/apk/res-auto" android:layout_width="match_parent" android:layout_height="38dp" android:layout_margin="1dp" android:background="#DFE6F6"> <TextView android:id="@+id/txtMesg" android:layout_width="0dp" android:layout_height="0dp" android:gravity="center" android:text="Message" app:layout_constraintBottom_toBottomOf="parent" app:layout_constraintEnd_toEndOf="parent" app:layout_constraintStart_toStartOf="parent" app:layout_constraintTop_toTopOf="parent" /> </androidx.constraintlayout.widget.ConstraintLayout>
カスタムAdapter
AdapterはRecyclerView.Adapterを継承して作ります。
少なくともViewHolder、getItemCount()、onCreateViewHolder()、onBindViewHolder()の実装が必要です。
実装の内容はコメントへ記載しました。
class ArrayAdapter(var items: Array<String>) : RecyclerView.Adapter<ArrayAdapter.ArrayViewHolder>() { // ----- ビューホルダー ------------------------------------------ class ArrayViewHolder(val view: View) : RecyclerView.ViewHolder(view) { val mesg = view.findViewById<TextView>(R.id.txtMesg) } // ----- アダプター本体 ------------------------------------------ override fun getItemCount(): Int { return items.size // アイテム数を返す } override fun onCreateViewHolder(parent: ViewGroup, viewType: Int) : ArrayViewHolder { val _view = LayoutInflater.from(parent.context) .inflate(R.layout.item, parent, false) return ArrayViewHolder(_view) // ViewHolderを返す } override fun onBindViewHolder(holder: ArrayViewHolder, position: Int) { holder.mesg.text = items[position] // 要素とデータを紐づける } }
ViewHolderはアイテム並びにエレメントのViewオブジェクトを格納する入れ物です。
先に述べたアイテムのView(RecyclerView内の子View)の再利用は、このViewHolder単位で行われます。
Viewオブジェクトを取り出すfindViewById( )関数は処理の重い処理です。ViewHolderが再利用できれば、findViewById( )の実行が省けます。
これがパフォーマンスの面で有利になります。
Adapterと同様に、ViewHolderはRecyclerView.ViewHolderを継承して作ります。
AdapterとLayoutManagerの組み込み
RecyclerViewのプロパティへAdapterとLayoutManagerを代入します。
val _adapter = ArrayAdapter(_items) val _manager = LinearLayoutManager(this@MainActivity) val _recyclerView = findViewById<RecyclerView>(R.id.rcySample) _recyclerView.adapter = _adapter _recyclerView.layoutManager = _manager
例はLayoutManagerにLinearLayoutManagerを指定しています。デフォルトで使っているので縦方向にアイテムを並べる動作になります。
例の実行結果
一覧表示するアイテム数(0~Fの16個)が多いため、全てのアイテムを表示出来ません。見えないアイテムはスクロールすれば表示できます。
様々なデータのタイプ
アイテムのデータを格納するオブジェクトが様々なデータタイプであっても、Adapterの実装で対応が可能です。
次にあげるのは、Array・List・CursorAdapterの異なるデータタイプを扱うAdapterですが、どれも同じ動作をします。
class ArrayAdapter(var items: Array<String>) : RecyclerView.Adapter<ArrayAdapter.ArrayViewHolder>() { // ----- ビューホルダー ------------------------------------------ class ArrayViewHolder(val view: View) : RecyclerView.ViewHolder(view) { val mesg = view.findViewById<TextView>(R.id.txtMesg) } // ----- アダプター本体 ------------------------------------------ override fun getItemCount(): Int { return items.size // アイテム数を返す } override fun onCreateViewHolder(parent: ViewGroup, viewType: Int) : ArrayViewHolder { val _view = LayoutInflater.from(parent.context) .inflate(R.layout.item, parent, false) return ArrayViewHolder(_view) // ViewHolderを返す } override fun onBindViewHolder(holder: ArrayViewHolder, position: Int) { holder.mesg.text = items[position] // 要素とデータを紐づける } }
val _items = arrayOf( "Item 0", "Item 1", "Item 2", "Item 3", "Item 4", "Item 5", "Item 6", "Item 7", "Item 8", "Item 9", "Item A", "Item B", "Item C", "Item D", "Item E", "Item F" ) val _adapter = ArrayAdapter(_items)
※「RecyclerViewの実装」と同じ
class ListAdapter(var items: List<String>) : RecyclerView.Adapter<ListAdapter.ListViewHolder>() { // ----- ビューホルダー ------------------------------------------ class ListViewHolder(val view: View) : RecyclerView.ViewHolder(view) { val mesg = view.findViewById<TextView>(R.id.txtMesg) } // ----- アダプター本体 ------------------------------------------ override fun getItemCount(): Int { return items.size // アイテム数を返す } override fun onCreateViewHolder(parent: ViewGroup, viewType: Int) : ListViewHolder { val _view = LayoutInflater.from(parent.context) .inflate(R.layout.item, parent, false) return ListViewHolder(_view) // ViewHolderを返す } override fun onBindViewHolder(holder: ListViewHolder, position: Int) { holder.mesg.text = items[position] // 要素とデータを紐づける } }
val _items = listOf( "Item 0", "Item 1", "Item 2", "Item 3", "Item 4", "Item 5", "Item 6", "Item 7", "Item 8", "Item 9", "Item A", "Item B", "Item C", "Item D", "Item E", "Item F" ) val _adapter = ListAdapter(_items)
class CursorAdapter(var items: Cursor) : RecyclerView.Adapter<CursorAdapter.ViewHolder>() { init { items.moveToFirst() } // ----- ビューホルダー ------------------------------------------ class ViewHolder(val view: View) : RecyclerView.ViewHolder(view) { val mesg = view.findViewById<TextView>(R.id.txtMesg) } // ----- アダプター本体 ------------------------------------------ override fun getItemCount(): Int { return items.count // アイテム数を返す } override fun onCreateViewHolder(parent: ViewGroup, viewType: Int) : ViewHolder { val _view = LayoutInflater.from(parent.context) .inflate(R.layout.item, parent, false) return ViewHolder(_view) // ViewHolderを返す } override fun onBindViewHolder(holder: ViewHolder, position: Int) { // 要素とデータを紐づける items.moveToPosition(position) holder.mesg.text = items.getString(items.getColumnIndex("mesg")) } }
val _items = MatrixCursor(arrayOf("_id", "mesg")).apply { addRow(arrayOf(1, "Item 0")) addRow(arrayOf(2, "Item 1")) addRow(arrayOf(3, "Item 2")) addRow(arrayOf(4, "Item 3")) addRow(arrayOf(5, "Item 4")) addRow(arrayOf(6, "Item 5")) addRow(arrayOf(7, "Item 6")) addRow(arrayOf(8, "Item 7")) addRow(arrayOf(9, "Item 8")) addRow(arrayOf(10, "Item 9")) addRow(arrayOf(11, "Item A")) addRow(arrayOf(12, "Item B")) addRow(arrayOf(13, "Item C")) addRow(arrayOf(14, "Item D")) addRow(arrayOf(15, "Item E")) addRow(arrayOf(16, "Item F")) } val _adapter = CursorAdapter(_items)
様々なアイテムのレイアウト
LayoutManagerを変えることで、アイテムの並べ方が変えられます。
LinearLayoutManager(縦)
アイテムを縦に並べて一覧表示します。
(1)と(2)は同じ動作です。(1)は(2)を詳細にパラメータ指定した場合です。
val _manager = LinearLayoutManager( this@MainActivity, LinearLayoutManager.VERTICAL, false) // (1) // val _manager = LinearLayoutManager(this@MainActivity) // (2)
※(1)の第3引数は並べる順番(false:昇順、true:降順)の指定です。
LinearLayoutManager(横)
アイテムを横に並べて一覧表示します
val _manager = LinearLayoutManager( this@MainActivity, LinearLayoutManager.HORIZONTAL, false)
※第3引数は並べる順番(false:昇順、true:降順)の指定です。
GridLayoutManager(格子状、縦方向に積む)
アイテムを格子状に並べて一覧表示します。格子は縦方向へ積まれます。
(1)と(2)は同じ動作です。(1)は(2)を詳細にパラメータ指定した場合です。
val _manager = GridLayoutManager( this@MainActivity, 3, RecyclerView.VERTICAL, false) // (1) // val _manager = GridLayoutManager(this@MainActivity, 3) // (2)
※第2引数は列の数です。
※(1)の第4引数は並べる順番(false:昇順、true:降順)の指定です。
GridLayoutManager(格子状、横方向に積む)
アイテムを格子状に並べて一覧表示します。格子は横方向へ積まれます。
val _manager = GridLayoutManager( this@MainActivity, 3, RecyclerView.HORIZONTAL, false)
※第2引数は行の数です。
※第4引数は並べる順番(false:昇順、true:降順)の指定です。
StaggeredGridLayoutManager(格子状、縦方向に積む、高さが異なる)
高さが異なるアイテムを格子状に並べて一覧表示します。格子は縦方向へ積まれます。
val _manager = StaggeredGridLayoutManager(3, RecyclerView.VERTICAL)
※第1引数は列の数です。
StaggeredGridLayoutManager(格子状、横方向に積む、幅が異なる)
幅が異なるアイテムを格子状に並べて一覧表示します。格子は横方向へ積まれます。
val _manager = StaggeredGridLayoutManager(3, RecyclerView.HORIZONTAL)
※第1引数は行の数です。
関連記事: