この 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 アプリに基づいて構築します。
DiffUtil
を使用してリストを効率的に更新するようにSleepNightAdapter
を更新します。- バインディング アダプターを使用して
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()
を使用して、表示するデータにバインドされたビューホルダーを取得します。
notificationDataSetChanged() メソッドが非効率的
リスト内のアイテムが変更されて更新する必要があることを 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
(Option+Enter
Mac の場合)そして メンバーを実装するを選択します。- 表示されるダイアログで、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
の前に、SleepNight
を最初の引数としてListAdapter
に追加します。- パラメータとして
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: レイアウト ファイルにデータ バインディングを追加する
- [Text] タブで
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] を選択します。(問題が解決しない場合は、[ファイル] > [キャッシュを無効にする / 再起動] を選択します)。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 primary コンストラクタ of class 'ViewHolder' to 'ListItemSleepNightBinding'] を選択します。これにより、
ViewHolder
クラスのパラメータの型が更新されます。
ViewHolder
のクラス定義までスクロールすると、署名の変更が表示されます。from()
メソッドでitemView
をbinding
に変更したため、itemView
のエラーが表示されます。ViewHolder
クラスの定義で、itemView
のオカレンスの 1 つを右クリックし、[リファクタリング] > [名前を変更] を選択します。名前を「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() を置き換える
findViewById()
ではなく binding
オブジェクトを使用するように、sleepLength
、quality
、qualityImage
プロパティを更新できるようになりました。
- 以下に示すように、
binding
オブジェクトのビューを使用するように、sleepLength
、qualityString
、qualityImage
の初期化を変更します。その後は、コード内にエラーが表示されなくなります。
val sleepLength: TextView = binding.sleepLength
val quality: TextView = binding.qualityString
val qualityImage: ImageView = binding.qualityImage
バインディング オブジェクトを使用すると、sleepLength
、quality
、qualityImage
の各プロパティを定義する必要がなくなります。DataBinding
はルックアップをキャッシュに保存するため、これらのプロパティを宣言する必要はありません。
sleepLength
、quality
、qualityImage
の各プロパティ名を右クリックします。[インラインでリファクタリング] を選択するか、Control+Command+N
(Mac ではOption+Command+N
)を押します。- アプリを実行します(エラーが発生した場合は、プロジェクトをクリーンアップして再ビルドする必要があります)。
このタスクでは、データ バインディングとバインディング アダプターを使用するようにアプリをアップグレードし、ビューにデータを設定します。
前の Codelab では、Transformations
クラスを使用して LiveData
を取得し、テキストビューに表示する書式付き文字列を生成しました。ただし、異なる型や複雑な型をバインドする必要がある場合は、バインディング アダプターを提供して、データ バインディングでそれらの型を使用できるようにします。バインディング アダプターは、データを取り込んでビューやテキスト、画像などのビューをバインドできるものに適応させるアダプターです。
高品質の画像用とテキスト フィールド用の 3 つのバインディング アダプターを実装します。まとめると、バインディング アダプターを宣言するには、アイテムとビューを受け取るメソッドを定義し、@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
属性のアダプターであるため、sleepDurationFormatted
を引数として@BindingAdapter
に渡します。
@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
にはItemCallBack
というクラスがあり、2 つのリストの違いを理解するために拡張されています。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 でリストを作成する
RecyclerView
DiffUtil
- データ バインディング ライブラリ
- バインディング アダプター
notifyDataSetChanged()
Transformations
その他のリソース:
このセクションでは、インストラクターが主導するコースの一環として、この Codelab に取り組む生徒の課題について説明します。教師は以下のことを行えます。
- 必要に応じて課題を割り当てます。
- 宿題の提出方法を生徒に伝える。
- 宿題を採点します。
教師はこれらの提案を少しだけ使うことができます。また、他の課題は自由に割り当ててください。
この Codelab にご自分で取り組む場合は、これらの課題を使用して知識をテストしてください。
次の質問に答えてください。
問題 1
DiffUtil
を使用するために必要なものは次のうちどれですか。該当するものをすべて選択してください。
▢ ItemCallBack
クラスを拡張する
▢ areItemsTheSame()
をオーバーライドします。
▢ areContentsTheSame()
をオーバーライドします。
▢ データ バインディングを使って項目間の差異を追跡します。
質問 2
バインディング アダプターの説明として正しいものは次のうちどれですか。
▢ バインディング アダプターは @BindingAdapter
アノテーション付きの関数です。
▢ バインディング アダプターを使用すると、データ形式をビューホルダーから分離できます。
▢ バインディング アダプターを使用するには、RecyclerViewAdapter
を使用する必要があります。
▢ バインディング アダプターは複雑なデータを変換する必要がある場合に適したソリューションです。
質問 3
バインディング アダプターの代わりに Transformations
を使用する必要があるのはどのような場合ですか?該当するものをすべて選択してください。
▢ データはシンプルです。
▢ 文字列を書式設定しています。
▢ リストが長すぎる。
▢ ViewHolder
にはビューが 1 つしかありません。
次のレッスンを開始する: