Android Kotlin の基礎 07.1: RecyclerView の基礎

この Codelab は、Android Kotlin の基礎コースの一部です。Codelab を順番に進めていくと、このコースを最大限に活用できます。すべてのコース Codelab は Android Kotlin の基礎 Codelab ランディング ページに掲載されています。

はじめに

この Codelab では、RecyclerView を使用してアイテムのリストを表示する方法について説明します。これまでの Codelab シリーズの睡眠管理アプリをベースに、RecyclerView と推奨アーキテクチャを使用して、的確で多様なデータ表示方法について学習します。

前提となる知識

以下について把握しておく必要があります。

  • アクティビティ、フラグメント、ビューを使用した基本的なユーザー インターフェース(UI)の作成。
  • フラグメント間を移動し、safeArgs を使用してフラグメント間でデータを渡す。
  • ビューモデルを使用して、モデル ファクトリ、変換、LiveData とそのオブザーバーを表示します。
  • Room データベースの作成、DAO の作成、エンティティの定義。
  • データベース タスクやその他の長時間実行タスクにコルーチンを使用する。

学習内容

  • AdapterViewHolderRecyclerView を使用してアイテムのリストを表示する方法。

演習内容

  • 前のレッスンの TrackMySleepQuality アプリを、RecyclerView を使って睡眠の質のデータを表示するように変更しました。

この Codelab では、睡眠の質を追跡するアプリの RecyclerView 部分を作成します。アプリは Room データベースを使用して、睡眠データを時系列で保存します。

スターター スリープ トラッカー アプリには、次の図に示すように、フラグメントで表される画面が 2 つあります。

左側の最初の画面には、トラッキングを開始および停止するボタンがあります。この画面には、ユーザーのすべての睡眠データも表示されます。[消去] ボタンをクリックすると、そのアプリがユーザーのために収集したすべてのデータが完全に削除されます。右の 2 番目の画面では、睡眠の質の評価を選択しています。

このアプリには、UI コントローラ、ViewModelLiveData を含むシンプルなアーキテクチャが使用されています。また、このアプリは Room データベースを使用して睡眠データを永続的にします。

最初の画面で表示される睡眠の夜間のリストは機能していますが、正しくありません。アプリは複雑なフォーマッタを使用して、テキストビュー用のテキスト文字列と、品質を示す数値を作成します。また、このデザインは拡張性がありません。この Codelab でこれらの問題をすべて修正すると、最終的なアプリは同じ機能を持ち、メイン画面は次のようになります。

データのリストやグリッドを表示することは、Android の UI タスクとしては非常に一般的なものです。リストは単純なものから非常に複雑なものまで多岐にわたります。テキストビューのリストには、ショッピング リストなどの単純なデータが表示される場合があります。複雑なリスト(旅行先に関するアノテーション付きリストなど)は、見出しを含むスクロール グリッド内に多数の詳細情報を表示します。

このようなユースケースに対応するために、Android には RecyclerView ウィジェットが用意されています。

RecyclerView の最大のメリットは、大規模なリストで効率が上がることです。

  • デフォルトでは、RecyclerView は画面上に現在表示されているアイテムを処理または描画する場合にのみ機能します。たとえば、リストには要素が 1,000 個ありますが、表示される要素が 10 個しかない場合、RecyclerView は画面に 10 個のアイテムを描画するのに十分な作業を行います。ユーザーがスクロールすると、RecyclerView は画面に新たに表示されるアイテムを認識して、その描画に十分な作業のみを行います。
  • アイテムが画面外にスクロールされると、アイテムのビューはリサイクルされます。具体的には、画面内にスクロールしてくる新たなコンテンツがその項目に設定されます。この RecyclerView の動作により、処理時間が大幅に短縮され、リストをスムーズにスクロールできます。
  • 項目が変更されると、リスト全体を再描画するのではなく、RecyclerViewその項目を更新できます。複雑なアイテムのリストを表示すると、効率が大幅に向上します。

次のシーケンスでは、1 つのビューにデータ ABC が設定されています。そのビューが画面外にスクロールされると、RecyclerView でそのビューが新しいデータ XYZ に再利用されます。

アダプタ パターン

異なるコンセントを使う国外に移動する場合は、アダプターを使ってデバイスをコンセントに接続することをおすすめします。アダプターを使用すると、あるタイプのプラグを別の種類のプラグに変換でき、それによってインターフェースが実際に変換されます。

ソフトウェア エンジニアリングにおけるアダプター パターンは、オブジェクトが別の API を操作するために役立ちます。RecyclerView は、アプリによるデータの保存と処理方法を変更することなく、アダプターを使用してアプリデータを RecyclerView が表示可能な形式に変換します。睡眠トラッカー アプリの場合は、Room データベースのデータを、RecyclerViewViewModel を変更せずに表示する方法に適応させるアダプターを作成します。

RecyclerView を実装する

RecyclerView にデータを表示するには、次のパーツが必要です。

  • 表示するデータです。
  • レイアウト ファイルで定義された RecyclerView インスタンス。ビューのコンテナとして機能します。
  • 1 つのデータの項目のレイアウト。
    すべてのリスト項目が同じに見える場合、それらすべてに同じレイアウトを使用できますが、これは必須ではありません。アイテム レイアウトは、一度に 1 つのアイテムビューを作成してデータで埋められるように、フラグメントのレイアウトとは別に作成する必要があります。
  • レイアウト マネージャー。
    レイアウト マネージャーは、ビュー内の UI コンポーネントの構成(レイアウト)を処理します。
  • ビューホルダー。
    ビューホルダーは ViewHolder クラスを拡張します。これには、アイテムのレイアウトから 1 つのアイテムを表示するためのビュー情報が含まれています。ビューホルダーにより、RecyclerView で画面での効率的なビューの移動に使用される情報も追加されます。
  • アダプター。
    アダプターはデータを RecyclerView に接続します。ViewHolder に表示できるようにデータを調整します。RecyclerView は、アダプタを使用して画面上にデータを表示します。

このタスクでは、レイアウト ファイルに RecyclerView を追加し、睡眠データを RecyclerView に公開するように Adapter を設定します。

ステップ 1: LayoutManager を使用して RecyclerView を追加する

このステップでは、fragment_sleep_tracker.xml ファイル内の ScrollViewRecyclerView に置き換えます。

  1. GitHub から RecyclerViewFundamentals-Starter アプリをダウンロードします。
  2. アプリをビルドして実行します。データが単純なテキストとして表示されることに注意してください。
  3. Android Studio の [Design] タブで fragment_sleep_tracker.xml レイアウト ファイルを開きます。
  4. [Component Tree] ペインで、ScrollView を削除します。この操作を行うと、ScrollView 内にある TextView も削除されます。
  5. [Palette] パネルで、左側のコンポーネント タイプの一覧をスクロールして [Containers] を選択します。
  6. RecyclerView を [Palette] ペインから [Component Tree] ペインにドラッグします。RecyclerViewConstraintLayout 内に配置します。

  1. 依存関係を追加するかどうかを尋ねるダイアログが表示されたら、[OK] をクリックして Android Studio で Gradle ファイルに recyclerview 依存関係を追加できるようにします。数秒後にアプリの同期が行われます。

  1. モジュールの build.gradle ファイルを開き、一番下までスクロールして、新しい依存関係に注意してください。次のようなコードです。
implementation 'androidx.recyclerview:recyclerview:1.0.0'
  1. fragment_sleep_tracker.xml に戻します。
  2. [Text] タブで、次の RecyclerView コードを探します。
<androidx.recyclerview.widget.RecyclerView
   android:layout_width="match_parent"
   android:layout_height="match_parent" />
  1. RecyclerViewsleep_listid を割り当てます。
android:id="@+id/sleep_list"
  1. RecyclerView を、ConstraintLayout 内の画面の残りの部分を占めるように配置します。そのためには、RecyclerView の上端をスタートボタンに制限し、下端をクリアボタンに制限します。Layout Editor または XML で、次のコードを使用してレイアウトの幅と高さを 0 dp に設定します。
    android:layout_width="0dp"
    android:layout_height="0dp"
    app:layout_constraintBottom_toTopOf="@+id/clear_button"
    app:layout_constraintEnd_toEndOf="parent"
    app:layout_constraintStart_toStartOf="parent"
    app:layout_constraintTop_toBottomOf="@+id/stop_button"
  1. RecyclerView XML にレイアウト マネージャーを追加します。すべての RecyclerView には、リスト内のアイテムの配置方法を指定するレイアウト マネージャーが必要です。Android には、デフォルトで全幅行の縦方向リストにアイテムを配置する LinearLayoutManager が用意されています。
app:layoutManager="androidx.recyclerview.widget.LinearLayoutManager"
  1. [Design] タブに切り替えます。追加された制約により、RecyclerView が使用可能なスペースを埋めるように拡大されています。

ステップ 2: リストアイテムのレイアウトとテキストビューホルダーを作成する

RecyclerView はコンテナにすぎません。このステップでは、RecyclerView 内に表示するアイテムのレイアウトとインフラストラクチャを作成します。

作業中の RecyclerView にできるだけ早く移動するために、最初は睡眠の質を数値として表示するシンプルなリスト項目を使用します。そのためには、ビューホルダー TextItemViewHolder が必要です。データ用のビュー(TextView)も必要です。(ビューホルダーと、すべての睡眠データのレイアウト方法については、この後のステップで説明します)。

  1. text_item_view.xml というレイアウト ファイルを作成します。ルート要素として使用するものは関係ありません。テンプレート コードが置き換えられるからです。
  2. text_item_view.xml で、指定されたコードをすべて削除します。
  3. 先頭と末尾に 16dp のパディング、24sp のテキストサイズを持つ TextView を追加します。幅を親に合わせ、高さをコンテンツをラップします。このビューは RecyclerView 内に表示されるため、ViewGroup 内に配置する必要はありません。
<?xml version="1.0" encoding="utf-8"?>
<TextView xmlns:android="http://schemas.android.com/apk/res/android"
    android:textSize="24sp"
    android:paddingStart="16dp"
    android:paddingEnd="16dp"
    android:layout_width="match_parent"       
    android:layout_height="wrap_content" />
  1. Util.kt を開きます。末尾までスクロールして、下記の定義を追加します。これにより、TextItemViewHolder クラスが作成されます。コードは、ファイルの末尾の最後の右中かっこの後に配置してください。このビューホルダーは一時的なものであり、後で交換するため、コードは Util.kt に入ります。
class TextItemViewHolder(val textView: TextView): RecyclerView.ViewHolder(textView)
  1. プロンプトが表示されたら、android.widget.TextViewandroidx.recyclerview.widget.RecyclerView をインポートします。

ステップ 3: SleepNightAdapter を作成する

RecyclerView の実装における主なタスクは、アダプターを作成することです。アイテムビューのシンプルなビューホルダーと、各アイテムのレイアウトがあるとします。これでアダプターを作成できるようになりました。アダプターはビューホルダーを作成し、表示するデータを入力して RecyclerView を表示します。

  1. sleeptracker パッケージで、SleepNightAdapter という新しい Kotlin クラスを作成します。
  2. SleepNightAdapter クラスを RecyclerView.Adapter に拡張します。このクラスは SleepNightAdapter と呼ばれ、SleepNight オブジェクトを RecyclerView が使用できるものに適応します。アダプターは、使用するビューホルダーを認識する必要があるので、TextItemViewHolder を渡します。メッセージが表示されたら必要なコンポーネントをインポートすると、実装が必要なメソッドがあるため、エラーが表示されます。
class SleepNightAdapter: RecyclerView.Adapter<TextItemViewHolder>() {}
  1. SleepNightAdapter の最上位で、データを保持する listOf SleepNight 変数を作成します。
var data =  listOf<SleepNight>()
  1. SleepNightAdapter で、getItemCount() をオーバーライドして、data の睡眠夜のリストのサイズを返します。RecyclerView は、アダプタが表示するアイテム数を認識する必要があり、それに応じて getItemCount() を呼び出します。
override fun getItemCount() = data.size
  1. 次のように、SleepNightAdapteronBindViewHolder() 関数をオーバーライドします。

    onBindViewHolder() 関数は RecyclerView によって呼び出され、指定した位置にある 1 つのリスト項目のデータを表示します。そのため、onBindViewHolder() メソッドは、ビューホルダーとバインドするデータの位置という 2 つの引数を取ります。このアプリの場合、ホルダーは TextItemViewHolder、掲載順位はリスト内の位置です。
override fun onBindViewHolder(holder: TextItemViewHolder, position: Int) {
}
  1. onBindViewHolder() 内で、データ内の特定の位置にある 1 つのアイテムの変数を作成します。
 val item = data[position]
  1. 作成した ViewHolder には、textView というプロパティがあります。onBindViewHolder() 内で、textViewtext を睡眠の質の数値に設定します。このコードは数値のリストのみを表示しますが、この簡単な例を見ると、アダプターがデータをビューホルダーに取り込み、画面に組み込む様子がわかります。
holder.textView.text = item.sleepQuality.toString()
  1. SleepNightAdapter で、onCreateViewHolder() をオーバーライドして実装します。これは、RecyclerView がアイテムを表すためにビューホルダーを必要とするときに呼び出されます。

    この関数は 2 つのパラメータを受け取り、ViewHolder を返します。parent パラメータ(ビューホルダーを保持するビューグループ)は常に RecyclerView です。viewType パラメータは、同じ RecyclerView に複数のビューがある場合に使用されます。たとえば、テキストビュー、画像、動画のリストをすべて同じ RecyclerView に置いた場合、onCreateViewHolder() 関数は使用するビューの種類を認識する必要があります。
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): TextItemViewHolder {
}
  1. onCreateViewHolder() で、LayoutInflater のインスタンスを作成します。

    レイアウト インフレータは、XML レイアウトからビューを作成する方法を認識しています。context には、ビューを正しくインフレートする方法に関する情報が含まれます。リサイクラー ビュー用のアダプターでは、常に parent ビューグループ(RecyclerView)のコンテキストを渡します。
val layoutInflater = LayoutInflater.from(parent.context)
  1. onCreateViewHolder() で、layoutinflater にインフレートを依頼して view を作成します。

    ビューの XML レイアウトと、ビューの parent ビューグループを渡します。3 番目のブール値の引数は attachToRoot です。このアイテムは RecyclerView によって自動的にビュー階層に追加されるため、この引数は false にする必要があります。
val view = layoutInflater
       .inflate(R.layout.text_item_view, parent, false) as TextView
  1. onCreateViewHolder() で、view で作成された TextItemViewHolder を返します。
return TextItemViewHolder(view)
  1. data はデータについて何も認識していないため、アダプターは、data が変更されたときに RecyclerView に通知する必要があります。認識されるのは、アダプターが与えるビューホルダーのみです。

    表示するデータが変更されたときに RecyclerView に通知するには、SleepNightAdapter クラスの一番上にある data 変数にカスタム セッターを追加します。セッターで、data に新しい値を設定し、notifyDataSetChanged() を呼び出して新しいデータでリストの再描画をトリガーします。
var data =  listOf<SleepNight>()
   set(value) {
       field = value
       notifyDataSetChanged()
   }

ステップ 4: RecyclerView をアダプタに通知する

RecyclerView は、ビューホルダーの取得に使用するアダプターについて認識している必要があります。

  1. SleepTrackerFragment.kt を開きます。
  2. onCreateview() でアダプタを作成します。このコードは、ViewModel モデルの作成後、return ステートメントの前に配置します。
val adapter = SleepNightAdapter()
  1. adapterRecyclerView に関連付けます。
binding.sleepList.adapter = adapter
  1. プロジェクトをクリーンアップおよび再ビルドし、binding オブジェクトを更新します。

    それでも binding.sleepList または binding.FragmentSleepTrackerBinding に関するエラーが表示される場合は、キャッシュを無効にして再起動します。([File] > [Invalidate Cache] > [Restart] の順に選択します)。

    ここでアプリを実行するとエラーは発生しませんが、[Start]、[Stop] の順にタップしたときに表示されるデータはありません。

ステップ 5: アダプターにデータを取得する

ここまでの作業で、アダプターと、アダプターから RecyclerView にデータを取得する方法を確認できました。次に、ViewModel からアダプターにデータを取得する必要があります。

  1. SleepTrackerViewModel を開きます。
  2. すべてのスリープナイトを格納する nights 変数を見つけます。これは表示するデータです。nights 変数は、データベースで getAllNights() を呼び出すことによって設定されます。
  3. nights から private を削除します。この変数にアクセスするオブザーバーが作成されるためです。宣言は次のようになります。
val nights = database.getAllNights()
  1. database パッケージで、SleepDatabaseDao を開きます。
  2. getAllNights() 関数を見つけます。この関数は、SleepNight 値のリストを LiveData として返します。つまり、nights 変数には、Room によって維持される LiveData が含まれ、nights を監視して変更を確認できます。
  3. SleepTrackerFragment を開きます。
  4. onCreateView() で、adapter の作成の下に、nights 変数にオブザーバーを作成します。

    ライフサイクル オーナーとしてフラグメントの viewLifecycleOwner を指定することにより、RecyclerView が画面上に表示されている場合にのみ、このオブザーバーがアクティブになるようにできます。
sleepTrackerViewModel.nights.observe(viewLifecycleOwner, Observer {
   })
  1. オブザーバー内で、null 以外の値(nights 用)を取得するたびに、その値をアダプターの data に割り当てます。オブザーバーとデータ設定が完了したコードは次のとおりです。
sleepTrackerViewModel.nights.observe(viewLifecycleOwner, Observer {
   it?.let {
       adapter.data = it
   }
})
  1. コードをビルドして実行します。

    アダプターが動作している場合、睡眠の質の数値がリストとして表示されます。左側のスクリーンショットでは、[開始] をタップした後に -1 が表示されます。右側のスクリーンショットでは、[停止] をタップして品質評価を選択した後の、更新された睡眠の質の数値を示しています。

ステップ 6: ビューホルダーがリサイクルされる仕組みを理解する

RecyclerView はビューホルダーをリサイクルします。つまり、ビューホルダーが再利用されます。ビューが画面外にスクロールされると、RecyclerView で画面にスクロールしようとしているビューを再利用します。

これらのビューホルダーはリサイクルされるため、onBindViewHolder() では、以前の項目がビューホルダーに行ったカスタマイズをすべて設定またはリセットしてください。

たとえば、品質評価が 1 未満で睡眠の質が悪いビューホルダーで、テキストの色を赤に設定できます。

  1. SleepNightAdapter クラスで、onBindViewHolder() の最後に次のコードを追加します。
if (item.sleepQuality <= 1) {
   holder.textView.setTextColor(Color.RED) // red
}
  1. アプリを実行します。
  2. 睡眠の質が低いデータを追加すると、数字が赤色になります。
  3. 画面上に赤色の数値が表示されるまで、睡眠の質に対する評価を高くします。

    RecyclerView はビューホルダーを再利用するため、最終的に赤いビューホルダーの 1 つを再利用して評価を高めます。評価の高い項目が間違って赤色で表示されます。

  1. この問題を解決するには、else ステートメントを追加して、品質が 1 以上以下であれば色を黒に設定します。

    両方の条件が明示的に指定されている場合、ビューホルダーは各項目の正しいテキスト色を使用します。
if (item.sleepQuality <= 1) {
   holder.textView.setTextColor(Color.RED) // red
} else {
   // reset
   holder.textView.setTextColor(Color.BLACK) // black
}
  1. アプリを実行すると、数値が常に正しい色になります。

これで、基本的な RecyclerView が機能しました。

このタスクでは、シンプルなビューホルダーを、睡眠データの表示数の多いビューホルダーに置き換えます。

Util.kt に追加したシンプルな ViewHolder は、TextItemViewHolderTextView をラップするだけです。

class TextItemViewHolder(val textView: TextView): RecyclerView.ViewHolder(textView)

RecyclerViewTextView を直接使用するだけでなく、この 1 行のコードは多くの機能を備えています。ViewHolder は、RecyclerView 内のアイテムビューとその場所に関するメタデータを記述します。RecyclerView はこの機能を使用して、リストをスクロールしたときにビューを正しく配置し、Adapter でアイテムが追加または削除されたときに、ビューをアニメーション化します。

RecyclerViewViewHolder に保存されているビューにアクセスする必要がある場合は、ビューホルダーの itemView プロパティを使用してアクセスします。RecyclerView は、画面上に表示するアイテムをバインドするとき、枠線のようなビューにデコレーションを描画するとき、ユーザー補助を実装する場合に itemView を使用します。

ステップ 1: アイテム レイアウトを作成する

このステップでは、1 つのアイテムのレイアウト ファイルを作成します。レイアウトは、睡眠の質を表す ImageView、睡眠時間の長さを表す TextView、テキストとしての品質を表す TextView を含む ConstraintLayout で構成されます。以前にレイアウトを実行したので、提供された XML コードをコピーして貼り付けます。

  1. 新しいレイアウト リソース ファイルを作成して、list_item_sleep_night という名前を付けます。
  2. ファイル内のすべてのコードを以下のコードに置き換えます。作成したレイアウトを確認します。
<?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"
   xmlns:tools="http://schemas.android.com/tools"
   android:layout_width="match_parent"
   android:layout_height="wrap_content">

   <ImageView
       android:id="@+id/quality_image"
       android:layout_width="@dimen/icon_size"
       android:layout_height="60dp"
       android:layout_marginStart="16dp"
       android:layout_marginTop="8dp"
       android:layout_marginBottom="8dp"
       app:layout_constraintBottom_toBottomOf="parent"
       app:layout_constraintStart_toStartOf="parent"
       app:layout_constraintTop_toTopOf="parent"
       tools:srcCompat="@drawable/ic_sleep_5" />

   <TextView
       android:id="@+id/sleep_length"
       android:layout_width="0dp"
       android:layout_height="20dp"
       android:layout_marginStart="8dp"
       android:layout_marginTop="8dp"
       android:layout_marginEnd="16dp"
       app:layout_constraintEnd_toEndOf="parent"
       app:layout_constraintStart_toEndOf="@+id/quality_image"
       app:layout_constraintTop_toTopOf="@+id/quality_image"
       tools:text="Wednesday" />

   <TextView
       android:id="@+id/quality_string"
       android:layout_width="0dp"
       android:layout_height="20dp"
       android:layout_marginTop="8dp"
       app:layout_constraintEnd_toEndOf="@+id/sleep_length"
       app:layout_constraintHorizontal_bias="0.0"
       app:layout_constraintStart_toStartOf="@+id/sleep_length"
       app:layout_constraintTop_toBottomOf="@+id/sleep_length"
       tools:text="Excellent!!!" />
</androidx.constraintlayout.widget.ConstraintLayout>
  1. Android Studio の [Design] タブに切り替えます。デザインビューでは、レイアウトは左下にあるスクリーンショットのようになります。ブループリント ビューでは、右のスクリーンショットのようになっています。

ステップ 2: ViewHolder を作成する

  1. SleepNightAdapter.kt を開きます。
  2. SleepNightAdapter 内に ViewHolder というクラスを作成し、RecyclerView.ViewHolder を拡張します。
class ViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView){}
  1. ViewHolder 内で、ビューへの参照を取得します。この ViewHolder が更新するビューへの参照が必要です。この ViewHolder をバインドするたびに、画像と両方のテキストビューにアクセスする必要があります。(このコードは、後でデータ バインディングを使用するように変換します)。
val sleepLength: TextView = itemView.findViewById(R.id.sleep_length)
val quality: TextView = itemView.findViewById(R.id.quality_string)
val qualityImage: ImageView = itemView.findViewById(R.id.quality_image)

ステップ 3: SleepNightAdapter で ViewHolder を使用する

  1. SleepNightAdapter 定義で、TextItemViewHolder ではなく、作成した SleepNightAdapter.ViewHolder を使用します。
class SleepNightAdapter: RecyclerView.Adapter<SleepNightAdapter.ViewHolder>() {

onCreateViewHolder() を更新します。

  1. ViewHolder を返すように onCreateViewHolder() の署名を変更します。
  2. 正しいレイアウト リソース list_item_sleep_night を使用するようにレイアウト インフレータを変更します。
  3. TextView へのキャストを削除します。
  4. TextItemViewHolder を返すのではなく、ViewHolder を返します。

    完成した onCreateViewHolder() 関数は次のようになります。
    override fun onCreateViewHolder(
            parent: ViewGroup, viewType: Int): ViewHolder {
        val layoutInflater = 
            LayoutInflater.from(parent.context)
        val view = layoutInflater
                .inflate(R.layout.list_item_sleep_night, 
                         parent, false)
        return ViewHolder(view)
    }

onBindViewHolder() を更新します。

  1. holder パラメータが TextItemViewHolder ではなく ViewHolder になるように、onBindViewHolder() のシグネチャを変更します。
  2. onBindViewHolder() 内で、item の定義以外のすべてのコードを削除します。
  3. このビューの resources への参照を保持する val res を定義します。
val res = holder.itemView.context.resources
  1. sleepLength テキストビューのテキストを時間に設定します。以下のコードをコピーします。この関数は、スターター コードとともに提供されるフォーマット関数を呼び出します。
holder.sleepLength.text = convertDurationToFormatted(item.startTimeMilli, item.endTimeMilli, res)
  1. convertDurationToFormatted() を定義する必要があるため、エラーが発生します。Util.kt を開き、コードとそれに関連するインポートのコメント化を解除します。([コード &行コメントでコメント] を選択します)。
  2. onBindViewHolder() に戻り、convertNumericQualityToString() を使用して画質を設定します。
holder.quality.text= convertNumericQualityToString(item.sleepQuality, res)
  1. 場合によっては、これらの関数を手動でインポートする必要があります。
import com.example.android.trackmysleepquality.convertDurationToFormatted
import com.example.android.trackmysleepquality.convertNumericQualityToString
  1. 画質に適したアイコンを設定します。新しい ic_sleep_active アイコンはスターター コード内に用意されています。
holder.qualityImage.setImageResource(when (item.sleepQuality) {
   0 -> R.drawable.ic_sleep_0
   1 -> R.drawable.ic_sleep_1
   2 -> R.drawable.ic_sleep_2
   3 -> R.drawable.ic_sleep_3
   4 -> R.drawable.ic_sleep_4
   5 -> R.drawable.ic_sleep_5
   else -> R.drawable.ic_sleep_active
})
  1. 更新された onBindViewHolder() 関数を次に示します。ViewHolder のすべてのデータを設定します。
   override fun onBindViewHolder(holder: ViewHolder, position: Int) {
        val item = data[position]
        val res = holder.itemView.context.resources
        holder.sleepLength.text = convertDurationToFormatted(item.startTimeMilli, item.endTimeMilli, res)
        holder.quality.text= convertNumericQualityToString(item.sleepQuality, res)
        holder.qualityImage.setImageResource(when (item.sleepQuality) {
            0 -> R.drawable.ic_sleep_0
            1 -> R.drawable.ic_sleep_1
            2 -> R.drawable.ic_sleep_2
            3 -> R.drawable.ic_sleep_3
            4 -> R.drawable.ic_sleep_4
            5 -> R.drawable.ic_sleep_5
            else -> R.drawable.ic_sleep_active
        })
    }
  1. アプリを実行します。下のスクリーンショットのように、睡眠のクオリティ アイコンとともに、睡眠時間と睡眠の質を示すテキストが表示されます。

RecyclerView が完成しました!AdapterViewHolder の実装方法を学び、それぞれを組み合わせて RecyclerView Adapter を含むリストを表示しました。

これまでのコードでは、アダプターとビューホルダーを作成するプロセスを示しています。ただし、このコードは改善できます。表示するコードとビューホルダーを管理するコードが混在しており、onBindViewHolder()ViewHolder の更新方法を詳細に認識しています。

本番環境のアプリでは、複数のビューホルダー、より複雑なアダプター、複数のデベロッパーが変更を加えることがあります。ビューホルダーに関連するものがすべてビューホルダー内にのみ含まれるようにコードを構成する必要があります。

ステップ 1: onBindViewHolder() をリファクタリングする

このステップでは、コードをリファクタリングし、すべてのビューホルダー機能を ViewHolder に移行します。このリファクタリングの目的は、ユーザーに対するアプリの見た目を変えることではなく、デベロッパーがコードを簡単に操作できるようにすることです。Android Studio には便利なツールがあります。

  1. SleepNightAdapteronBindViewHolder() で、ステートメントを除くすべての要素を選択して変数 item を宣言します。
  2. 右クリックして、[リファクタリング > 抽出 > 関数] を選択します。
  3. 関数に bind という名前を付け、推奨されるパラメータを受け入れます。[OK] をクリックします。

    bind() 関数は onBindViewHolder() の下に配置されます。
    private fun bind(holder: ViewHolder, item: SleepNight) {
        val res = holder.itemView.context.resources
        holder.sleepLength.text = convertDurationToFormatted(item.startTimeMilli, item.endTimeMilli, res)
        holder.quality.text = convertNumericQualityToString(item.sleepQuality, res)
        holder.qualityImage.setImageResource(when (item.sleepQuality) {
            0 -> R.drawable.ic_sleep_0
            1 -> R.drawable.ic_sleep_1
            2 -> R.drawable.ic_sleep_2
            3 -> R.drawable.ic_sleep_3
            4 -> R.drawable.ic_sleep_4
            5 -> R.drawable.ic_sleep_5
            else -> R.drawable.ic_sleep_active
        })
    }
  1. bind()holder パラメータの単語 holder にカーソルを置きます。Alt+Enter(Mac では Option+Enter)を押して、インテント メニューを開きます。[パラメータをパラメータに変換] を選択して、次のシグネチャを持つ拡張関数に変換します。
private fun ViewHolder.bind(item: SleepNight) {...}
  1. bind() 関数を切り取り、ViewHolder に貼り付けます。
  2. bind() を公開します。
  3. 必要に応じて、bind() をアダプタにインポートします。
  4. 現在は ViewHolder 内にあるため、署名の ViewHolder の部分は削除できます。ViewHolder クラスの bind() 関数の最終コードを次に示します。
fun bind(item: SleepNight) {
   val res = itemView.context.resources
   sleepLength.text = convertDurationToFormatted(
           item.startTimeMilli, item.endTimeMilli, res)
   quality.text = convertNumericQualityToString(
           item.sleepQuality, res)
   qualityImage.setImageResource(when (item.sleepQuality) {
       0 -> R.drawable.ic_sleep_0
       1 -> R.drawable.ic_sleep_1
       2 -> R.drawable.ic_sleep_2
       3 -> R.drawable.ic_sleep_3
       4 -> R.drawable.ic_sleep_4
       5 -> R.drawable.ic_sleep_5
       else -> R.drawable.ic_sleep_active
   })
}

ステップ 2: onCreateViewHolder をリファクタリングする

現在、アダプタの onCreateViewHolder() メソッドは ViewHolder のレイアウト リソースからビューをインフレートします。ただし、インフレーションはアダプタとは関係がなく、ViewHolder と関係があります。インフレーションは ViewHolder で行う必要があります。

  1. onCreateViewHolder() で、関数の本文にあるすべてのコードを選択します。
  2. 右クリックして、[リファクタリング > 抽出 > 関数] を選択します。
  3. 関数に from という名前を付け、推奨されるパラメータを受け入れます。[OK] をクリックします。
  4. 関数名 from の上にカーソルを置きます。Alt+Enter(Mac では Option+Enter)を押して、インテント メニューを開きます。
  5. [コンパニオン オブジェクトに移動] を選択します。from() 関数は、ViewHolder インスタンスではなく ViewHolder クラスで呼び出すことができるように、コンパニオン オブジェクトに含める必要があります。
  6. companion オブジェクトを ViewHolder クラスに移動します。
  7. from() を公開します。
  8. onCreateViewHolder() で、return ステートメントを変更して、ViewHolder クラスの from() を呼び出した結果を返します。

    完成した onCreateViewHolder() メソッドと from() メソッドは次のようなコードになります。コードはエラーなくビルドおよび実行される必要があります。
    override fun onCreateViewHolder(parent: ViewGroup, viewType: 
Int): ViewHolder {
        return ViewHolder.from(parent)
    }
companion object {
   fun from(parent: ViewGroup): ViewHolder {
       val layoutInflater = LayoutInflater.from(parent.context)
       val view = layoutInflater
               .inflate(R.layout.list_item_sleep_night, parent, false)
       return ViewHolder(view)
   }
}
  1. コンストラクタが非公開になるように ViewHolder クラスのシグネチャを変更します。from() は新しい ViewHolder インスタンスを返すメソッドになったため、ViewHolder のコンストラクタをこれ以上呼び出す必要はありません。
class ViewHolder private constructor(itemView: View) : RecyclerView.ViewHolder(itemView){
  1. アプリを実行します。アプリは以前と同じようにビルドして実行します。これは、リファクタリング後に望ましい結果になります。

Android Studio プロジェクト: RecyclerViewFundamentals

  • データのリストやグリッドを表示することは、Android の UI タスクとしては非常に一般的なものです。RecyclerView は、非常に大きなリストを表示する場合でも効率的に使用できるように設計されています。
  • RecyclerView は、現在画面に表示されているアイテムを処理または描画するために必要な作業のみを行います。
  • アイテムが画面外にスクロールされると、そのビューはリサイクルされます。具体的には、画面内にスクロールしてくる新たなコンテンツがその項目に設定されます。
  • ソフトウェア エンジニアリングにおけるアダプター パターンは、オブジェクトが別の API と連携する際に役立ちます。RecyclerView は、アプリによるデータの保存や処理方法を変更せずに、アプリデータを表示できるデータに変換するためにアダプターを使用します。

RecyclerView にデータを表示するには、次のパーツが必要です。

  • RecyclerView
    RecyclerView のインスタンスを作成するには、レイアウト ファイルで <RecyclerView> 要素を定義します。
  • LayoutManager
    RecyclerViewLayoutManager を使用して、RecyclerView 内のアイテムのレイアウト(グリッドや線形リストへのレイアウトなど)を整理します。

    レイアウト ファイルの <RecyclerView> で、app:layoutManager 属性をレイアウト マネージャー(LinearLayoutManagerGridLayoutManager など)に設定します。

    RecyclerViewLayoutManager はプログラムで設定することもできます。(この手法については、後の Codelab で説明します)。
  • 各項目のレイアウト
    XML レイアウト ファイルで、データ項目のレイアウトを作成します。
  • アダプター
    データとその ViewHolder での表示方法を準備するアダプターを作成します。アダプターを RecyclerView に関連付けます。

    RecyclerView の実行時には、アダプターを使用して画面上にデータをどのように表示するかを判断します。

    アダプターでは、次のメソッドを実装する必要があります。
    getItemCount(): アイテム数を返します。
    onCreateViewHolder(): リスト内のアイテムの ViewHolder を返します。
    onBindViewHolder(): リスト内のアイテムのビューにデータを適応させます。

  • ViewHolder
    ViewHolder には、アイテムのレイアウトから 1 つのアイテムを表示するためのビュー情報が含まれます。
  • アダプターの onBindViewHolder() メソッドは、データをビューに適応させます。常にこのメソッドをオーバーライドします。通常、onBindViewHolder() はアイテムのレイアウトをインフレートし、データをレイアウト内のビューに配置します。
  • RecyclerView はデータを認識しないため、データが変更されたときに AdapterRecyclerView に通知する必要があります。notifyDataSetChanged() を使用して、データが変更されていることを Adapter に通知します。

Udacity コース:

Android デベロッパー ドキュメント:

このセクションでは、インストラクターが主導するコースの一環として、この Codelab に取り組む生徒の課題について説明します。教師は以下のことを行えます。

  • 必要に応じて課題を割り当てます。
  • 宿題の提出方法を生徒に伝える。
  • 宿題を採点します。

教師はこれらの提案を少しだけ使うことができます。また、他の課題は自由に割り当ててください。

この Codelab にご自分で取り組む場合は、これらの課題を使用して知識をテストしてください。

次の質問に答えてください。

問題 1

RecyclerView ではアイテムをどのように表示しますか?該当するものをすべて選択してください。

▢ リストやグリッドにアイテムを表示する

▢ 垂直方向または水平方向にスクロールする。

▢ タブレットなどの大型デバイスでは斜めにスクロールする。

▢ リストやグリッドで不十分な場合はカスタム レイアウトを使用できるようにする。

質問 2

RecyclerView を使用すると、どのようなメリットがありますか。該当するものをすべて選択してください。

▢ 大規模なリストを効率的に表示します。

▢ データを自動的に更新します。

▢ アイテムの更新や削除、リストに追加への更新の手間を最小限に抑えます

▢ 画面外にスクロールするビューを再利用して、画面上でスクロールする次のアイテムを表示します。

問題 3

アダプターを使用する理由をいくつか挙げてください。該当するものをすべて選択してください。

▢ 関心を分離することで、コードの変更やテストが簡単になります。

RecyclerView は、表示対象データに依存しないものです。

▢ データ処理レイヤは、表示方法を考慮する必要はありません。

▢ アプリの実行速度が上がります。

問題 4

ViewHolder の説明として正しいものは次のうちどれですか。該当するものをすべて選択してください。

ViewHolder レイアウトは XML レイアウト ファイルで定義します。

▢ データセットのデータ単位ごとに 1 つの ViewHolder があります。

RecyclerView に複数の ViewHolder を含めることができます

Adapter がデータを ViewHolder にバインドします。

次のレッスンを開始する: 7.2: DiffUtil および RecyclerView とのデータ バインディング