RecyclerViewの実装

投稿日:  更新日:

RecyclerViewは子Viewを並べて表示するコンテナタイプ(ConstraintLayoutと同じ)のViewです。

複数のデータをスクリーン上に一覧表示したい時、例えば電話帳のような「氏名+住所+電話番号」の一覧を表示する場合などに最適です。

アプリを開発していると一覧表示したいデータが多いことに気付きます。

なのでRecyclerViewはとても重要で重宝するViewです。

しかし、思い通りの表示を行わせるためのテクニックが多すぎて、使いこなしが難しいです。

今まで調べたテクニックを忘れないように、整理して書き残そうと思います。

今回は基本の「RecyclerViewの実装」です。

スポンサーリンク

RecyclerViewとは

RecyclerViewは子Viewを並べて表示するコンテナタイプ(ConstraintLayoutと同じ)のViewです。

表示範囲内に収まらなかった子Viewはスクロールすることで表示出来ます。

RecyclerViewとは

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を指定しています。デフォルトで使っているので縦方向にアイテムを並べる動作になります。

例の実行結果

RecyclerViewの例
【アイテムの最上部】

RecyclerViewの例
【アイテムの最下部】

一覧表示するアイテム数(0~Fの16個)が多いため、全てのアイテムを表示出来ません。見えないアイテムはスクロールすれば表示できます。

スポンサーリンク

様々なデータのタイプ

アイテムのデータを格納するオブジェクトが様々なデータタイプであっても、Adapterの実装で対応が可能です。

次にあげるのは、Array・List・CursorAdapterの異なるデータタイプを扱うAdapterですが、どれも同じ動作をします。

ArrayListCursor
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:降順)の指定です。

RecyclerViewの例

LinearLayoutManager(横)

アイテムを横に並べて一覧表示します

        val _manager = LinearLayoutManager(
                this@MainActivity, LinearLayoutManager.HORIZONTAL, false)

※第3引数は並べる順番(false:昇順、true:降順)の指定です。

RecyclerViewの例

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:降順)の指定です。

RecyclerViewの例

GridLayoutManager(格子状、横方向に積む)

アイテムを格子状に並べて一覧表示します。格子は横方向へ積まれます。

        val _manager = GridLayoutManager(
                this@MainActivity, 3, RecyclerView.HORIZONTAL, false)

※第2引数は行の数です。
※第4引数は並べる順番(false:昇順、true:降順)の指定です。

RecyclerViewの例

StaggeredGridLayoutManager(格子状、縦方向に積む、高さが異なる)

高さが異なるアイテムを格子状に並べて一覧表示します。格子は縦方向へ積まれます。

        val _manager = StaggeredGridLayoutManager(3, RecyclerView.VERTICAL)

※第1引数は列の数です。

RecyclerViewの例

StaggeredGridLayoutManager(格子状、横方向に積む、幅が異なる)

幅が異なるアイテムを格子状に並べて一覧表示します。格子は横方向へ積まれます。

        val _manager = StaggeredGridLayoutManager(3, RecyclerView.HORIZONTAL)

※第1引数は行の数です。

recyclerViewの例

スポンサーリンク

関連記事:

RecyclerViewでアイテムのクリックイベントを取得し、処理を実行する方法を紹介します。 ...
RecyclerViewはアイテムのレイアウトをアイテム毎に変更できます。その時に使う値がViewTypeです。 ViewTypeでアイテムのレイアウトを変更する方法を紹介します。 ...
RecyclerViewは表示が変更される(アイテムの更新、スクロール)時、アイテムのViewをリサイクル(再生利用)します。 これにより余分なViewの作成が行われなくなり、メモリーの節約とパフォーマンスの向上が望めます。 リサイクルはCachedViewsとRecyclerPoolという2つのキャッシュで行われます。 このキャッシュを使ったリサイクルの動作を調べたので紹介します。 ...
RecyclerViewのリサイクル動作で使われるキャッシュは、サイズを大きくすれば多くのViewHolderが保持できます。その分、多くのメモリを消費します。 ViewHolderを多く保持できたとしても、サイクル動作で効率よく使われなければ、メモリの浪費です。 キャッシュのサイズはRecyclerViewの使われ方よって適切なサイズがあります。 そのため、RecyclerViewはキャッシュのサイズを変更できるようになっています。 ...
RecyclerViewはアイテムを一覧表示してくれます。 ただ一覧表示するだけではなく、「追加・削除・移動・切り替え」といったアイテムの表示を効率よく変更する仕組み持っています。 今回はこの仕組みを使ったアイテムの変更方法を紹介します。 ...
RecyclerViewのリサイクル動作で使われるキャッシュは、サイズを大きくすれば多くのViewHolderが保持できます。その分、多くのメモリを消費します。 ViewHolderを多く保持できたとしても、サイクル動作で効率よく使われなければ、メモリの浪費です。 キャッシュのサイズはRecyclerViewの使われ方よって適切なサイズがあります。 そのため、RecyclerViewはキャッシュのサイズを変更できるようになっています。 ...
RecyclerViewが空(アイテムが無い)の時、EmptyViewを表示する実装を行ったので紹介します。 ...
RecyclerView上のアイテムを選択する方法を紹介します。 外部ライブラリー(AndroidX)で提供されるrecyclerview-selection APIを用いた方法です。 ...
RecyclerViewはListView(RecyclerViewの前身)の時に存在していたChoiceModeがありません。 同様な機能が欲しければプログラマ側で実装しなければなりません。 RecyclerView.AdapterをカスタマイズしてChoiceModeを実装してみたので紹介します。 ...
RecyclerViewはアイテムへ装飾を付けることが出来るようになっています。 装飾とは、例えばアイテムの区切り線などです。 今回はアイテムへ装飾を付ける方法を紹介します。 ...
RecyclerViewに表示しきれなかったアイテムはスクロールを行うことで表示されるようになっています。 スクロールは「外部入力(指でスクリーン上をタッチしてスライド)によるスクロール」の他に、「プログラムによるスクロール」をすることも出来ます。 今回はこのアイテムのスクロールについてまとめてみました。 ...
RecyclerViewはアイテムをスクロールさせて隠れたアイテムを表示できます。 スクロールを止める位置は任意です。 任意であるがゆえに、止めた位置によってはアイテムの一部が欠けてしまうこともあります。 携帯端末の画面は狭いので、効率よくコンテンツの表示を行いたいとアプリ開発者は考えます。 アイテムの一部が欠けてしまうことは、効率が良いとは言えません。 このような問題を解決するために、アイテムのスナップをRecyclerViewへ追加できます。 アイテムのスナップを追加する方法を紹介します。 ...
RecyclerViewでアイテムの変更(Change/Insert/Move/Remove)を行うと、変更される様子がアニメーション化されています。 これはデフォルトでアイテムの変更アニメーションが組み込まれているためです。 デフォルトは単純なアニメーションですが、「ある」と「ない」の違いは歴然で、アニメーションのある方が高価なアプリケーションに見えます。 GUI(Graphical User Interface)が主体の携帯端末にとって、利用者に対するアプリの見せ方は重要です。高価に見えた方が使ってもらえる可能性が高くなります。 上記のことから、アプリの機能に関係なくても、ちょっとした動きをアニメーション化するメリットがあります。 2回にわたりアイテムの変更アニメーションについてまとめてみました。 アイテムの変更アニメーション(DefaultItemAnimator、デフォルト) アイテムの変更アニメーション(SimpleItemAnimatorの継承、カスタム) 今回は第1回目「ItemAnimator、デフォルト」編です。デフォルト変更アニメーションの動作について説明します。 ...
RecyclerViewでアイテムの変更(Change/Insert/Move/Remove)を行うと、変更される様子がアニメーション化されています。 これはデフォルトでアイテムの変更アニメーションが組み込まれているためです。 デフォルトは単純なアニメーションですが、「ある」と「ない」の違いは歴然で、アニメーションのある方が高価なアプリケーションに見えます。 GUI(Graphical User Interface)が主体の携帯端末にとって、利用者に対するアプリの見せ方は重要です。高価に見えた方が使ってもらえる可能性が高くなります。 上記のことから、アプリの機能に関係なくても、ちょっとした動きをアニメーション化するメリットがあります。 2回にわたりアイテムの変更アニメーションについてまとめてみました。 アイテムの変更アニメーション(DefaultItemAnimator、デフォルト) アイテムの変更アニメーション(SimpleItemAnimatorの継承、カスタム) 今回は第2回目「SimpleItemAnimatorの継承、カスタム」編です。カスタム変更アニメーションの作り方を説明 ...
RecyclerViewへアイテムが表示されるとき、アニメーションはありません。。一瞬で表示されて終わりです。 「RecyclerViewへアイテムが表示される」ことを、ここでは「アイテムの出現」と言い表すことにします。 このアイテムの出現にアニメーションを付ける方法を紹介します。 アイテムの出現をアニメーションで演出することで、RecyclerViewに表示したい内容が際立つと思います。 ...
スポンサーリンク