Android Kotlin の基礎 07.2: DiffUtil および RecyclerView とのデータ バインディング

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

はじめに

前の Codelab では、TrackMySleepQuality アプリを更新して、睡眠の質に関するデータを RecyclerView に表示しました。最初の RecyclerView を作成したときに学んだ手法は、それほど大きくないシンプルなリストを表示するほとんどの RecyclerViews に十分です。ただし、大規模なリストでの RecyclerView の効率を高め、複雑なリストやグリッドでのコードの保守と拡張を容易にする手法は数多くあります。

この Codelab では、前の Codelab で作成した睡眠トラッカー アプリをベースにアプリを作成します。睡眠データのリストを更新するより効果的な方法と、RecyclerView でデータ バインディングを使用する方法を学びます。(前の Codelab のアプリがない場合は、この Codelab のスターター コードをダウンロードできます)。

前提となる知識

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

学習内容

  • DiffUtil を使用して RecyclerView で表示されるリストを効率的に更新する方法。
  • RecyclerView でデータ バインディングを使用する方法。
  • バインディング アダプタを使用してデータを変換する方法。

演習内容

  • このシリーズの前の Codelab で作成した TrackMySleepQuality アプリをベースに構築します。
  • SleepNightAdapter を更新して、DiffUtil を使用してリストを効率的に更新します。
  • バインディング アダプターを使用してデータを変換し、RecyclerView のデータ バインディングを実装します。

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

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

このアプリは、UI コントローラ ViewModelLiveData、および Room データベースを使用して睡眠データを永続化するように設計されています。

睡眠データは RecyclerView に表示されます。この Codelab では、RecyclerViewDiffUtil とデータバインディング部分を構築します。この Codelab を終了すると、アプリの外観はまったく同じになりますが、効率が向上し、スケーリングとメンテナンスが容易になります。

前の Codelab の SleepTracker アプリを引き続き使用することも、GitHub から RecyclerViewDiffUtilDataBinding-Starter アプリをダウンロードすることもできます。

  1. 必要に応じて、GitHub から RecyclerViewDiffUtilDataBinding-Starter アプリをダウンロードし、Android Studio でプロジェクトを開きます。
  2. アプリを実行します。
  3. SleepNightAdapter.kt ファイルを開きます。
  4. コードを調べて、アプリの構造を理解します。下の図は、アダプター パターンで RecyclerView を使用して睡眠データをユーザーに表示する方法をまとめたものです。

  • アプリは、ユーザー入力から SleepNight オブジェクトのリストを作成します。各 SleepNight オブジェクトは、1 泊の睡眠、その時間、質を表します。
  • SleepNightAdapter は、SleepNight オブジェクトのリストを RecyclerView が使用して表示できるものに適合させます。
  • SleepNightAdapter アダプターは、リサイクラー ビューがデータを表示するためのビュー、データ、メタ情報を含む ViewHolders を生成します。
  • RecyclerViewSleepNightAdapter を使用して、表示するアイテムの数(getItemCount())を決定します。RecyclerViewonCreateViewHolder()onBindViewHolder() を使用して、表示するデータにバインドされたビューホルダーを取得します。

notifyDataSetChanged() メソッドは非効率的です

リスト内のアイテムが変更され、更新が必要であることを RecyclerView に伝えるために、現在のコードは、次の例に示すように、SleepNightAdapternotifyDataSetChanged() を呼び出します。

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 を拡張します。

  1. SleepNightAdapter.kt を開きます。
  2. SleepNightAdapter の完全なクラス定義の下に、DiffUtil.ItemCallback を拡張する SleepNightDiffCallback という新しいトップレベル クラスを作成します。SleepNight を汎用パラメータとして渡します。
class SleepNightDiffCallback : DiffUtil.ItemCallback<SleepNight>() {
}
  1. SleepNightDiffCallback クラス名にカーソルを置きます。
  2. Alt+Enter (Mac の場合は Option+Enter )を押して、[Implement Members] を選択します。
  3. 表示されたダイアログで、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.
    }
  1. areItemsTheSame() 内で、TODO を、渡された 2 つの SleepNight 項目 oldItemnewItem が同じかどうかをテストするコードに置き換えます。アイテムの nightId が同じ場合は、同じアイテムであるため、true を返します。それ以外の場合は、false を返します。DiffUtil は、このテストを使用して、アイテムが追加、削除、移動されたかどうかを検出します。
override fun areItemsTheSame(oldItem: SleepNight, newItem: SleepNight): Boolean {
   return oldItem.nightId == newItem.nightId
}
  1. areContentsTheSame() 内で、oldItemnewItem に同じデータが含まれているかどうか、つまり等しいかどうかを確認します。SleepNight はデータクラスであるため、この等価性チェックではすべてのフィールドがチェックされます。Data クラスは、equals とその他のいくつかのメソッドを自動的に定義します。oldItemnewItem の間に違いがある場合、このコードは DiffUtil にアイテムが更新されたことを伝えます。
override fun areContentsTheSame(oldItem: SleepNight, newItem: SleepNight): Boolean {
   return oldItem == newItem
}

RecyclerView を使用して変更されるリストを表示するのは一般的なパターンです。RecyclerView は、リストを基盤とする RecyclerView アダプタの構築に役立つアダプタ クラス ListAdapter を提供します。

ListAdapter はリストを追跡し、リストが更新されるとアダプターに通知します。

ステップ 1: アダプターを変更して ListAdapter を拡張する

  1. SleepNightAdapter.kt ファイルで、SleepNightAdapter のクラス シグネチャを ListAdapter を拡張するように変更します。
  2. プロンプトが表示されたら、androidx.recyclerview.widget.ListAdapter をインポートします。
  3. SleepNightAdapter.ViewHolder の前に、ListAdapter の最初の引数として SleepNight を追加します。
  4. コンストラクタのパラメータとして SleepNightDiffCallback() を追加します。ListAdapter は、これを使用してリスト内の変更点を把握します。完成した SleepNightAdapter クラスのシグネチャは次のようになります。
class SleepNightAdapter : ListAdapter<SleepNight, SleepNightAdapter.ViewHolder>(SleepNightDiffCallback()) {
  1. SleepNightAdapter クラス内で、セッターを含む data フィールドを削除します。ListAdapter がリストを追跡してくれるため、この変数は不要になりました。
  2. ListAdapter がこのメソッドを実装するため、getItemCount() のオーバーライドを削除します。
  3. onBindViewHolder() のエラーを解消するには、item 変数を変更します。data を使用して item を取得する代わりに、ListAdapter が提供する getItem(position) メソッドを呼び出します。
val item = getItem(position)

ステップ 2: submitList() を使用してリストを最新の状態に保つ

コードでは、変更されたリストが利用可能になったときに ListAdapter に通知する必要があります。ListAdapter は、リストの新しいバージョンが利用可能であることを ListAdapter に通知する submitList() というメソッドを提供します。このメソッドが呼び出されると、ListAdapter は新しいリストと古いリストを比較し、追加、削除、移動、変更された項目を検出します。次に、ListAdapterRecyclerView で表示されるアイテムを更新します。

  1. SleepTrackerFragment.kt を開きます。
  2. onCreateView()sleepTrackerViewModel のオブザーバーで、削除した data 変数が参照されているエラーを見つけます。
  3. adapter.data = itadapter.submitList(it) の呼び出しに置き換えます。更新されたコードは次のとおりです。

sleepTrackerViewModel.nights.observe(viewLifecycleOwner, Observer {
   it?.let {
       adapter.submitList(it)
   }
})
  1. アプリを実行します。実行速度が速くなります。リストが小さい場合は、速度の向上を実感できないかもしれません。

このタスクでは、前の Codelab と同じ手法を使用してデータ バインディングを設定し、findViewById() の呼び出しを削除します。

ステップ 1: レイアウト ファイルにデータ バインディングを追加する

  1. [テキスト] タブで list_item_sleep_night.xml レイアウト ファイルを開きます。
  2. ConstraintLayout タグにカーソルを置き、Alt+Enter キー(Mac の場合は Option+Enter キー)を押します。インテンション メニュー(クイック フィックス メニュー)が開きます。
  3. [Convert to data binding layout] を選択します。これにより、レイアウトが <layout> でラップされ、その中に <data> タグが追加されます。
  4. 必要に応じて一番上までスクロールし、<data> タグ内で sleep という名前の変数を宣言します。
  5. typeSleepNightcom.example.android.trackmysleepquality.database.SleepNight の完全修飾名にします。完成した <data> タグは次のようになります。
   <data>
        <variable
            name="sleep"
            type="com.example.android.trackmysleepquality.database.SleepNight"/>
    </data>
  1. Binding オブジェクトの作成を強制するには、[Build] > [Clean Project] を選択してから、[Build] > [Rebuild Project] を選択します。(それでも問題が解決しない場合は、[File] > [Invalidate Caches / Restart] を選択します)。ListItemSleepNightBinding バインディング オブジェクトと関連するコードが、プロジェクトの生成されたファイルに追加されます。

ステップ 2: データ バインディングを使用してアイテム レイアウトを拡張する

  1. SleepNightAdapter.kt を開きます。
  2. ViewHolder クラスで from() メソッドを見つけます。
  3. view 変数の宣言を削除します。

削除するコード:

val view = layoutInflater
       .inflate(R.layout.list_item_sleep_night, parent, false)
  1. view 変数があった場所に、次に示すように、ListItemSleepNightBinding バインディング オブジェクトを拡張する binding という新しい変数を定義します。バインディング オブジェクトの必要なインポートを行います。
val binding =
ListItemSleepNightBinding.inflate(layoutInflater, parent, false)
  1. 関数の最後で、view を返すのではなく、binding を返します。
return ViewHolder(binding)
  1. エラーを解消するには、binding という単語にカーソルを合わせます。Alt+Enter(Mac の場合は Option+Enter)キーを押して、インテンション メニューを開きます。
  1. [Change parameter 'itemView' type of primary constructor of class 'ViewHolder' to 'ListItemSleepNightBinding'] を選択します。これにより、ViewHolder クラスのパラメータ タイプが更新されます。

  1. ViewHolder のクラス定義までスクロールして、シグネチャの変更を確認します。from() メソッドで itemViewbinding に変更したため、itemView のエラーが表示されます。

    ViewHolder クラス定義で、itemView のいずれかの出現箇所を右クリックし、[Refactor] > [Rename] を選択します。名前を「binding」に変更します。
  2. コンストラクタ パラメータ binding の前に val を付けて、プロパティにします。
  3. 親クラス RecyclerView.ViewHolder の呼び出しで、パラメータを binding から binding.root に変更します。View を渡す必要があります。binding.root はアイテム レイアウトのルート ConstraintLayout です。
  4. 完成したクラス宣言は次のようになります。
class ViewHolder private constructor(val binding: ListItemSleepNightBinding) : RecyclerView.ViewHolder(binding.root){

findViewById() の呼び出しに関するエラーも表示されます。このエラーは次の手順で修正します。

ステップ 3: findViewById() を置き換える

sleepLengthqualityqualityImage の各プロパティを更新して、findViewById() ではなく binding オブジェクトを使用できるようになりました。

  1. 以下に示すように、sleepLengthqualityStringqualityImage の初期化を変更して、binding オブジェクトのビューを使用します。これで、コードにエラーが表示されなくなります。
val sleepLength: TextView = binding.sleepLength
val quality: TextView = binding.qualityString
val qualityImage: ImageView = binding.qualityImage

バインディング オブジェクトが配置されているため、sleepLengthqualityqualityImage の各プロパティを定義する必要はなくなりました。DataBinding はルックアップをキャッシュに保存するため、これらのプロパティを宣言する必要はありません。

  1. sleepLengthqualityqualityImage プロパティ名を右クリックします。[Refactor > Inline] を選択するか、Control+Command+N(Mac の場合は Option+Command+N)を押します。
  2. アプリを実行します(エラーがある場合は、プロジェクトのクリーン再ビルドが必要になることがあります)。

このタスクでは、バインディング アダプターでデータ バインディングを使用し、ビューにデータを設定するようにアプリをアップグレードします。

前の Codelab では、Transformations クラスを使用して LiveData を取得し、テキスト ビューに表示する書式設定された文字列を生成しました。ただし、異なる型や複雑な型をバインドする必要がある場合は、データ バインディングでそれらの型を使用できるようにバインディング アダプタを提供できます。バインディング アダプターは、データを取得して、テキストや画像などのビューをバインドするためにデータ バインディングで使用できるものに変換するアダプターです。

3 つのバインディング アダプタ(高品質の画像用と、テキスト フィールドごとに 1 つ)を実装します。要するに、バインディング アダプターを宣言するには、アイテムとビューを受け取るメソッドを定義し、それに @BindingAdapter アノテーションを付けます。メソッドの本体で、変換を実装します。Kotlin では、データを受け取るビュークラスの拡張関数としてバインディング アダプタを作成できます。

ステップ 1: バインディング アダプタを作成する

この手順では複数のクラスをインポートする必要がありますが、個別に呼び出されることはありません。

  1. SleepNightAdapater.kt を開きます。
  2. ViewHolder クラス内で bind() メソッドを見つけ、このメソッドの機能を確認します。binding.sleepLengthbinding.qualitybinding.qualityImage の値を計算するコードを取得し、アダプタ内で使用します。(今のところ、コードはそのままにしておきます。後のステップで移動します)。
  3. sleeptracker パッケージで、BindingUtils.kt というファイルを作成して開きます。
  4. TextViewsetSleepDurationFormatted という拡張関数を宣言し、SleepNight を渡します。この関数は、睡眠時間を計算してフォーマットするためのアダプターになります。
fun TextView.setSleepDurationFormatted(item: SleepNight) {}
  1. setSleepDurationFormatted の本文で、ViewHolder.bind() の場合と同様に、データをビューにバインドします。convertDurationToFormatted() を呼び出し、TextViewtext を書式設定されたテキストに設定します。(これは TextView の拡張関数であるため、text プロパティに直接アクセスできます)。
text = convertDurationToFormatted(item.startTimeMilli, item.endTimeMilli, context.resources)
  1. このバインディング アダプターをデータ バインディングに伝えるには、関数に @BindingAdapter アノテーションを付けます。
  2. この関数は sleepDurationFormatted 属性のアダプターであるため、@BindingAdapter の引数として sleepDurationFormatted を渡します。
@BindingAdapter("sleepDurationFormatted")
  1. 2 番目のアダプタは、SleepNight オブジェクトの値に基づいて睡眠の質を設定します。TextViewsetSleepQualityString() という名前の拡張関数を作成し、SleepNight を渡します。
  2. 本文で、ViewHolder.bind() の場合と同様に、データをビューにバインドします。convertNumericQualityToString を呼び出して text を設定します。
  3. 関数に @BindingAdapter("sleepQualityString") アノテーションを付けます。
@BindingAdapter("sleepQualityString")
fun TextView.setSleepQualityString(item: SleepNight) {
   text = convertNumericQualityToString(item.sleepQuality, context.resources)
}
  1. 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 を更新する

  1. SleepNightAdapter.kt を開きます。
  2. bind() メソッド内のすべてを削除します。データ バインディングと新しいアダプターを使用して、この作業を行うことができるためです。
fun bind(item: SleepNight) {
}
  1. bind() 内で、item にスリープを割り当てます。これは、バインディング オブジェクトに新しい SleepNight を通知する必要があるためです。
binding.sleep = item
  1. その行の下に binding.executePendingBindings() を追加します。この呼び出しは、保留中のバインディングをすぐに実行するようにデータ バインディングに要求する最適化です。RecyclerView でバインディング アダプターを使用する場合は、常に executePendingBindings() を呼び出すことをおすすめします。ビューのサイズ設定をわずかに高速化できるためです。
 binding.executePendingBindings()

ステップ 3: XML レイアウトにバインディングを追加する

  1. list_item_sleep_night.xml を開きます。
  2. ImageView で、画像を設定するバインディング アダプターと同じ名前の app プロパティを追加します。以下に示すように、sleep 変数を渡します。

    このプロパティは、アダプターを介してビューとバインディング オブジェクト間の接続を作成します。sleepImage が参照されるたびに、アダプターは SleepNight からデータを適応させます。
app:sleepImage="@{sleep}"
  1. sleep_lengthquality_string のテキスト ビューについても同様に処理します。sleepDurationFormatted または sleepQualityString が参照されるたびに、アダプターは SleepNight からデータを適応させます。
app:sleepDurationFormatted="@{sleep}"
app:sleepQualityString="@{sleep}"
  1. アプリを実行します。以前とまったく同じように動作します。バインディング アダプタは、データの変更に応じてビューのフォーマットと更新を行うすべての処理を担当するため、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 デベロッパー ドキュメント:

その他のリソース:

このセクションでは、インストラクター主導のコースの一環として、この Codelab に取り組んでいる生徒向けに考えられる宿題をいくつか示します。インストラクターは、以下のようなことを行えます。

  • 必要に応じて宿題を与える
  • 宿題の提出方法を生徒に伝える
  • 宿題を採点する

インストラクターは、これらの提案を必要なだけ使用し、必要に応じて他の宿題も自由に与えることができます。

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

以下の質問に回答してください

問題 1

DiffUtil を使用するのに必要なのは、次のどれですか。該当するものをすべて選択してください。

ItemCallBack クラスを拡張します。

areItemsTheSame() をオーバーライドします。

areContentsTheSame() をオーバーライドします。

▢ データ バインディングを使用してアイテム間の違いをトラッキングする。

質問 2

バインディング アダプターの説明として正しいものは次のうちどれですか。

▢ バインディング アダプターは @BindingAdapter アノテーションが付いた関数です。

▢ バインディング アダプタを使用すると、データ形式とビューホルダーを分離できます。

▢ バインディング アダプターを使用するには RecyclerViewAdapter を使用する必要があります。

▢ バインディング アダプターは、複雑なデータを変換する必要がある場合に適したソリューションである。

質問 3

バインディング アダプターの代わりに Transformations を使用することを検討すべきなのは、どのような場合ですか?該当するものをすべて選択してください。

▢ データがシンプルである。

▢ 文字列をフォーマットしている。

▢ リストが長すぎる。

ViewHolder に含まれるビューが 1 つのみである。

次のレッスンに進む: 7.3: RecyclerView を使用した GridLayout