スマートフォンでよく使用しているアプリについて考えてみましょう。ほとんどのアプリに少なくともリストが 1 つあります。通話履歴画面、連絡帳アプリ、ソーシャル メディア アプリには、どれにもデータのリストが表示されます。次のスクリーンショットのように、簡単な単語やフレーズのリストを表示するアプリもあれば、テキストや画像が入ったカードなどの複雑な項目を表示するアプリもあります。内容にかかわらず、データのリストを表示することは、Android の UI タスクとしては非常に一般的なものです。
Android には、リストのあるアプリを作成するために RecyclerView
が用意されています。RecyclerView
は、非常に効率的に設計されていて、大きなリストを使用する場合でも、画面外にスクロールしたビューを再利用(リサイクル)します。画面外にスクロールされるリスト項目があると、RecyclerView
でそのビューが次に表示されるリスト項目に再利用されます。具体的には、画面内にスクロールしてくる新たなコンテンツがその項目に設定されます。このような RecyclerView
の動作により、処理時間が大幅に短縮され、リストをスムーズにスクロールできます。
次のシーケンスでは、1 つのビューにデータ ABC
が設定されています。そのビューが画面外にスクロールされると、RecyclerView
でそのビューが新しいデータ XYZ
に再利用されます。
この Codelab では、Affirmations アプリを作成します。Affirmations は、スクロール リストに 10 個のアファメーション(肯定的な宣言)をテキストで表示するシンプルなアプリです。これに続く Codelab では、さらに一歩進んで、アファメーションのそれぞれに想像をかき立てる画像を追加するとともに、アプリの UI を改良します。
前提条件
- Android Studio でテンプレートからプロジェクトを作成できる。
- アプリに文字列リソースを追加できる。
- XML でレイアウトを定義できる。
- Kotlin のクラスと継承(抽象クラスを含む)を理解している。
- 既存のクラスを継承して、そのメソッドをオーバーライドできる。
- developer.android.com にある、Android フレームワークで用意されているクラスについてのドキュメントを利用できる。
学習内容
RecyclerView
を使用してデータのリストを表示する方法- コードをパッケージにまとめる方法
RecyclerView
でアダプタを使用して、個々のリスト項目の外観をカスタマイズする方法
作成するアプリの概要
RecyclerView
を使用してアファメーション文字列のリストを表示するアプリ
必要なもの
- Android Studio バージョン 4.1 以上がインストールされているパソコン。
Empty Activity プロジェクトを作成する
新しいプロジェクトを作成する前に、Android Studio 4.1 以降を使用していることを確認してください。
- Android Studio で、[Empty Activity] テンプレートを使用して、新しい Kotlin プロジェクトを開始します。
- アプリの [Name] に「Affirmations」を、[Package name] に「com.example.affirmations」を入力し、[Minimum SDK] に [API Level 19] を選びます。
- [Finish] をクリックして、プロジェクトを作成します。
Affirmations アプリを作成するための次のステップは、リソースの追加です。以下をプロジェクトに追加します。
- アプリにアファメーションとして表示する文字列リソース
- アプリにアファメーションのリストを供給するデータのソース
アファメーション文字列を追加する
- [Project] ウィンドウで、[app] > [res] > [values] > [strings.xml] を開きます。このファイルには現在、アプリ名の文字列リソースが 1 つあるだけです。
strings.xml
に、以下のアファメーションを別々の文字列リソースとして追加します。それらに、affirmation1
、affirmation2
というように名前を付けます。
アファメーションのテキスト
I am strong. I believe in myself. Each day is a new opportunity to grow and be a better version of myself. Every challenge in my life is an opportunity to learn from. I have so much to be grateful for. Good things are always coming into my life. New opportunities await me at every turn. I have the courage to follow my heart. Things will unfold at precisely the right time. I will be present in all the moments that this day brings.
完成した strings.xml
ファイルは次のようになります。
<resources>
<string name="app_name">Affirmations</string>
<string name="affirmation1">I am strong.</string>
<string name="affirmation2">I believe in myself.</string>
<string name="affirmation3">Each day is a new opportunity to grow and be a better version of myself.</string>
<string name="affirmation4">Every challenge in my life is an opportunity to learn from.</string>
<string name="affirmation5">I have so much to be grateful for.</string>
<string name="affirmation6">Good things are always coming into my life.</string>
<string name="affirmation7">New opportunities await me at every turn.</string>
<string name="affirmation8">I have the courage to follow my heart.</string>
<string name="affirmation9">Things will unfold at precisely the right time.</string>
<string name="affirmation10">I will be present in all the moments that this day brings.</string>
</resources>
文字列リソースを追加したので、コード内で R.string.affirmation1
や R.string.affirmation2
として参照できます。
新規パッケージを作成する
コードを論理的に整理すると、自分や他のデベロッパーにとってコードの理解、メンテナンス、拡張が容易になります。書類をフォルダに整理するのと同じように、コードをファイルやパッケージに整理できます。
パッケージとは
- Android Studio の [Project] ウィンドウ(Android)で、Affirmations アプリの [app] > [java] にある新しいプロジェクト ファイルを確認します。次のスクリーンショットのように、コード用のパッケージが 1 つ(com.example.affirmations)と、テストファイル用のパッケージが 2 つ(com.example.affirmations (androidTest) と com.example.affirmations (test))の合計 3 つのパッケージが表示されているはずです。
- このように、パッケージ名は、いくつかの単語をピリオドで区切ったものです。
パッケージには 2 つの使い方があります。
- コードの各部分に対して個別にパッケージを作成する。たとえば、データを扱うクラスと UI を構築するクラスを別々のパッケージに分割することがよくあります。
- コード内で他のパッケージのコードを使用する。他のパッケージのクラスを使用するには、それをビルドシステムの依存関係で定義する必要があります。また、コード内で
import
するのも標準的な方法です。こうすると、完全修飾名(android.widget.TextView
など)の代わりに短縮名(TextView
など)を使用できます。たとえば、すでにsqrt
(import kotlin.math.sqrt
)やView
(import android.view.View
)などのクラスのためにimport
ステートメントを使用しています。
Affirmations アプリでは、Android クラスと Kotlin クラスをインポートするだけでなく、アプリを整理して複数のパッケージに分割します。アプリにあるクラスが少ない場合でも、パッケージを使用して機能ごとにクラスをグループ化することをおすすめします。
パッケージの命名
パッケージ名は、グローバルに一意である限り、どのようなものでも構いません。他に同じ名前の公開パッケージがあってはなりません。膨大な数のパッケージがあり、ランダムな一意の名前を思い付くのは難しいので、プログラマーはパッケージ名の作成と理解が簡単になるように規則を使用しています。
- 通常、パッケージ名は、小文字で表された名前の各部分を、一般的なものから限定的なものという順にして、ピリオドで区切って構成したものです。重要: ピリオドは名前の一部にすぎません。コード内の階層を示すものではなく、フォルダ構造を規定するものでもありません。
- インターネット ドメインはグローバルに一意であるため、名前の最初の部分にはドメイン(通常はデベロッパーまたは組織のドメイン)を使用するのが一般的です。
- パッケージ名を適切に選ぶことで、パッケージの内容とパッケージ同士の関係を示すことができます。
- このコードのように、コード例では
com.example
の後にアプリ名が続くものがよく使用されます。
既定のパッケージ名とその内容の例を以下に示します。
kotlin.math
- 数学関係の関数と定数android.widget
- ビュー(TextView
など)
パッケージを作成する
- Android Studio の [Project] ペインで [app] > [java] > [com.example.affirmations] を右クリックし、[New] > [Package] を選択します。
- [New Package] ポップアップで、パッケージ名の接頭辞が提案されます。提案されるパッケージ名の最初の部分は、右クリックしたパッケージの名前です。パッケージ名によってパッケージの階層が作られることはありませんが、内容の関連性と編成を反映させるために名前の一部が再利用されます。
- ポップアップで、提案されたパッケージ名の末尾に「model」を追加します。デベロッパーは通常、データをモデル化(または表現)するクラスのパッケージ名として「model」を使用します。
- Enter キーを押します。これにより、com.example.affirmations(ルート)パッケージの下に新しいパッケージが作成されます。この新しいパッケージには、アプリで定義されたデータ関連のクラスがすべて含まれます。
Affirmation データクラスを作成する
このタスクでは、Affirmation.
という名前のクラスを作成します。Affirmation
のオブジェクト インスタンスは 1 つのアファメーションを表し、アファメーション文字列のリソース ID を含みます。
- com.example.affirmations.model パッケージを右クリックし、[New] > [Kotlin File/Class] を選択します。
- ポップアップで [Class] を選択し、クラスの名前として「
Affirmation
」と入力します。これにより、Affirmation.kt
という名前の新しいファイルがmodel
パッケージに作成されます。 - クラス定義の前に
data
キーワードを追加して、Affirmation
をデータクラスにします。データクラスには少なくとも 1 つのプロパティを定義する必要があるため、このままではエラーが残ります。
Affirmation.kt
package com.example.affirmations.model
data class Affirmation {
}
Affirmation
のインスタンスを作成するときは、アファメーション文字列のリソース ID を渡す必要があります。リソース ID は整数です。
val
整数パラメータstringResourceId
をAffirmation
クラスのコンストラクタに追加します。これによりエラーを回避できます。
package com.example.affirmations.model
data class Affirmation(val stringResourceId: Int)
データソースとなるクラスを作成する
アプリに表示されるデータは、さまざまなソースから取得されます(アプリ プロジェクト内、データのダウンロードにインターネットへの接続が必要な外部ソースなど)。そのため、データの形式が必要な形式と完全には一致していない場合があります。アプリの他の部分で、データの取得元やデータの元々の形式を気にするべきではありません。このようなデータの準備は、アプリのためにデータを準備する別の Datasource
クラスで行うことが可能であり、そうすべきです。
データの準備は別の関心事であるため、Datasource
クラスを別の data パッケージに入れます。
- Android Studio の [Project] ウィンドウで [app] > [java] > [com.example.affirmations] を右クリックし、[New] > [Package] を選択します。
- パッケージ名の最後の部分として「
data
」と入力します。 data
パッケージを右クリックして、[new Kotlin File/Class] を選択します。- クラス名として「
Datasource
」と入力します。 Datasource
クラス内に、loadAffirmations()
という名前の関数を作成します。
loadAffirmations()
関数は Affirmations
のリストを返す必要があります。そのために、リストを作成し、それに各リソース文字列の Affirmation
インスタンスを入れます。
- メソッド
loadAffirmations()
の戻り値の型としてList<Affirmation>
を宣言します。 loadAffirmations()
の本体に、return
ステートメントを追加します。return
キーワードの後で、listOf<>()
を呼び出してList
を作成します。- 山かっこ「
<>
」内で、リスト項目の型をAffirmation
に指定します。必要に応じて、com.example.affirmations.model.Affirmation
をインポートします。 - かっこ内で、以下に示すように、
Affirmation
を作成し、リソース ID としてR.string.affirmation1
を渡します。
Affirmation(R.string.affirmation1)
- 残りの
Affirmation
オブジェクトをカンマで区切って全アファメーションのリストに追加します。完成したコードは次のようになります。
Datasource.kt
package com.example.affirmations.data
import com.example.affirmations.R
import com.example.affirmations.model.Affirmation
class Datasource {
fun loadAffirmations(): List<Affirmation> {
return listOf<Affirmation>(
Affirmation(R.string.affirmation1),
Affirmation(R.string.affirmation2),
Affirmation(R.string.affirmation3),
Affirmation(R.string.affirmation4),
Affirmation(R.string.affirmation5),
Affirmation(R.string.affirmation6),
Affirmation(R.string.affirmation7),
Affirmation(R.string.affirmation8),
Affirmation(R.string.affirmation9),
Affirmation(R.string.affirmation10)
)
}
}
(任意)TextView 内に Affirmations リストのサイズを表示する
アファメーションのリストを作成できることを確認するために、loadAffirmations()
を呼び出して、Empty Activity アプリ テンプレートの TextView
に、返されたアファメーションのリストのサイズを表示します。
layouts/activity_main.xml
で、テンプレートのTextView
に、textview
のid
を付与します。onCreate()
メソッドのMainActivity
の今あるコードの後で、textview
への参照を取得します。
val textView: TextView = findViewById(R.id.textview)
- 次に、アファメーション リストを作成して、そのサイズを表示するコードを追加します。
Datasource
を作成してloadAffirmations()
を呼び出し、返されたリストのサイズを取得して文字列に変換し、textView
のtext
に代入します。
textView.text = Datasource().loadAffirmations().size.toString()
- アプリを実行します。次のような画面が表示されるはずです。
MainActivity
に追加したコードを削除します。
このタスクでは、Affirmations
のリストを表示するように RecyclerView
をセットアップします。
RecyclerView
の作成と使用は、多くの部分に分割されます。これは役割分担と考えられます。下の図にその概要を示します。各部分を実装しながら学習してゆきます。
- item - 表示するリストの 1 つのデータ項目です。アプリ内の 1 つの
Affirmation
オブジェクトを表します。 - Adapter - データを受け取って
RecyclerView
で表示できるようにします。 - ViewHolders - アファメーションの表示に使用および再利用する
RecyclerView
のビューのプールです。 - RecyclerView - 画面上のビューです。
RecyclerView をレイアウトに追加する
Affirmations アプリは、MainActivity
という名前の単一のアクティビティで構成され、そのレイアウト ファイルは activity_main
.xml
という名前です。まず、MainActivity
のレイアウトに RecyclerView
を追加する必要があります。
activity_main.xml
を開きます([app] > [res] > [layout] > [activity_main.xml])。- まだ使用していない場合は、[Split view] に切り替えます。
TextView
を削除します。
現在のレイアウトは ConstraintLayout
を使用しています。ConstraintLayout
は、レイアウト内で複数の子ビューを配置する場合に最適で柔軟性があります。レイアウトには RecyclerView
という子ビュー 1 つしかないため、単一の子ビューを保持するために使用する FrameLayout
というシンプルな ViewGroup
に変更できます。
- XML で、
ConstraintLayout
をFrameLayout
に置き換えます。完成したレイアウトは次のようになります。
activity_main.xml
<?xml version="1.0" encoding="utf-8"?>
<FrameLayout 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="match_parent"
tools:context=".MainActivity">
</FrameLayout>
- [Design] ビューに切り替えます。
- [Palette] で [Containers] を選択し、[RecyclerView] を見つけます。
- [RecyclerView] をレイアウトにドラッグします。
- [Add Project Dependency] ポップアップが表示された場合は、それを読んで [OK] をクリックします(表示されない場合は、何もする必要はありません)。
- Android Studio の処理が終わり、レイアウトに
RecyclerView
が表示されるまで待ちます。 - 必要に応じて、
RecyclerView
のlayout_width
属性とlayout_height
属性をmatch_parent
に変更して、RecyclerView
が画面全体に表示されるようにします。 RecyclerView
のリソース ID をrecycler_view
に設定します。
RecyclerView
では、線形リストやグリッドなど、さまざまな方法で項目を表示できます。項目の並べ替えには、LayoutManager
を使用します。Android フレームワークでは、基本的な項目レイアウト用のレイアウト マネージャーが用意されています。Affirmations アプリでは項目を縦方向のリストとして表示するので、LinearLayoutManager
を使用できます。
- [Code] ビューに戻ります。XML コード内の
RecyclerView
要素に、次に示すようにLinearLayoutManager
をRecyclerView
のレイアウト マネージャー属性として追加します。
app:layoutManager="LinearLayoutManager"
画面よりも長い項目のリストをスクロールするために、縦方向のスクロールバーを追加する必要があります。
RecyclerView
内のandroid:scrollbars
属性をvertical
に設定します。
android:scrollbars="vertical"
完成した XML レイアウトは次のようになります。
activity_main.xml
<FrameLayout 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="match_parent"
tools:context=".MainActivity">
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/recycler_view"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:scrollbars="vertical"
app:layoutManager="LinearLayoutManager" />
</FrameLayout>
- アプリを実行します。
プロジェクトは問題なくコンパイルされ動作するはずです。ただし、重要なコードを入れていないため、アプリには白い背景のみが表示されます。この時点で、データのソースがあり、RecyclerView
がレイアウトに追加されていますが、RecyclerView
には Affirmation
オブジェクトの表示方法に関する情報がありません。
RecyclerView のアダプタを実装する
アプリには、Datasource
からデータを取得して、各 Affirmation
を RecyclerView
内の項目として表示できるように整形する手段が必要です。
アダプタは、データを RecyclerView
で使用できる形に適応させるデザイン パターンです。この場合、loadAffirmations()
によって返されたリストから Affirmation
インスタンスを受け取って、それをリスト項目ビューに変換して、RecyclerView.
に表示できるようにするアダプタが必要です。
アプリを実行すると、RecyclerView
では、データを画面にどのように表示するか決めるために、アダプタが使用されます。RecyclerView
からアダプタに対して、リストの最初のデータ項目用に新しいリスト項目ビューを作成するように指示が送られます。ビューが作成されると、アダプタは項目を描画するためのデータを要求されます。この動作は、全画面を埋めるのに十分な量のビューが RecyclerView
に提供されるまで繰り返されます。一度に 3 つのリスト項目ビューだけで画面が満たされる場合、RecyclerView
からアダプタに対して、10 項目すべてのリスト項目ビューではなく、3 つのリスト項目ビューを準備するように指示が送られます。
このステップでは、Affirmation
オブジェクト インスタンスを RecyclerView
オブジェクトに表示できるように適応させるアダプタを作成します。
アダプタを作成する
アダプタは複数の部分で構成されており、このコースでこれまでに作成したよりも、はるかに複雑で大量のコードを作成することになります。最初から詳細を十分に理解しなくても構いません。RecyclerView
を使ってアプリ全体を完成させると、すべてのパーツがどのように組み合わされるのかを深く理解できます。RecyclerView
を使って作成する今後のアプリのベースとして、このコードを再利用することもできます。
項目のレイアウトを作成する
RecyclerView
の項目ごとに個別のレイアウトがあり、それを別々のレイアウト ファイルで定義します。文字列を表示するだけなので、項目レイアウトに TextView
を使用できます。
- [res] > [layout] に、
list_item.xml
という名前の新しい空のファイルを作成します。 - [Code] ビューで
list_item.xml
を開きます。 TextView
にid
item_title
を追加します。- 次のコードのように、
layout_width
とlayout_height
にwrap_content
を追加します。
このリスト項目レイアウトは後でインフレートされ、親 RecyclerView
の子として追加されるため、レイアウトを囲む ViewGroup
は必要ありません。
list_item.xml
<?xml version="1.0" encoding="utf-8"?>
<TextView xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/item_title"
android:layout_width="wrap_content"
android:layout_height="wrap_content" />
または、[File] > [New] > [Layout Resource File] を使用して、[File name] に list_item.xml
を、[Root element] に TextView
を設定します。次に、生成されたコードを上記のコードに合わせて修正します。
ItemAdapter クラスを作成する
- Android Studio の [Project] ペインで [app] > [java] > [com.example.affirmations] を右クリックし、[New] > [Package] を選択します。
- パッケージ名の最後の部分として「
adapter
」と入力します。 adapter
パッケージを右クリックして、[New] > [Kotlin File/Class] を選択します。- クラス名として「
ItemAdapter
」と入力し、終了すると、ItemAdapter.kt
ファイルが開きます。
ItemAdapter
のコンストラクタにパラメータを追加して、アファメーションをアダプタに渡すようにする必要があります。
List<Affirmation>
型のdataset
というval
のパラメータをItemAdapter
コンストラクタに追加します。必要に応じて、Affirmation
をインポートします。dataset
は、このクラスでのみ使用されるため、private
にします。
ItemAdapter.kt
import com.example.affirmations.model.Affirmation
class ItemAdapter(private val dataset: List<Affirmation>) {
}
ItemAdapter
では、文字列リソースを解決する方法に関する情報が必要です。この情報とアプリに関するその他の情報は、ItemAdapter
インスタンスに渡すことができる Context
オブジェクト インスタンスに格納されます。
Context
型のcontext
というval
のパラメータをItemAdapter
コンストラクタに追加します。これをコンストラクタの最初のパラメータにします。
class ItemAdapter(private val context: Context, private val dataset: List<Affirmation>) {
}
ViewHolder を作成する
RecyclerView
に項目ビューとの直接のやり取りはありませんが、代わりに ViewHolders
を取り扱います。ViewHolder
は RecyclerView
内の 1 つのリスト項目ビューを表し、可能であれば再利用できます。ViewHolder
インスタンスは、リスト項目レイアウト内の個々のビューへの参照を保持します(「ビューホルダー」という名前の由来です)。これにより、リスト項目ビューを新しいデータで更新するのが簡単になります。ビューホルダーにより、RecyclerView
で画面での効率的なビューの移動に使用される情報も追加されます。
ItemAdapter
クラスの中のItemAdapter
の右波かっこの前で、ItemViewHolder
クラスを作成します。
class ItemAdapter(private val context: Context, private val dataset: List<Affirmation>) {
class ItemViewHolder()
}
- クラスを別のクラスの中で定義することは、ネストされたクラスの作成と呼ばれます。
ItemViewHolder
はItemAdapter
でのみ使用されるため、ItemAdapter
内に作成することで、この関係が示されます。これは必須ではありませんが、他のデベロッパーがプログラムの構造を理解するのに役立ちます。
View
型のprivate
val
view
をパラメータとしてItemViewHolder
クラスのコンストラクタに追加します。ItemViewHolder
をRecyclerView
ViewHolder
のサブクラスにして、view
パラメータをスーパークラス コンストラクタに渡します。ItemViewHolder
内で、TextView
型のval
プロパティtextView
を定義します。これに、list_item
.xml
で定義した IDitem_title
のビューを代入します。
class ItemAdapter(private val context: Context, private val dataset: List<Affirmation>) {
class ItemViewHolder(private val view: View) : RecyclerView.ViewHolder(view) {
val textView: TextView = view.findViewById(R.id.item_title)
}
}
アダプタ メソッドをオーバーライドする
- 抽象クラス
RecyclerView.Adapter
を拡張するItemAdapter
のコードを追加します。山かっこ内にビューホルダーの型としてItemAdapter.ItemViewHolder
を指定します。
class ItemAdapter(
private val context: Context,
private val dataset: List<Affirmation>
) : RecyclerView.Adapter<ItemAdapter.ItemViewHolder>() {
class ItemViewHolder(private val view: View) : RecyclerView.ViewHolder(view) {
val textView: TextView = view.findViewById(R.id.item_title)
}
}
RecyclerView.Adapter
の抽象メソッドを実装する必要があるため、エラーが表示されます。
ItemAdapter
にカーソルを置き、Command+I キー(Windows の場合は Control+I キー)を押します。実装する必要があるメソッドの一覧(getItemCount()
、onCreateViewHolder()
、onBindViewHolder()
)が表示されます。
- Shift+クリックを使用して 3 つの関数をすべて選択し、[OK] をクリックします。
これによって、以下のように 3 つのメソッドに適切なパラメータを持ったスタブが作成されます。
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ItemViewHolder {
TODO("Not yet implemented")
}
override fun getItemCount(): Int {
TODO("Not yet implemented")
}
override fun onBindViewHolder(holder: ItemViewHolder, position: Int) {
TODO("Not yet implemented")
}
これでエラーが表示されなくなります。次は、これらのメソッドを実装して、このアプリとして正しい動作になっていることを確認します。
getItemCount() を実装する
getItemCount()
メソッドからは、データセットのサイズを返す必要があります。アプリのデータは、ItemAdapter
コンストラクタに渡す dataset
プロパティに含まれていて、そのサイズは size
で取得できます。
getItemCount()
を次のように置き換えます。
override fun getItemCount() = dataset.size
これは以下の記述を簡略化したものです。
override fun getItemCount(): Int {
return dataset.size
}
onCreateViewHolder() を実装する
onCreateViewHolder()
メソッドは、RecyclerView
の新しいビューホルダーを作成するためにレイアウト マネージャーから呼び出されます(再利用できるビューホルダーがないとき)。ビューホルダーが 1 つのリスト項目ビューを表すことに留意してください。
onCreateViewHolder()
メソッドは、2 つのパラメータを受け取り、新しい ViewHolder
を返します。
parent
パラメータ: 新しいリスト項目ビューが子としてアタッチされるビューグループです。親はRecyclerView
です。viewType
パラメータ: 同じRecyclerView
に複数の項目ビュータイプがある場合に重要となります。RecyclerView
内に異なるリスト項目レイアウトが表示される場合、異なる項目ビュータイプがあります。リサイクルできるのは同じ項目ビュータイプのビューだけです。今回は、1 つのリスト項目レイアウトと 1 つの項目ビュータイプだけを指定しているため、このパラメータを気にする必要はありません。
onCreateViewHolder()
メソッドで、渡されたコンテキスト(parent
のcontext
)からLayoutInflater
のインスタンスを取得します。レイアウト インフレーターは、XML レイアウトをビュー オブジェクトの階層にインフレートする方法を認識しています。
val adapterLayout = LayoutInflater.from(parent.context)
LayoutInflater
オブジェクト インスタンスを作成したら、ピリオドと、その後ろに実際のリスト項目ビューをインフレートする別のメソッド呼び出しを追加します。XML レイアウトのリソース IDR.layout.list_item
とparent
ビューグループを渡します。3 番目のブール引数はattachToRoot
です。この項目はRecyclerView
によって自動的にビュー階層に追加されるため、この引数はfalse
にする必要があります。
val adapterLayout = LayoutInflater.from(parent.context)
.inflate(R.layout.list_item, parent, false)
adapterLayout
はリスト項目ビューへの参照を保持するようになりました(ここから、後で
TextView
のような子ビューを見つけることができます)。
onCreateViewHolder()
で、ルートビューがadapterLayout
である新しいItemViewHolder
インスタンスを返します。
return ItemViewHolder(adapterLayout)
現時点での onCreateViewHolder()
のコードは次のとおりです。
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ItemViewHolder {
// create a new view
val adapterLayout = LayoutInflater.from(parent.context)
.inflate(R.layout.list_item, parent, false)
return ItemViewHolder(adapterLayout)
}
onBindViewHolder() を実装する
オーバーライドする必要がある最後のメソッドは onBindViewHolder()
です。このメソッドは、レイアウト マネージャーからリスト項目ビューの内容を置き換えるために呼び出されます。
onBindViewHolder()
メソッドには 2 つのパラメータがあります。以前に onCreateViewHolder()
メソッドで作成された ItemViewHolder
と、リスト内の現在の項目 position
を表す int
です。このメソッドでは、データセットから位置に基づいて適切な Affirmation
オブジェクトを見つけます。
onBindViewHolder()
内でval
item
を作成し、dataset
内の指定されたposition
にある項目を取得します。
val item = dataset[position]
最後に、この項目に正しいデータが反映されるように、ビューホルダーによって参照されているすべてのビューを更新する必要があります。今回は、ビューが 1 つしかありません(ItemViewHolder
内の TextView
)。TextView
のテキストを、この項目の Affirmation
文字列を表示するように設定します。
Affirmation
オブジェクト インスタンスで、item.stringResourceId
を呼び出すことにより、対応する文字列リソース ID を見つけることができます。ただし、これは整数なので、実際の文字列値へのマッピングを見つける必要があります。
Android フレームワークでは、文字列リソース ID を指定して getString()
を呼び出すと、その文字列に関連付けられた文字列値が返されます。getString()
は Resources
クラスのメソッドであり、Resources
クラスのインスタンスは context
インスタンスを通じて取得できます。
つまり、context.resources.getString()
を呼び出して、文字列リソース ID を渡すことができます。結果の文字列は、holder
ItemViewHolder
にある textView
の text
に設定できます。つまり、この行のコードでは、アファメーション文字列を表示するビューホルダーを更新しています。
holder.textView.text = context.resources.getString(item.stringResourceId)
完成した onBindViewHolder()
メソッドは次のようになります。
override fun onBindViewHolder(holder: ItemViewHolder, position: Int) {
val item = dataset[position]
holder.textView.text = context.resources.getString(item.stringResourceId)
}
最終的なアダプタのコードは以下のようになります。
ItemAdapter.kt
package com.example.affirmations.adapter
import android.content.Context
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.widget.TextView
import androidx.recyclerview.widget.RecyclerView
import com.example.affirmations.R
import com.example.affirmations.model.Affirmation
/**
* Adapter for the [RecyclerView] in [MainActivity]. Displays [Affirmation] data object.
*/
class ItemAdapter(
private val context: Context,
private val dataset: List<Affirmation>
) : RecyclerView.Adapter<ItemAdapter.ItemViewHolder>() {
// Provide a reference to the views for each data item
// Complex data items may need more than one view per item, and
// you provide access to all the views for a data item in a view holder.
// Each data item is just an Affirmation object.
class ItemViewHolder(private val view: View) : RecyclerView.ViewHolder(view) {
val textView: TextView = view.findViewById(R.id.item_title)
}
/**
* Create new views (invoked by the layout manager)
*/
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ItemViewHolder {
// create a new view
val adapterLayout = LayoutInflater.from(parent.context)
.inflate(R.layout.list_item, parent, false)
return ItemViewHolder(adapterLayout)
}
/**
* Replace the contents of a view (invoked by the layout manager)
*/
override fun onBindViewHolder(holder: ItemViewHolder, position: Int) {
val item = dataset[position]
holder.textView.text = context.resources.getString(item.stringResourceId)
}
/**
* Return the size of your dataset (invoked by the layout manager)
*/
override fun getItemCount() = dataset.size
}
ItemAdapter
を実装したので、このアダプタを使用するように RecyclerView
に指示する必要があります。
RecyclerView を使用するように MainActivity を変更する
最後に、Datasource
クラスと ItemAdapter
クラスを使用し、RecyclerView
の項目を作成して表示する必要があります。この処理は MainActivity
で行います。
MainActivity.kt
を開きます。MainActivity,
で、onCreate()
メソッドに移動します。これ以降のステップで説明する新しいコードを、setContentView(R.layout.activity_main).
の呼び出しの後に挿入します。Datasource
のインスタンスを作成し、そのloadAffirmations()
メソッドを呼び出します。返されたアファメーションのリストをmyDataset
というval
に保存します。
val myDataset = Datasource().loadAffirmations()
recyclerView
という変数を作成し、findViewById()
を使用して、レイアウト内のRecyclerView
への参照を見つけます。
val recyclerView = findViewById<RecyclerView>(R.id.recycler_view)
- 作成した
ItemAdapter
クラスを使用するようにrecyclerView
に指示するために、新しいItemAdapter
インスタンスを作成します。ItemAdapter
には、このアクティビティのコンテキスト(this
)とmyDataset
のアファメーションという 2 つのパラメータが必要です。 ItemAdapter
オブジェクトをrecyclerView
のadapter
プロパティに代入します。
recyclerView.adapter = ItemAdapter(this, myDataset)
- アクティビティ レイアウトでは
RecyclerView
のレイアウト サイズが固定されているため、RecyclerView
のsetHasFixedSize
パラメータをtrue
に設定できます。この設定は、パフォーマンスの向上にのみ必要です。内容を変更してもRecyclerView
のレイアウト サイズが変わらないことがわかっている場合は、この設定を使用します。
recyclerView.setHasFixedSize(true)
- 完了すると、
MainActivity
のコードは次のようになります。
MainActivity.kt
package com.example.affirmations
import android.os.Bundle
import androidx.appcompat.app.AppCompatActivity
import androidx.recyclerview.widget.RecyclerView
import com.example.affirmations.adapter.ItemAdapter
import com.example.affirmations.data.Datasource
class MainActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
// Initialize data.
val myDataset = Datasource().loadAffirmations()
val recyclerView = findViewById<RecyclerView>(R.id.recycler_view)
recyclerView.adapter = ItemAdapter(this, myDataset)
// Use this setting to improve performance if you know that changes
// in content do not change the layout size of the RecyclerView
recyclerView.setHasFixedSize(true)
}
}
- アプリを実行します。アファメーション文字列のリストが画面に表示されます。
これで完成です。RecyclerView
とカスタム アダプタを使用してデータのリストを表示するアプリを作成しました。作成したコードをよく読んで、各部分がどのように連携するのかを理解しましょう。
このアプリには、アファメーションを表示するのに必要なパーツがすべて揃っていますが、製品版とするには不十分です。UI に改良の余地がありそうです。次の Codelab では、コードを改良して、アプリに画像を追加する方法を学習し、UI を改良します。
この Codelab の解答コードは、以下に示すプロジェクトとモジュールにあります。ファイルの先頭の package
ステートメントで示されるように、一部の Kotlin ファイルは別のパッケージに入っています。
res/values/strings.xml
<resources>
<string name="app_name">Affirmations</string>
<string name="affirmation1">I am strong.</string>
<string name="affirmation2">I believe in myself.</string>
<string name="affirmation3">Each day is a new opportunity to grow and be a better version of myself.</string>
<string name="affirmation4">Every challenge in my life is an opportunity to learn from.</string>
<string name="affirmation5">I have so much to be grateful for.</string>
<string name="affirmation6">Good things are always coming into my life.</string>
<string name="affirmation7">New opportunities await me at every turn.</string>
<string name="affirmation8">I have the courage to follow my heart.</string>
<string name="affirmation9">Things will unfold at precisely the right time.</string>
<string name="affirmation10">I will be present in all the moments that this day brings.</string>
</resources>
affirmations/data/Datasource.kt
package com.example.affirmations.data
import com.example.affirmations.R
import com.example.affirmations.model.Affirmation
class Datasource {
fun loadAffirmations(): List<Affirmation> {
return listOf<Affirmation>(
Affirmation(R.string.affirmation1),
Affirmation(R.string.affirmation2),
Affirmation(R.string.affirmation3),
Affirmation(R.string.affirmation4),
Affirmation(R.string.affirmation5),
Affirmation(R.string.affirmation6),
Affirmation(R.string.affirmation7),
Affirmation(R.string.affirmation8),
Affirmation(R.string.affirmation9),
Affirmation(R.string.affirmation10)
)
}
}
affirmations/model/Affirmation.kt
package com.example.affirmations.model
data class Affirmation(val stringResourceId: Int)
affirmations/MainActivty.kt
package com.example.affirmations
import android.os.Bundle
import androidx.appcompat.app.AppCompatActivity
import androidx.recyclerview.widget.RecyclerView
import com.example.affirmations.adapter.ItemAdapter
import com.example.affirmations.data.Datasource
class MainActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
// Initialize data.
val myDataset = Datasource().loadAffirmations()
val recyclerView = findViewById<RecyclerView>(R.id.recycler_view)
recyclerView.adapter = ItemAdapter(this, myDataset)
// Use this setting to improve performance if you know that changes
// in content do not change the layout size of the RecyclerView
recyclerView.setHasFixedSize(true)
}
}
affirmations/adapter/ItemAdapter.kt
package com.example.affirmations.adapter
import android.content.Context
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.widget.TextView
import androidx.recyclerview.widget.RecyclerView
import com.example.affirmations.R
import com.example.affirmations.model.Affirmation
/**
* Adapter for the [RecyclerView] in [MainActivity]. Displays [Affirmation] data object.
*/
class ItemAdapter(
private val context: Context,
private val dataset: List<Affirmation>
) : RecyclerView.Adapter<ItemAdapter.ItemViewHolder>() {
// Provide a reference to the views for each data item
// Complex data items may need more than one view per item, and
// you provide access to all the views for a data item in a view holder.
// Each data item is just an Affirmation object.
class ItemViewHolder(private val view: View) : RecyclerView.ViewHolder(view) {
val textView: TextView = view.findViewById(R.id.item_title)
}
/**
* Create new views (invoked by the layout manager)
*/
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ItemViewHolder {
// create a new view
val adapterLayout = LayoutInflater.from(parent.context)
.inflate(R.layout.list_item, parent, false)
return ItemViewHolder(adapterLayout)
}
/**
* Replace the contents of a view (invoked by the layout manager)
*/
override fun onBindViewHolder(holder: ItemViewHolder, position: Int) {
val item = dataset[position]
holder.textView.text = context.resources.getString(item.stringResourceId)
}
/**
* Return the size of your dataset (invoked by the layout manager)
*/
override fun getItemCount() = dataset.size
}
src/main/res/layout/activty_main.xml
<FrameLayout 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="match_parent"
tools:context=".MainActivity">
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/recycler_view"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:scrollbars="vertical"
app:layoutManager="LinearLayoutManager" />
</FrameLayout>
src/main/res/layout/list_item.xml
<?xml version="1.0" encoding="utf-8"?>
<TextView xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/item_title"
android:layout_width="wrap_content"
android:layout_height="wrap_content" />
RecyclerView
ウィジェットを使用すると、データのリストを表示できます。RecyclerView
は、アダプタ パターンを使用して、データの調整と表示を行います。ViewHolder
は、RecyclerView
のためにビューを作成して保持します。RecyclerView
は、組み込みのLayoutManagers
に付属しています。RecyclerView
は、項目の配置をLayoutManagers
に委譲します。
アダプタの実装は次のように行います。
- アダプタ用に新しいクラスを作成します(例:
ItemAdapter
)。 - 単一のリスト項目ビューを表すカスタム
ViewHolder
クラスを作成します。RecyclerView.ViewHolder
クラスを拡張します。 ItemAdapter
クラスを、RecyclerView
.Adapter
クラス(カスタムのViewHolder
クラス付き)を拡張するように書き換えます。- メソッド
getItemsCount()
、onCreateViewHolder()
、onBindViewHolder()
をアダプタ内に実装します。