この Codelab は、Android Kotlin の基礎コースの一部です。このコースを最大限に活用するには、Codelab を順番に進めることをおすすめします。コースのすべての Codelab は、Android Kotlin の基礎の Codelab のランディング ページに一覧表示されています。
はじめに
前の Codelab では、TrackMySleepQuality アプリを更新して、睡眠の質に関するデータを RecyclerView に表示しました。最初の RecyclerView を作成したときに学んだ手法は、それほど大きくないシンプルなリストを表示するほとんどの RecyclerViews に十分です。ただし、大規模なリストでの RecyclerView の効率を高め、複雑なリストやグリッドでのコードの保守と拡張を容易にする手法は数多くあります。
この Codelab では、前の Codelab で作成した睡眠トラッカー アプリをベースにアプリを作成します。睡眠データのリストを更新するより効果的な方法と、RecyclerView でデータ バインディングを使用する方法を学びます。(前の Codelab のアプリがない場合は、この Codelab のスターター コードをダウンロードできます)。
前提となる知識
- アクティビティ、フラグメント、ビューを使用して基本的なユーザー インターフェースを構築する。
- フラグメント間の移動、
safeArgsを使用してフラグメント間でデータを渡す。 - モデル、モデル ファクトリ、変換、
LiveData、オブザーバーを表示します。 Roomデータベースを作成し、DAO を作成して、エンティティを定義する方法。- データベースやその他の長時間実行タスクにコルーチンを使用する方法。
Adapter、ViewHolder、アイテム レイアウトを使用して基本的なRecyclerViewを実装する方法。
学習内容
DiffUtilを使用してRecyclerViewで表示されるリストを効率的に更新する方法。RecyclerViewでデータ バインディングを使用する方法。- バインディング アダプタを使用してデータを変換する方法。
演習内容
- このシリーズの前の Codelab で作成した TrackMySleepQuality アプリをベースに構築します。
SleepNightAdapterを更新して、DiffUtilを使用してリストを効率的に更新します。- バインディング アダプターを使用してデータを変換し、
RecyclerViewのデータ バインディングを実装します。
睡眠トラッカー アプリには、下の図に示すように、フラグメントで表される 2 つの画面があります。
|
|
左側に表示されている最初の画面には、トラッキングの開始と停止のボタンがあります。画面には、ユーザーの睡眠データの一部が表示されます。[消去] ボタンをクリックすると、アプリがユーザーのために収集したすべてのデータが完全に削除されます。右側の 2 つ目の画面は、睡眠の質の評価を選択するための画面です。
このアプリは、UI コントローラ ViewModel と LiveData、および Room データベースを使用して睡眠データを永続化するように設計されています。

睡眠データは RecyclerView に表示されます。この Codelab では、RecyclerView の DiffUtil とデータバインディング部分を構築します。この Codelab を終了すると、アプリの外観はまったく同じになりますが、効率が向上し、スケーリングとメンテナンスが容易になります。
前の Codelab の SleepTracker アプリを引き続き使用することも、GitHub から RecyclerViewDiffUtilDataBinding-Starter アプリをダウンロードすることもできます。
- 必要に応じて、GitHub から RecyclerViewDiffUtilDataBinding-Starter アプリをダウンロードし、Android Studio でプロジェクトを開きます。
- アプリを実行します。
SleepNightAdapter.ktファイルを開きます。- コードを調べて、アプリの構造を理解します。下の図は、アダプター パターンで
RecyclerViewを使用して睡眠データをユーザーに表示する方法をまとめたものです。

- アプリは、ユーザー入力から
SleepNightオブジェクトのリストを作成します。各SleepNightオブジェクトは、1 泊の睡眠、その時間、質を表します。 SleepNightAdapterは、SleepNightオブジェクトのリストをRecyclerViewが使用して表示できるものに適合させます。SleepNightAdapterアダプターは、リサイクラー ビューがデータを表示するためのビュー、データ、メタ情報を含むViewHoldersを生成します。RecyclerViewはSleepNightAdapterを使用して、表示するアイテムの数(getItemCount())を決定します。RecyclerViewはonCreateViewHolder()とonBindViewHolder()を使用して、表示するデータにバインドされたビューホルダーを取得します。
notifyDataSetChanged() メソッドは非効率的です
リスト内のアイテムが変更され、更新が必要であることを RecyclerView に伝えるために、現在のコードは、次の例に示すように、SleepNightAdapter の notifyDataSetChanged() を呼び出します。
var data = listOf<SleepNight>()
set(value) {
field = value
notifyDataSetChanged()
}ただし、notifyDataSetChanged() はリスト全体が無効になる可能性があることを RecyclerView に伝えます。その結果、RecyclerView は画面に表示されていないアイテムも含め、リスト内のすべてのアイテムを再バインドして再描画します。これは不要な作業です。リストが大きい場合や複雑な場合、この処理に時間がかかり、ユーザーがリストをスクロールする際に画面がちらついたり、途切れたりすることがあります。
この問題を解決するには、RecyclerView に変更内容を正確に伝えます。RecyclerView は、画面上で変更されたビューのみを更新できます。
RecyclerView には、単一の要素を更新するための豊富な API があります。notifyItemChanged() を使用して、アイテムが変更されたことを RecyclerView に通知できます。同様の関数を、追加、削除、移動されたアイテムに使用できます。すべて手動で行うこともできますが、このタスクは簡単ではなく、かなりの量のコードが必要になる可能性があります。
幸いなことに、もっと良い方法があります。
DiffUtil は効率的で、面倒な作業を代行してくれる
RecyclerView には、2 つのリストの差を計算するための DiffUtil というクラスがあります。DiffUtil は古いリストと新しいリストを受け取り、違いを特定します。追加、削除、変更されたアイテムを検出します。次に、Eugene W. Myers の差分アルゴリズムを使用して、古いリストから新しいリストを作成するために必要な最小限の変更を特定します。
DiffUtil が変更内容を把握すると、RecyclerView はその情報を使用して、変更、追加、削除、移動されたアイテムのみを更新できます。これは、リスト全体をやり直すよりもはるかに効率的です。
このタスクでは、SleepNightAdapter をアップグレードして DiffUtil を使用し、データの変更に合わせて RecyclerView を最適化します。
ステップ 1: SleepNightDiffCallback を実装する
DiffUtil クラスの機能を使用するには、DiffUtil.ItemCallback を拡張します。
SleepNightAdapter.ktを開きます。SleepNightAdapterの完全なクラス定義の下に、DiffUtil.ItemCallbackを拡張するSleepNightDiffCallbackという新しいトップレベル クラスを作成します。SleepNightを汎用パラメータとして渡します。
class SleepNightDiffCallback : DiffUtil.ItemCallback<SleepNight>() {
}SleepNightDiffCallbackクラス名にカーソルを置きます。Alt+Enter(Mac の場合はOption+Enter)を押して、[Implement Members] を選択します。- 表示されたダイアログで、Shift キーを押しながら左クリックして
areItemsTheSame()メソッドとareContentsTheSame()メソッドを選択し、[OK] をクリックします。
これにより、次の図に示すように、2 つのメソッドのスタブがSleepNightDiffCallback内に生成されます。DiffUtilは、これらの 2 つのメソッドを使用して、リストとアイテムがどのように変更されたかを判断します。
override fun areItemsTheSame(oldItem: SleepNight, newItem: SleepNight): Boolean {
TODO("not implemented") //To change body of created functions use File | Settings | File Templates.
}
override fun areContentsTheSame(oldItem: SleepNight, newItem: SleepNight): Boolean {
TODO("not implemented") //To change body of created functions use File | Settings | File Templates.
}areItemsTheSame()内で、TODOを、渡された 2 つのSleepNight項目oldItemとnewItemが同じかどうかをテストするコードに置き換えます。アイテムのnightIdが同じ場合は、同じアイテムであるため、trueを返します。それ以外の場合は、falseを返します。DiffUtilは、このテストを使用して、アイテムが追加、削除、移動されたかどうかを検出します。
override fun areItemsTheSame(oldItem: SleepNight, newItem: SleepNight): Boolean {
return oldItem.nightId == newItem.nightId
}areContentsTheSame()内で、oldItemとnewItemに同じデータが含まれているかどうか、つまり等しいかどうかを確認します。SleepNightはデータクラスであるため、この等価性チェックではすべてのフィールドがチェックされます。Dataクラスは、equalsとその他のいくつかのメソッドを自動的に定義します。oldItemとnewItemの間に違いがある場合、このコードはDiffUtilにアイテムが更新されたことを伝えます。
override fun areContentsTheSame(oldItem: SleepNight, newItem: SleepNight): Boolean {
return oldItem == newItem
}RecyclerView を使用して変更されるリストを表示するのは一般的なパターンです。RecyclerView は、リストを基盤とする RecyclerView アダプタの構築に役立つアダプタ クラス ListAdapter を提供します。
ListAdapter はリストを追跡し、リストが更新されるとアダプターに通知します。
ステップ 1: アダプターを変更して ListAdapter を拡張する
SleepNightAdapter.ktファイルで、SleepNightAdapterのクラス シグネチャをListAdapterを拡張するように変更します。- プロンプトが表示されたら、
androidx.recyclerview.widget.ListAdapterをインポートします。 SleepNightAdapter.ViewHolderの前に、ListAdapterの最初の引数としてSleepNightを追加します。- コンストラクタのパラメータとして
SleepNightDiffCallback()を追加します。ListAdapterは、これを使用してリスト内の変更点を把握します。完成したSleepNightAdapterクラスのシグネチャは次のようになります。
class SleepNightAdapter : ListAdapter<SleepNight, SleepNightAdapter.ViewHolder>(SleepNightDiffCallback()) {SleepNightAdapterクラス内で、セッターを含むdataフィールドを削除します。ListAdapterがリストを追跡してくれるため、この変数は不要になりました。ListAdapterがこのメソッドを実装するため、getItemCount()のオーバーライドを削除します。onBindViewHolder()のエラーを解消するには、item変数を変更します。dataを使用してitemを取得する代わりに、ListAdapterが提供するgetItem(position)メソッドを呼び出します。
val item = getItem(position)ステップ 2: submitList() を使用してリストを最新の状態に保つ
コードでは、変更されたリストが利用可能になったときに ListAdapter に通知する必要があります。ListAdapter は、リストの新しいバージョンが利用可能であることを ListAdapter に通知する submitList() というメソッドを提供します。このメソッドが呼び出されると、ListAdapter は新しいリストと古いリストを比較し、追加、削除、移動、変更された項目を検出します。次に、ListAdapter が RecyclerView で表示されるアイテムを更新します。
SleepTrackerFragment.ktを開きます。onCreateView()のsleepTrackerViewModelのオブザーバーで、削除したdata変数が参照されているエラーを見つけます。adapter.data = itをadapter.submitList(it)の呼び出しに置き換えます。更新されたコードは次のとおりです。
sleepTrackerViewModel.nights.observe(viewLifecycleOwner, Observer {
it?.let {
adapter.submitList(it)
}
})- アプリを実行します。実行速度が速くなります。リストが小さい場合は、速度の向上を実感できないかもしれません。
このタスクでは、前の Codelab と同じ手法を使用してデータ バインディングを設定し、findViewById() の呼び出しを削除します。
ステップ 1: レイアウト ファイルにデータ バインディングを追加する
- [テキスト] タブで
list_item_sleep_night.xmlレイアウト ファイルを開きます。 ConstraintLayoutタグにカーソルを置き、Alt+Enterキー(Mac の場合はOption+Enterキー)を押します。インテンション メニュー(クイック フィックス メニュー)が開きます。- [Convert to data binding layout] を選択します。これにより、レイアウトが
<layout>でラップされ、その中に<data>タグが追加されます。 - 必要に応じて一番上までスクロールし、
<data>タグ内でsleepという名前の変数を宣言します。 typeをSleepNight、com.example.android.trackmysleepquality.database.SleepNightの完全修飾名にします。完成した<data>タグは次のようになります。
<data>
<variable
name="sleep"
type="com.example.android.trackmysleepquality.database.SleepNight"/>
</data>Bindingオブジェクトの作成を強制するには、[Build] > [Clean Project] を選択してから、[Build] > [Rebuild Project] を選択します。(それでも問題が解決しない場合は、[File] > [Invalidate Caches / Restart] を選択します)。ListItemSleepNightBindingバインディング オブジェクトと関連するコードが、プロジェクトの生成されたファイルに追加されます。
ステップ 2: データ バインディングを使用してアイテム レイアウトを拡張する
SleepNightAdapter.ktを開きます。ViewHolderクラスでfrom()メソッドを見つけます。view変数の宣言を削除します。
削除するコード:
val view = layoutInflater
.inflate(R.layout.list_item_sleep_night, parent, false)view変数があった場所に、次に示すように、ListItemSleepNightBindingバインディング オブジェクトを拡張するbindingという新しい変数を定義します。バインディング オブジェクトの必要なインポートを行います。
val binding =
ListItemSleepNightBinding.inflate(layoutInflater, parent, false)- 関数の最後で、
viewを返すのではなく、bindingを返します。
return ViewHolder(binding)- エラーを解消するには、
bindingという単語にカーソルを合わせます。Alt+Enter(Mac の場合はOption+Enter)キーを押して、インテンション メニューを開きます。
- [Change parameter 'itemView' type of primary constructor of class 'ViewHolder' to 'ListItemSleepNightBinding'] を選択します。これにより、
ViewHolderクラスのパラメータ タイプが更新されます。

ViewHolderのクラス定義までスクロールして、シグネチャの変更を確認します。from()メソッドでitemViewをbindingに変更したため、itemViewのエラーが表示されます。ViewHolderクラス定義で、itemViewのいずれかの出現箇所を右クリックし、[Refactor] > [Rename] を選択します。名前を「binding」に変更します。- コンストラクタ パラメータ
bindingの前にvalを付けて、プロパティにします。 - 親クラス
RecyclerView.ViewHolderの呼び出しで、パラメータをbindingからbinding.rootに変更します。Viewを渡す必要があります。binding.rootはアイテム レイアウトのルートConstraintLayoutです。 - 完成したクラス宣言は次のようになります。
class ViewHolder private constructor(val binding: ListItemSleepNightBinding) : RecyclerView.ViewHolder(binding.root){findViewById() の呼び出しに関するエラーも表示されます。このエラーは次の手順で修正します。
ステップ 3: findViewById() を置き換える
sleepLength、quality、qualityImage の各プロパティを更新して、findViewById() ではなく binding オブジェクトを使用できるようになりました。
- 以下に示すように、
sleepLength、qualityString、qualityImageの初期化を変更して、bindingオブジェクトのビューを使用します。これで、コードにエラーが表示されなくなります。
val sleepLength: TextView = binding.sleepLength
val quality: TextView = binding.qualityString
val qualityImage: ImageView = binding.qualityImageバインディング オブジェクトが配置されているため、sleepLength、quality、qualityImage の各プロパティを定義する必要はなくなりました。DataBinding はルックアップをキャッシュに保存するため、これらのプロパティを宣言する必要はありません。
sleepLength、quality、qualityImageプロパティ名を右クリックします。[Refactor > Inline] を選択するか、Control+Command+N(Mac の場合はOption+Command+N)を押します。
- アプリを実行します(エラーがある場合は、プロジェクトのクリーンと再ビルドが必要になることがあります)。
このタスクでは、バインディング アダプターでデータ バインディングを使用し、ビューにデータを設定するようにアプリをアップグレードします。
前の Codelab では、Transformations クラスを使用して LiveData を取得し、テキスト ビューに表示する書式設定された文字列を生成しました。ただし、異なる型や複雑な型をバインドする必要がある場合は、データ バインディングでそれらの型を使用できるようにバインディング アダプタを提供できます。バインディング アダプターは、データを取得して、テキストや画像などのビューをバインドするためにデータ バインディングで使用できるものに変換するアダプターです。
3 つのバインディング アダプタ(高品質の画像用と、テキスト フィールドごとに 1 つ)を実装します。要するに、バインディング アダプターを宣言するには、アイテムとビューを受け取るメソッドを定義し、それに @BindingAdapter アノテーションを付けます。メソッドの本体で、変換を実装します。Kotlin では、データを受け取るビュークラスの拡張関数としてバインディング アダプタを作成できます。
ステップ 1: バインディング アダプタを作成する
この手順では複数のクラスをインポートする必要がありますが、個別に呼び出されることはありません。
SleepNightAdapater.ktを開きます。ViewHolderクラス内でbind()メソッドを見つけ、このメソッドの機能を確認します。binding.sleepLength、binding.quality、binding.qualityImageの値を計算するコードを取得し、アダプタ内で使用します。(今のところ、コードはそのままにしておきます。後のステップで移動します)。sleeptrackerパッケージで、BindingUtils.ktというファイルを作成して開きます。TextViewでsetSleepDurationFormattedという拡張関数を宣言し、SleepNightを渡します。この関数は、睡眠時間を計算してフォーマットするためのアダプターになります。
fun TextView.setSleepDurationFormatted(item: SleepNight) {}setSleepDurationFormattedの本文で、ViewHolder.bind()の場合と同様に、データをビューにバインドします。convertDurationToFormatted()を呼び出し、TextViewのtextを書式設定されたテキストに設定します。(これはTextViewの拡張関数であるため、textプロパティに直接アクセスできます)。
text = convertDurationToFormatted(item.startTimeMilli, item.endTimeMilli, context.resources)- このバインディング アダプターをデータ バインディングに伝えるには、関数に
@BindingAdapterアノテーションを付けます。 - この関数は
sleepDurationFormatted属性のアダプターであるため、@BindingAdapterの引数としてsleepDurationFormattedを渡します。
@BindingAdapter("sleepDurationFormatted")- 2 番目のアダプタは、
SleepNightオブジェクトの値に基づいて睡眠の質を設定します。TextViewにsetSleepQualityString()という名前の拡張関数を作成し、SleepNightを渡します。 - 本文で、
ViewHolder.bind()の場合と同様に、データをビューにバインドします。convertNumericQualityToStringを呼び出してtextを設定します。 - 関数に
@BindingAdapter("sleepQualityString")アノテーションを付けます。
@BindingAdapter("sleepQualityString")
fun TextView.setSleepQualityString(item: SleepNight) {
text = convertNumericQualityToString(item.sleepQuality, context.resources)
}- 3 つ目のバインディング アダプターは、画像ビューに画像を設定します。以下に示すように、
ImageViewで拡張関数を作成し、setSleepImageを呼び出し、ViewHolder.bind()のコードを使用します。
@BindingAdapter("sleepImage")
fun ImageView.setSleepImage(item: SleepNight) {
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: SleepNightAdapter を更新する
SleepNightAdapter.ktを開きます。bind()メソッド内のすべてを削除します。データ バインディングと新しいアダプターを使用して、この作業を行うことができるためです。
fun bind(item: SleepNight) {
}bind()内で、itemにスリープを割り当てます。これは、バインディング オブジェクトに新しいSleepNightを通知する必要があるためです。
binding.sleep = item- その行の下に
binding.executePendingBindings()を追加します。この呼び出しは、保留中のバインディングをすぐに実行するようにデータ バインディングに要求する最適化です。RecyclerViewでバインディング アダプターを使用する場合は、常にexecutePendingBindings()を呼び出すことをおすすめします。ビューのサイズ設定をわずかに高速化できるためです。
binding.executePendingBindings()ステップ 3: XML レイアウトにバインディングを追加する
list_item_sleep_night.xmlを開きます。ImageViewで、画像を設定するバインディング アダプターと同じ名前のappプロパティを追加します。以下に示すように、sleep変数を渡します。
このプロパティは、アダプターを介してビューとバインディング オブジェクト間の接続を作成します。sleepImageが参照されるたびに、アダプターはSleepNightからデータを適応させます。
app:sleepImage="@{sleep}"sleep_lengthとquality_stringのテキスト ビューについても同様に処理します。sleepDurationFormattedまたはsleepQualityStringが参照されるたびに、アダプターはSleepNightからデータを適応させます。
app:sleepDurationFormatted="@{sleep}"app:sleepQualityString="@{sleep}"- アプリを実行します。以前とまったく同じように動作します。バインディング アダプタは、データの変更に応じてビューのフォーマットと更新を行うすべての処理を担当するため、
ViewHolderが簡素化され、コードの構造が以前よりも大幅に改善されます。
ここ数回のエクササイズで同じリストが表示されています。これは、Adapter インターフェースを使用すると、さまざまな方法でコードを設計できることを示すための設計です。コードが複雑になるほど、アーキテクチャを適切に設計することが重要になります。本番環境アプリでは、これらのパターンやその他のパターンが RecyclerView とともに使用されます。どのパターンも機能し、それぞれにメリットがあります。どちらを選択するかは、構築する内容によって異なります。
おめでとうございます。これで、Android での RecyclerView の習得はほぼ完了です。
Android Studio プロジェクト: RecyclerViewDiffUtilDataBinding。
DiffUtil:
RecyclerViewには、2 つのリストの差を計算するためのDiffUtilというクラスがあります。DiffUtilには、2 つのリストの違いを把握するために拡張するItemCallBackというクラスがあります。ItemCallbackクラスで、areItemsTheSame()メソッドとareContentsTheSame()メソッドをオーバーライドする必要があります。
ListAdapter:
- リスト管理を無料で利用するには、
RecyclerView.Adapterの代わりにListAdapterクラスを使用します。ただし、ListAdapterを使用する場合は、他のレイアウト用に独自のアダプターを作成する必要があります。この Codelab では、その方法について説明します。 - Android Studio でインテンション メニューを開くには、コードの任意の項目にカーソルを置き、
Alt+Enter(Mac の場合はOption+Enter)を押します。このメニューは、コードのリファクタリングやメソッドを実装するためのスタブの作成に特に便利です。メニューはコンテキスト依存型であるため、正しいメニューを表示するには、カーソルを正確に配置する必要があります。
データ バインディング:
- アイテム レイアウトでデータ バインディングを使用して、データをビューにバインドします。
バインディング アダプター:
- 以前は
Transformationsを使用してデータから文字列を作成していました。異なる型や複雑な型のデータをバインドする必要がある場合は、データ バインディングがそれらを使用できるようにバインディング アダプターを提供します。 - バインディング アダプターを宣言するには、アイテムとビューを受け取るメソッドを定義し、そのメソッドに
@BindingAdapterアノテーションを付けます。Kotlin では、バインディング アダプターをViewの拡張関数として記述できます。アダプターが適応するプロパティの名前を渡します。次に例を示します。
@BindingAdapter("sleepDurationFormatted")- XML レイアウトで、バインディング アダプタと同じ名前の
appプロパティを設定します。データを含む変数を渡します。次に例を示します。
.app:sleepDurationFormatted="@{sleep}"Udacity のコース:
Android デベロッパー ドキュメント:
- RecyclerView でリストを作成する
RecyclerViewDiffUtil- データ バインディング ライブラリ
- バインディング アダプター
notifyDataSetChanged()Transformations
その他のリソース:
このセクションでは、インストラクター主導のコースの一環として、この Codelab に取り組んでいる生徒向けに考えられる宿題をいくつか示します。インストラクターは、以下のようなことを行えます。
- 必要に応じて宿題を与える
- 宿題の提出方法を生徒に伝える
- 宿題を採点する
インストラクターは、これらの提案を必要なだけ使用し、必要に応じて他の宿題も自由に与えることができます。
この Codelab に独力で取り組む場合は、これらの宿題を自由に使用して知識をテストしてください。
以下の質問に回答してください
問題 1
DiffUtil を使用するのに必要なのは、次のどれですか。該当するものをすべて選択してください。
▢ ItemCallBack クラスを拡張します。
▢ areItemsTheSame() をオーバーライドします。
▢ areContentsTheSame() をオーバーライドします。
▢ データ バインディングを使用してアイテム間の違いをトラッキングする。
質問 2
バインディング アダプターの説明として正しいものは次のうちどれですか。
▢ バインディング アダプターは @BindingAdapter アノテーションが付いた関数です。
▢ バインディング アダプタを使用すると、データ形式とビューホルダーを分離できます。
▢ バインディング アダプターを使用するには RecyclerViewAdapter を使用する必要があります。
▢ バインディング アダプターは、複雑なデータを変換する必要がある場合に適したソリューションである。
質問 3
バインディング アダプターの代わりに Transformations を使用することを検討すべきなのは、どのような場合ですか?該当するものをすべて選択してください。
▢ データがシンプルである。
▢ 文字列をフォーマットしている。
▢ リストが長すぎる。
▢ ViewHolder に含まれるビューが 1 つのみである。
次のレッスンに進む:

