プロジェクト

全般

プロフィール

Androidプログラミング-RecyclerView

概要

多数のデータから一部をリスト表示する

RecyclerViewは、リスト表示部品で、特に多数のデータの一部分を表示するのに向く部品です。Androidには古くからリスト表示部品であるListViewが存在していますが、RecyclerViewはListViewに替わり新しい機能を持ちます。

関係するパーツ

  • RecyclerView を画面レイアウトに貼付
  • RecyclerView.LayoutManager プロパティにどのようなレイアウトをするかレイアウトマネージャ名を指定
  • 1つのデータの表示レイアウトを定義するXML
  • RecyclerView.Adapter サブクラスを作成して実装
  • RecyclerView.ViewHolder サブクラスを作成して実装

recyclerview.png

データとの関連付け

表示対象となるデータは、ArrayList等のコレクションに格納しておく方法と、Cursorで外部から取得してくる方法とがあります。
RecyclerViewの入門的なサンプルは、実装が簡単なコレクションに格納したデータを扱うものが多いです。しかし、実際使う場合はデータベースに格納したデータを扱うことが多いでしょう。

実装例

コンテントプロバイダから取得したCursorからリスト表示

体温を記録する単純なアプリケーションでの使用例を述べます。検温した日時と体温(℃)を端末内データベースに永続化し、それを一覧表示します。体温の記録はコンテントプロバイダとして提供される想定です。

画面レイアウトにRecyclerViewを貼付

Android Studio 4.0 のレイアウトエディタが持つパレットには残念ながら RecyclerView がありません。そこで、XMLファイルに直接記述します。

  • activity_main.xml
    <?xml version="1.0" encoding="utf-8"?>
    <androidx.constraintlayout.widget.ConstraintLayout
        xmlns:android="http://schemas.android.com/apk/res/android" ...>
    
      :
      <androidx.recyclerview.widget.RecyclerView />
    
    

属性は後でレイアウトエディタ上で設定できるので、まずは要素だけXMLファイルに記述します。

レイアウトマネージャを指定

RecyclerView に、各アイテムをどのように配列するかを制御するレイアウトマネージャを指定します。
ライブラリが提供するレイアウトマネージャは次の3種類です。

  • LinearLayoutManager
    アイテムを水平、または垂直に配置
  • GridLayoutManager
    アイテムを均等な格子状に配置
  • StaggeredGridLayoutManager
    アイテムを不均等な格子状に配置

独自のレイアウトマネージャを実装してそれを指定することも可能です。

本サンプルでは、リスト(一覧)表示をするのでLinearLayout(垂直)を使用します。
レイアウトエディタで RecyclerView を選択し、属性 layoutManager に LinearLayoutManager をテキスト入力します。

1つのデータの表示レイアウトを定義

一つのデータ表示項目のレイアウトを独立したXMLファイルに記述します。
今回は、検温日時と体温の2つを1行に表示します。

LinearLayout で水平にTextViewを2つ並べた例を次に示します。

  • list_item.xml
    <?xml version="1.0" encoding="utf-8"?>
    <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" 
        android:layout_width="match_parent" 
        android:layout_height="wrap_content">
        <TextView
            android:id="@+id/dateTimeView" 
            android:layout_width="0dp" 
            android:layout_height="wrap_content" 
            android:layout_weight="1" 
            android:textAlignment="center" />
    
        <TextView
            android:id="@+id/temperatureView" 
            android:layout_width="0dp" 
            android:layout_height="wrap_content" 
            android:layout_weight="1" 
            android:text="TextView" 
            android:textAlignment="center" />
    
    </LinearLayout>
    

RecyclerView.Adapter サブクラスを作成

Adapterサブクラスの作成でやる事は次です。

  • RecyclerView.Adapter を継承したクラスの定義
  • RecyclerView.ViewHolder を継承したネストクラスの定義
  • onCreateViewHolderメソッドの実装
  • getItemCountメソッドの実装
  • onBindViewHolderメソッドの実装

メソッドは仮の実装とした Adapter サブクラスの全体像は次です。

class TempAdapter(private var cursor: Cursor?) : RecyclerView.Adapter<TempAdapter.TempViewHolder>() {

    class TempViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) {
        val dateTimeView: TextView = itemView.dateTimeView
        val temperatureView: TextView = itemView.temperatureView
    }

    override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): TempViewHolder {
        TODO("Not yet implemented")
    }

    override fun onBindViewHolder(holder: TempViewHolder, position: Int) {
        TODO("Not yet implemented")
    }

    override fun getItemCount(): Int {
        TODO("Not yet implemented")
    }
}
RecyclerView.Adapter を継承したクラスの定義

RecyclerView.Adapterクラスは、仮型パラメータにRecyclerView.ViewHolderクラスを持つジェネリクスクラスです。
class Adapter<HV extends ViewHolder> (修飾子等省略)

そこで、ネストクラスにViewHolder サブクラスを定義し、それを型パラメータで取ります。(ネストクラスでなくてもかまいませんが)

RecyclerView.ViewHolder を継承したネストクラスの定義

コンストラクタで渡されるアイテム1件(1行)の各ビュー要素を保持します。

    class TempViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) {
        val dateTimeView: TextView = itemView.dateTimeView
        val temperatureView: TextView = itemView.temperatureView
    }

ここでは、Kotlin Android Extensions を利用して findViewByIdを呼ばずにレイアウトに定義されたビューを取得しています。
最近では、ViewBindingを使用した方がよいかと思います。

onCreateViewHolderメソッドの実装
    override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): TempViewHolder {
        return TempViewHolder(
            LayoutInflater.from(parent.context).inflate(R.layout.list_item, parent, false)
        )
    }

TempViewHolderのインスタンスを返却する実装を記述します。
inflateメソッドの第1引数に指定している R.layout.list_item の list_item は、レイアウトXMLファイル名に基づきます。

getItemCountメソッドの実装
    override fun getItemCount() = cursor?.let { if (it.isClosed) 0 else it.count } ?: 0
onBindViewHolderメソッドの実装
    override fun onBindViewHolder(holder: TempViewHolder, position: Int) {
        cursor?.let {
            if (it.isClosed) return
            it.moveToPosition(position)
            holder.dateTimeView.text = getDateTime(it)
            holder.temperatureView.text = "%.1f".format(getTemperature(it))
        }
    }

cursorがnullでない時の処理を cursor?.letで指定しています。

cursorから日時を文字列で取り出す処理をprivateメソッドで定義しています。

    private fun getDateTime(cursor: Cursor): String {
        val isoDateTimetext = cursor.getString(cursor.getColumnIndex(TempContract.TempEntry.MEASURED_AT))
        val measuredAt = LocalDateTime.parse(isoDateTimetext, DateTimeFormatter.ISO_LOCAL_DATE_TIME)
        return measuredAt.format(DATE_TIME_VIEW_FORMATTER)
    }

cursorから計測値(体温)を取り出す処理をprivateメソッドで定義しています。

    private fun getTemperature(cursor: Cursor) =
        cursor.getDouble(cursor.getColumnIndex(TempContract.TempEntry.MEASUREMENT))

参考記事