この Codelab は、Android Kotlin の基礎コースの一部です。Codelab を順番に進めていくと、このコースを最大限に活用できます。すべてのコース Codelab は Android Kotlin の基礎 Codelab ランディング ページに掲載されています。
はじめに
この Codelab では、RecyclerView
を使用してアイテムのリストを表示する方法について説明します。これまでの Codelab シリーズの睡眠管理アプリをベースに、RecyclerView
と推奨アーキテクチャを使用して、的確で多様なデータ表示方法について学習します。
前提となる知識
以下について把握しておく必要があります。
- アクティビティ、フラグメント、ビューを使用した基本的なユーザー インターフェース(UI)の作成。
- フラグメント間を移動し、
safeArgs
を使用してフラグメント間でデータを渡す。 - ビューモデルを使用して、モデル ファクトリ、変換、
LiveData
とそのオブザーバーを表示します。 Room
データベースの作成、DAO の作成、エンティティの定義。- データベース タスクやその他の長時間実行タスクにコルーチンを使用する。
学習内容
Adapter
とViewHolder
でRecyclerView
を使用してアイテムのリストを表示する方法。
演習内容
- 前のレッスンの TrackMySleepQuality アプリを、
RecyclerView
を使って睡眠の質のデータを表示するように変更しました。
この Codelab では、睡眠の質を追跡するアプリの RecyclerView
部分を作成します。アプリは Room
データベースを使用して、睡眠データを時系列で保存します。
スターター スリープ トラッカー アプリには、次の図に示すように、フラグメントで表される画面が 2 つあります。
左側の最初の画面には、トラッキングを開始および停止するボタンがあります。この画面には、ユーザーのすべての睡眠データも表示されます。[消去] ボタンをクリックすると、そのアプリがユーザーのために収集したすべてのデータが完全に削除されます。右の 2 番目の画面では、睡眠の質の評価を選択しています。
このアプリには、UI コントローラ、ViewModel
、LiveData
を含むシンプルなアーキテクチャが使用されています。また、このアプリは Room
データベースを使用して睡眠データを永続的にします。
最初の画面で表示される睡眠の夜間のリストは機能していますが、正しくありません。アプリは複雑なフォーマッタを使用して、テキストビュー用のテキスト文字列と、品質を示す数値を作成します。また、このデザインは拡張性がありません。この Codelab でこれらの問題をすべて修正すると、最終的なアプリは同じ機能を持ち、メイン画面は次のようになります。
データのリストやグリッドを表示することは、Android の UI タスクとしては非常に一般的なものです。リストは単純なものから非常に複雑なものまで多岐にわたります。テキストビューのリストには、ショッピング リストなどの単純なデータが表示される場合があります。複雑なリスト(旅行先に関するアノテーション付きリストなど)は、見出しを含むスクロール グリッド内に多数の詳細情報を表示します。
このようなユースケースに対応するために、Android には RecyclerView
ウィジェットが用意されています。
RecyclerView
の最大のメリットは、大規模なリストで効率が上がることです。
- デフォルトでは、
RecyclerView
は画面上に現在表示されているアイテムを処理または描画する場合にのみ機能します。たとえば、リストには要素が 1,000 個ありますが、表示される要素が 10 個しかない場合、RecyclerView
は画面に 10 個のアイテムを描画するのに十分な作業を行います。ユーザーがスクロールすると、RecyclerView
は画面に新たに表示されるアイテムを認識して、その描画に十分な作業のみを行います。 - アイテムが画面外にスクロールされると、アイテムのビューはリサイクルされます。具体的には、画面内にスクロールしてくる新たなコンテンツがその項目に設定されます。この
RecyclerView
の動作により、処理時間が大幅に短縮され、リストをスムーズにスクロールできます。 - 項目が変更されると、リスト全体を再描画するのではなく、
RecyclerView
その項目を更新できます。複雑なアイテムのリストを表示すると、効率が大幅に向上します。
次のシーケンスでは、1 つのビューにデータ ABC
が設定されています。そのビューが画面外にスクロールされると、RecyclerView
でそのビューが新しいデータ XYZ
に再利用されます。
アダプタ パターン
異なるコンセントを使う国外に移動する場合は、アダプターを使ってデバイスをコンセントに接続することをおすすめします。アダプターを使用すると、あるタイプのプラグを別の種類のプラグに変換でき、それによってインターフェースが実際に変換されます。
ソフトウェア エンジニアリングにおけるアダプター パターンは、オブジェクトが別の API を操作するために役立ちます。RecyclerView
は、アプリによるデータの保存と処理方法を変更することなく、アダプターを使用してアプリデータを RecyclerView
が表示可能な形式に変換します。睡眠トラッカー アプリの場合は、Room
データベースのデータを、RecyclerView
が ViewModel
を変更せずに表示する方法に適応させるアダプターを作成します。
RecyclerView を実装する
RecyclerView
にデータを表示するには、次のパーツが必要です。
- 表示するデータです。
- レイアウト ファイルで定義された
RecyclerView
インスタンス。ビューのコンテナとして機能します。 - 1 つのデータの項目のレイアウト。
すべてのリスト項目が同じに見える場合、それらすべてに同じレイアウトを使用できますが、これは必須ではありません。アイテム レイアウトは、一度に 1 つのアイテムビューを作成してデータで埋められるように、フラグメントのレイアウトとは別に作成する必要があります。 - レイアウト マネージャー。
レイアウト マネージャーは、ビュー内の UI コンポーネントの構成(レイアウト)を処理します。 - ビューホルダー。
ビューホルダーはViewHolder
クラスを拡張します。これには、アイテムのレイアウトから 1 つのアイテムを表示するためのビュー情報が含まれています。ビューホルダーにより、RecyclerView
で画面での効率的なビューの移動に使用される情報も追加されます。 - アダプター。
アダプターはデータをRecyclerView
に接続します。ViewHolder
に表示できるようにデータを調整します。RecyclerView
は、アダプタを使用して画面上にデータを表示します。
このタスクでは、レイアウト ファイルに RecyclerView
を追加し、睡眠データを RecyclerView
に公開するように Adapter
を設定します。
ステップ 1: LayoutManager を使用して RecyclerView を追加する
このステップでは、fragment_sleep_tracker.xml
ファイル内の ScrollView
を RecyclerView
に置き換えます。
- GitHub から RecyclerViewFundamentals-Starter アプリをダウンロードします。
- アプリをビルドして実行します。データが単純なテキストとして表示されることに注意してください。
- Android Studio の [Design] タブで
fragment_sleep_tracker.xml
レイアウト ファイルを開きます。 - [Component Tree] ペインで、
ScrollView
を削除します。この操作を行うと、ScrollView
内にあるTextView
も削除されます。 - [Palette] パネルで、左側のコンポーネント タイプの一覧をスクロールして [Containers] を選択します。
RecyclerView
を [Palette] ペインから [Component Tree] ペインにドラッグします。RecyclerView
をConstraintLayout
内に配置します。
- 依存関係を追加するかどうかを尋ねるダイアログが表示されたら、[OK] をクリックして Android Studio で Gradle ファイルに
recyclerview
依存関係を追加できるようにします。数秒後にアプリの同期が行われます。
- モジュールの
build.gradle
ファイルを開き、一番下までスクロールして、新しい依存関係に注意してください。次のようなコードです。
implementation 'androidx.recyclerview:recyclerview:1.0.0'
fragment_sleep_tracker.xml
に戻します。- [Text] タブで、次の
RecyclerView
コードを探します。
<androidx.recyclerview.widget.RecyclerView
android:layout_width="match_parent"
android:layout_height="match_parent" />
RecyclerView
にsleep_list
のid
を割り当てます。
android:id="@+id/sleep_list"
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"
RecyclerView
XML にレイアウト マネージャーを追加します。すべてのRecyclerView
には、リスト内のアイテムの配置方法を指定するレイアウト マネージャーが必要です。Android には、デフォルトで全幅行の縦方向リストにアイテムを配置するLinearLayoutManager
が用意されています。
app:layoutManager="androidx.recyclerview.widget.LinearLayoutManager"
- [Design] タブに切り替えます。追加された制約により、
RecyclerView
が使用可能なスペースを埋めるように拡大されています。
ステップ 2: リストアイテムのレイアウトとテキストビューホルダーを作成する
RecyclerView
はコンテナにすぎません。このステップでは、RecyclerView
内に表示するアイテムのレイアウトとインフラストラクチャを作成します。
作業中の RecyclerView
にできるだけ早く移動するために、最初は睡眠の質を数値として表示するシンプルなリスト項目を使用します。そのためには、ビューホルダー TextItemViewHolder
が必要です。データ用のビュー(TextView
)も必要です。(ビューホルダーと、すべての睡眠データのレイアウト方法については、この後のステップで説明します)。
text_item_view.xml
というレイアウト ファイルを作成します。ルート要素として使用するものは関係ありません。テンプレート コードが置き換えられるからです。text_item_view.xml
で、指定されたコードをすべて削除します。- 先頭と末尾に
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" />
Util.kt
を開きます。末尾までスクロールして、下記の定義を追加します。これにより、TextItemViewHolder
クラスが作成されます。コードは、ファイルの末尾の最後の右中かっこの後に配置してください。このビューホルダーは一時的なものであり、後で交換するため、コードはUtil.kt
に入ります。
class TextItemViewHolder(val textView: TextView): RecyclerView.ViewHolder(textView)
- プロンプトが表示されたら、
android.widget.TextView
とandroidx.recyclerview.widget.RecyclerView
をインポートします。
ステップ 3: SleepNightAdapter を作成する
RecyclerView
の実装における主なタスクは、アダプターを作成することです。アイテムビューのシンプルなビューホルダーと、各アイテムのレイアウトがあるとします。これでアダプターを作成できるようになりました。アダプターはビューホルダーを作成し、表示するデータを入力して RecyclerView
を表示します。
sleeptracker
パッケージで、SleepNightAdapter
という新しい Kotlin クラスを作成します。SleepNightAdapter
クラスをRecyclerView.Adapter
に拡張します。このクラスはSleepNightAdapter
と呼ばれ、SleepNight
オブジェクトをRecyclerView
が使用できるものに適応します。アダプターは、使用するビューホルダーを認識する必要があるので、TextItemViewHolder
を渡します。メッセージが表示されたら必要なコンポーネントをインポートすると、実装が必要なメソッドがあるため、エラーが表示されます。
class SleepNightAdapter: RecyclerView.Adapter<TextItemViewHolder>() {}
SleepNightAdapter
の最上位で、データを保持するlistOf
SleepNight
変数を作成します。
var data = listOf<SleepNight>()
SleepNightAdapter
で、getItemCount()
をオーバーライドして、data
の睡眠夜のリストのサイズを返します。RecyclerView
は、アダプタが表示するアイテム数を認識する必要があり、それに応じてgetItemCount()
を呼び出します。
override fun getItemCount() = data.size
- 次のように、
SleepNightAdapter
でonBindViewHolder()
関数をオーバーライドします。onBindViewHolder()
関数はRecyclerView
によって呼び出され、指定した位置にある 1 つのリスト項目のデータを表示します。そのため、onBindViewHolder()
メソッドは、ビューホルダーとバインドするデータの位置という 2 つの引数を取ります。このアプリの場合、ホルダーはTextItemViewHolder
、掲載順位はリスト内の位置です。
override fun onBindViewHolder(holder: TextItemViewHolder, position: Int) {
}
onBindViewHolder()
内で、データ内の特定の位置にある 1 つのアイテムの変数を作成します。
val item = data[position]
- 作成した
ViewHolder
には、textView
というプロパティがあります。onBindViewHolder()
内で、textView
のtext
を睡眠の質の数値に設定します。このコードは数値のリストのみを表示しますが、この簡単な例を見ると、アダプターがデータをビューホルダーに取り込み、画面に組み込む様子がわかります。
holder.textView.text = item.sleepQuality.toString()
SleepNightAdapter
で、onCreateViewHolder()
をオーバーライドして実装します。これは、RecyclerView
がアイテムを表すためにビューホルダーを必要とするときに呼び出されます。
この関数は 2 つのパラメータを受け取り、ViewHolder
を返します。parent
パラメータ(ビューホルダーを保持するビューグループ)は常にRecyclerView
です。viewType
パラメータは、同じRecyclerView
に複数のビューがある場合に使用されます。たとえば、テキストビュー、画像、動画のリストをすべて同じRecyclerView
に置いた場合、onCreateViewHolder()
関数は使用するビューの種類を認識する必要があります。
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): TextItemViewHolder {
}
onCreateViewHolder()
で、LayoutInflater
のインスタンスを作成します。
レイアウト インフレータは、XML レイアウトからビューを作成する方法を認識しています。context
には、ビューを正しくインフレートする方法に関する情報が含まれます。リサイクラー ビュー用のアダプターでは、常にparent
ビューグループ(RecyclerView
)のコンテキストを渡します。
val layoutInflater = LayoutInflater.from(parent.context)
onCreateViewHolder()
で、layoutinflater
にインフレートを依頼してview
を作成します。
ビューの XML レイアウトと、ビューのparent
ビューグループを渡します。3 番目のブール値の引数はattachToRoot
です。このアイテムはRecyclerView
によって自動的にビュー階層に追加されるため、この引数はfalse
にする必要があります。
val view = layoutInflater
.inflate(R.layout.text_item_view, parent, false) as TextView
onCreateViewHolder()
で、view
で作成されたTextItemViewHolder
を返します。
return TextItemViewHolder(view)
data
はデータについて何も認識していないため、アダプターは、data
が変更されたときにRecyclerView
に通知する必要があります。認識されるのは、アダプターが与えるビューホルダーのみです。
表示するデータが変更されたときにRecyclerView
に通知するには、SleepNightAdapter
クラスの一番上にあるdata
変数にカスタム セッターを追加します。セッターで、data
に新しい値を設定し、notifyDataSetChanged()
を呼び出して新しいデータでリストの再描画をトリガーします。
var data = listOf<SleepNight>()
set(value) {
field = value
notifyDataSetChanged()
}
ステップ 4: RecyclerView をアダプタに通知する
RecyclerView
は、ビューホルダーの取得に使用するアダプターについて認識している必要があります。
SleepTrackerFragment.kt
を開きます。onCreateview()
でアダプタを作成します。このコードは、ViewModel
モデルの作成後、return
ステートメントの前に配置します。
val adapter = SleepNightAdapter()
adapter
をRecyclerView
に関連付けます。
binding.sleepList.adapter = adapter
- プロジェクトをクリーンアップおよび再ビルドし、
binding
オブジェクトを更新します。
それでもbinding.sleepList
またはbinding.FragmentSleepTrackerBinding
に関するエラーが表示される場合は、キャッシュを無効にして再起動します。([File] > [Invalidate Cache] > [Restart] の順に選択します)。
ここでアプリを実行するとエラーは発生しませんが、[Start]、[Stop] の順にタップしたときに表示されるデータはありません。
ステップ 5: アダプターにデータを取得する
ここまでの作業で、アダプターと、アダプターから RecyclerView
にデータを取得する方法を確認できました。次に、ViewModel
からアダプターにデータを取得する必要があります。
SleepTrackerViewModel
を開きます。- すべてのスリープナイトを格納する
nights
変数を見つけます。これは表示するデータです。nights
変数は、データベースでgetAllNights()
を呼び出すことによって設定されます。 nights
からprivate
を削除します。この変数にアクセスするオブザーバーが作成されるためです。宣言は次のようになります。
val nights = database.getAllNights()
database
パッケージで、SleepDatabaseDao
を開きます。getAllNights()
関数を見つけます。この関数は、SleepNight
値のリストをLiveData
として返します。つまり、nights
変数には、Room
によって維持されるLiveData
が含まれ、nights
を監視して変更を確認できます。SleepTrackerFragment
を開きます。onCreateView()
で、adapter
の作成の下に、nights
変数にオブザーバーを作成します。
ライフサイクル オーナーとしてフラグメントのviewLifecycleOwner
を指定することにより、RecyclerView
が画面上に表示されている場合にのみ、このオブザーバーがアクティブになるようにできます。
sleepTrackerViewModel.nights.observe(viewLifecycleOwner, Observer {
})
- オブザーバー内で、null 以外の値(
nights
用)を取得するたびに、その値をアダプターのdata
に割り当てます。オブザーバーとデータ設定が完了したコードは次のとおりです。
sleepTrackerViewModel.nights.observe(viewLifecycleOwner, Observer {
it?.let {
adapter.data = it
}
})
- コードをビルドして実行します。
アダプターが動作している場合、睡眠の質の数値がリストとして表示されます。左側のスクリーンショットでは、[開始] をタップした後に -1 が表示されます。右側のスクリーンショットでは、[停止] をタップして品質評価を選択した後の、更新された睡眠の質の数値を示しています。
ステップ 6: ビューホルダーがリサイクルされる仕組みを理解する
RecyclerView
はビューホルダーをリサイクルします。つまり、ビューホルダーが再利用されます。ビューが画面外にスクロールされると、RecyclerView
で画面にスクロールしようとしているビューを再利用します。
これらのビューホルダーはリサイクルされるため、onBindViewHolder()
では、以前の項目がビューホルダーに行ったカスタマイズをすべて設定またはリセットしてください。
たとえば、品質評価が 1 未満で睡眠の質が悪いビューホルダーで、テキストの色を赤に設定できます。
SleepNightAdapter
クラスで、onBindViewHolder()
の最後に次のコードを追加します。
if (item.sleepQuality <= 1) {
holder.textView.setTextColor(Color.RED) // red
}
- アプリを実行します。
- 睡眠の質が低いデータを追加すると、数字が赤色になります。
- 画面上に赤色の数値が表示されるまで、睡眠の質に対する評価を高くします。
RecyclerView
はビューホルダーを再利用するため、最終的に赤いビューホルダーの 1 つを再利用して評価を高めます。評価の高い項目が間違って赤色で表示されます。
- この問題を解決するには、
else
ステートメントを追加して、品質が 1 以上以下であれば色を黒に設定します。
両方の条件が明示的に指定されている場合、ビューホルダーは各項目の正しいテキスト色を使用します。
if (item.sleepQuality <= 1) {
holder.textView.setTextColor(Color.RED) // red
} else {
// reset
holder.textView.setTextColor(Color.BLACK) // black
}
- アプリを実行すると、数値が常に正しい色になります。
これで、基本的な RecyclerView
が機能しました。
このタスクでは、シンプルなビューホルダーを、睡眠データの表示数の多いビューホルダーに置き換えます。
Util.kt
に追加したシンプルな ViewHolder
は、TextItemViewHolder
で TextView
をラップするだけです。
class TextItemViewHolder(val textView: TextView): RecyclerView.ViewHolder(textView)
RecyclerView
が TextView
を直接使用するだけでなく、この 1 行のコードは多くの機能を備えています。ViewHolder
は、RecyclerView
内のアイテムビューとその場所に関するメタデータを記述します。RecyclerView
はこの機能を使用して、リストをスクロールしたときにビューを正しく配置し、Adapter
でアイテムが追加または削除されたときに、ビューをアニメーション化します。
RecyclerView
が ViewHolder
に保存されているビューにアクセスする必要がある場合は、ビューホルダーの itemView
プロパティを使用してアクセスします。RecyclerView
は、画面上に表示するアイテムをバインドするとき、枠線のようなビューにデコレーションを描画するとき、ユーザー補助を実装する場合に itemView
を使用します。
ステップ 1: アイテム レイアウトを作成する
このステップでは、1 つのアイテムのレイアウト ファイルを作成します。レイアウトは、睡眠の質を表す ImageView
、睡眠時間の長さを表す TextView
、テキストとしての品質を表す TextView
を含む ConstraintLayout
で構成されます。以前にレイアウトを実行したので、提供された XML コードをコピーして貼り付けます。
- 新しいレイアウト リソース ファイルを作成して、
list_item_sleep_night
という名前を付けます。 - ファイル内のすべてのコードを以下のコードに置き換えます。作成したレイアウトを確認します。
<?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>
- Android Studio の [Design] タブに切り替えます。デザインビューでは、レイアウトは左下にあるスクリーンショットのようになります。ブループリント ビューでは、右のスクリーンショットのようになっています。
ステップ 2: ViewHolder を作成する
SleepNightAdapter.kt
を開きます。SleepNightAdapter
内にViewHolder
というクラスを作成し、RecyclerView.ViewHolder
を拡張します。
class ViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView){}
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 を使用する
SleepNightAdapter
定義で、TextItemViewHolder
ではなく、作成したSleepNightAdapter.ViewHolder
を使用します。
class SleepNightAdapter: RecyclerView.Adapter<SleepNightAdapter.ViewHolder>() {
onCreateViewHolder()
を更新します。
ViewHolder
を返すようにonCreateViewHolder()
の署名を変更します。- 正しいレイアウト リソース
list_item_sleep_night
を使用するようにレイアウト インフレータを変更します。 TextView
へのキャストを削除します。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()
を更新します。
holder
パラメータがTextItemViewHolder
ではなくViewHolder
になるように、onBindViewHolder()
のシグネチャを変更します。onBindViewHolder()
内で、item
の定義以外のすべてのコードを削除します。- このビューの
resources
への参照を保持するval
res
を定義します。
val res = holder.itemView.context.resources
sleepLength
テキストビューのテキストを時間に設定します。以下のコードをコピーします。この関数は、スターター コードとともに提供されるフォーマット関数を呼び出します。
holder.sleepLength.text = convertDurationToFormatted(item.startTimeMilli, item.endTimeMilli, res)
convertDurationToFormatted()
を定義する必要があるため、エラーが発生します。Util.kt
を開き、コードとそれに関連するインポートのコメント化を解除します。([コード &行コメントでコメント] を選択します)。onBindViewHolder()
に戻り、convertNumericQualityToString()
を使用して画質を設定します。
holder.quality.text= convertNumericQualityToString(item.sleepQuality, res)
- 場合によっては、これらの関数を手動でインポートする必要があります。
import com.example.android.trackmysleepquality.convertDurationToFormatted
import com.example.android.trackmysleepquality.convertNumericQualityToString
- 画質に適したアイコンを設定します。新しい
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
})
- 更新された
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
})
}
- アプリを実行します。下のスクリーンショットのように、睡眠のクオリティ アイコンとともに、睡眠時間と睡眠の質を示すテキストが表示されます。
RecyclerView
が完成しました!Adapter
と ViewHolder
の実装方法を学び、それぞれを組み合わせて RecyclerView
Adapter
を含むリストを表示しました。
これまでのコードでは、アダプターとビューホルダーを作成するプロセスを示しています。ただし、このコードは改善できます。表示するコードとビューホルダーを管理するコードが混在しており、onBindViewHolder()
は ViewHolder
の更新方法を詳細に認識しています。
本番環境のアプリでは、複数のビューホルダー、より複雑なアダプター、複数のデベロッパーが変更を加えることがあります。ビューホルダーに関連するものがすべてビューホルダー内にのみ含まれるようにコードを構成する必要があります。
ステップ 1: onBindViewHolder() をリファクタリングする
このステップでは、コードをリファクタリングし、すべてのビューホルダー機能を ViewHolder
に移行します。このリファクタリングの目的は、ユーザーに対するアプリの見た目を変えることではなく、デベロッパーがコードを簡単に操作できるようにすることです。Android Studio には便利なツールがあります。
SleepNightAdapter
のonBindViewHolder()
で、ステートメントを除くすべての要素を選択して変数item
を宣言します。- 右クリックして、[リファクタリング > 抽出 > 関数] を選択します。
- 関数に
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
})
}
bind()
のholder
パラメータの単語holder
にカーソルを置きます。Alt+Enter
(Mac ではOption+Enter
)を押して、インテント メニューを開きます。[パラメータをパラメータに変換] を選択して、次のシグネチャを持つ拡張関数に変換します。
private fun ViewHolder.bind(item: SleepNight) {...}
bind()
関数を切り取り、ViewHolder
に貼り付けます。bind()
を公開します。- 必要に応じて、
bind()
をアダプタにインポートします。 - 現在は
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
で行う必要があります。
onCreateViewHolder()
で、関数の本文にあるすべてのコードを選択します。- 右クリックして、[リファクタリング > 抽出 > 関数] を選択します。
- 関数に
from
という名前を付け、推奨されるパラメータを受け入れます。[OK] をクリックします。 - 関数名
from
の上にカーソルを置きます。Alt+Enter
(Mac ではOption+Enter
)を押して、インテント メニューを開きます。 - [コンパニオン オブジェクトに移動] を選択します。
from()
関数は、ViewHolder
インスタンスではなくViewHolder
クラスで呼び出すことができるように、コンパニオン オブジェクトに含める必要があります。 companion
オブジェクトをViewHolder
クラスに移動します。from()
を公開します。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)
}
}
- コンストラクタが非公開になるように
ViewHolder
クラスのシグネチャを変更します。from()
は新しいViewHolder
インスタンスを返すメソッドになったため、ViewHolder
のコンストラクタをこれ以上呼び出す必要はありません。
class ViewHolder private constructor(itemView: View) : RecyclerView.ViewHolder(itemView){
- アプリを実行します。アプリは以前と同じようにビルドして実行します。これは、リファクタリング後に望ましい結果になります。
Android Studio プロジェクト: RecyclerViewFundamentals
- データのリストやグリッドを表示することは、Android の UI タスクとしては非常に一般的なものです。
RecyclerView
は、非常に大きなリストを表示する場合でも効率的に使用できるように設計されています。 RecyclerView
は、現在画面に表示されているアイテムを処理または描画するために必要な作業のみを行います。- アイテムが画面外にスクロールされると、そのビューはリサイクルされます。具体的には、画面内にスクロールしてくる新たなコンテンツがその項目に設定されます。
- ソフトウェア エンジニアリングにおけるアダプター パターンは、オブジェクトが別の API と連携する際に役立ちます。
RecyclerView
は、アプリによるデータの保存や処理方法を変更せずに、アプリデータを表示できるデータに変換するためにアダプターを使用します。
RecyclerView
にデータを表示するには、次のパーツが必要です。
- RecyclerView
RecyclerView
のインスタンスを作成するには、レイアウト ファイルで<RecyclerView>
要素を定義します。 - LayoutManager
RecyclerView
はLayoutManager
を使用して、RecyclerView
内のアイテムのレイアウト(グリッドや線形リストへのレイアウトなど)を整理します。
レイアウト ファイルの<RecyclerView>
で、app:layoutManager
属性をレイアウト マネージャー(LinearLayoutManager
やGridLayoutManager
など)に設定します。RecyclerView
のLayoutManager
はプログラムで設定することもできます。(この手法については、後の Codelab で説明します)。 - 各項目のレイアウト
XML レイアウト ファイルで、データ項目のレイアウトを作成します。 - アダプター
データとそのViewHolder
での表示方法を準備するアダプターを作成します。アダプターをRecyclerView
に関連付けます。RecyclerView
の実行時には、アダプターを使用して画面上にデータをどのように表示するかを判断します。
アダプターでは、次のメソッドを実装する必要があります。
–getItemCount()
: アイテム数を返します。
–onCreateViewHolder()
: リスト内のアイテムのViewHolder
を返します。
–onBindViewHolder()
: リスト内のアイテムのビューにデータを適応させます。 - ViewHolder
ViewHolder
には、アイテムのレイアウトから 1 つのアイテムを表示するためのビュー情報が含まれます。 - アダプターの
onBindViewHolder()
メソッドは、データをビューに適応させます。常にこのメソッドをオーバーライドします。通常、onBindViewHolder()
はアイテムのレイアウトをインフレートし、データをレイアウト内のビューに配置します。 RecyclerView
はデータを認識しないため、データが変更されたときにAdapter
はRecyclerView
に通知する必要があります。notifyDataSetChanged()
を使用して、データが変更されていることをAdapter
に通知します。
Udacity コース:
Android デベロッパー ドキュメント:
このセクションでは、インストラクターが主導するコースの一環として、この Codelab に取り組む生徒の課題について説明します。教師は以下のことを行えます。
- 必要に応じて課題を割り当てます。
- 宿題の提出方法を生徒に伝える。
- 宿題を採点します。
教師はこれらの提案を少しだけ使うことができます。また、他の課題は自由に割り当ててください。
この Codelab にご自分で取り組む場合は、これらの課題を使用して知識をテストしてください。
次の質問に答えてください。
問題 1
RecyclerView
ではアイテムをどのように表示しますか?該当するものをすべて選択してください。
▢ リストやグリッドにアイテムを表示する
▢ 垂直方向または水平方向にスクロールする。
▢ タブレットなどの大型デバイスでは斜めにスクロールする。
▢ リストやグリッドで不十分な場合はカスタム レイアウトを使用できるようにする。
質問 2
RecyclerView
を使用すると、どのようなメリットがありますか。該当するものをすべて選択してください。
▢ 大規模なリストを効率的に表示します。
▢ データを自動的に更新します。
▢ アイテムの更新や削除、リストに追加への更新の手間を最小限に抑えます
▢ 画面外にスクロールするビューを再利用して、画面上でスクロールする次のアイテムを表示します。
問題 3
アダプターを使用する理由をいくつか挙げてください。該当するものをすべて選択してください。
▢ 関心を分離することで、コードの変更やテストが簡単になります。
▢ RecyclerView
は、表示対象データに依存しないものです。
▢ データ処理レイヤは、表示方法を考慮する必要はありません。
▢ アプリの実行速度が上がります。
問題 4
ViewHolder
の説明として正しいものは次のうちどれですか。該当するものをすべて選択してください。
▢ ViewHolder
レイアウトは XML レイアウト ファイルで定義します。
▢ データセットのデータ単位ごとに 1 つの ViewHolder
があります。
▢ RecyclerView
に複数の ViewHolder
を含めることができます
▢ Adapter
がデータを ViewHolder
にバインドします。
次のレッスンを開始する: