Эта практическая работа входит в курс «Основы Android Kotlin». Вы получите максимальную пользу от этого курса, если будете выполнять практические работы последовательно. Все практические работы курса перечислены на целевой странице практической работы «Основы Android Kotlin» .
Введение
В предыдущей лабораторной работе вы обновили приложение TrackMySleepQuality для отображения данных о качестве сна в RecyclerView
. Методы, которые вы освоили при создании своего первого RecyclerView
, достаточны для большинства RecyclerViews
, отображающих простые и не слишком большие списки. Однако существует ряд методов, которые повышают эффективность RecyclerView
для больших списков и упрощают поддержку и расширение кода для сложных списков и сеток.
В этой практической работе вы доработаете приложение для отслеживания сна из предыдущей. Вы узнаете более эффективный способ обновления списка данных о сне и научитесь использовать привязку данных с RecyclerView
. (Если у вас нет приложения из предыдущей практической работы, вы можете скачать начальный код для этой практической работы.)
Что вам уже следует знать
- Создание базового пользовательского интерфейса с использованием активности, фрагментов и представлений.
- Навигация между фрагментами и использование
safeArgs
для передачи данных между фрагментами. - Просмотр моделей, просмотр фабрик моделей, преобразований, а также
LiveData
и их наблюдателей. - Как создать базу данных
Room
, создать DAO и определить сущности. - Как использовать сопрограммы для баз данных и других длительных задач.
- Как реализовать базовый
RecyclerView
сAdapter
,ViewHolder
и макетом элемента.
Чему вы научитесь
- Как использовать
DiffUtil
для эффективного обновления списка, отображаемогоRecyclerView
. - Как использовать привязку данных с
RecyclerView
. - Как использовать адаптеры привязки для преобразования данных.
Что ты будешь делать?
- Используйте приложение TrackMySleepQuality из предыдущей лабораторной работы этой серии.
- Обновите
SleepNightAdapter
для эффективного обновления списка с помощьюDiffUtil
. - Реализуйте привязку данных для
RecyclerView
, используя адаптеры привязки для преобразования данных.
Приложение для отслеживания сна имеет два экрана, представленных фрагментами, как показано на рисунке ниже.
На первом экране, показанном слева, расположены кнопки для запуска и остановки отслеживания. На экране отображаются некоторые данные о сне пользователя. Кнопка «Очистить» безвозвратно удаляет все данные, собранные приложением. Второй экран, показанный справа, предназначен для выбора оценки качества сна.
Это приложение спроектировано для использования контроллера пользовательского интерфейса, ViewModel
и LiveData
, а также базы данных Room
для сохранения данных о сне.
Данные о сне отображаются в RecyclerView
. В этой лабораторной работе вы создадите DiffUtil
и модуль привязки данных для RecyclerView
. После выполнения этой лабораторной работы ваше приложение будет выглядеть точно так же, но станет более эффективным и простым в масштабировании и обслуживании.
Вы можете продолжить использование приложения SleepTracker из предыдущей лабораторной работы или загрузить приложение RecyclerViewDiffUtilDataBinding-Starter с GitHub.
- При необходимости загрузите приложение RecyclerViewDiffUtilDataBinding-Starter с GitHub и откройте проект в Android Studio.
- Запустите приложение.
- Откройте файл
SleepNightAdapter.kt
. - Изучите код, чтобы ознакомиться со структурой приложения. На схеме ниже представлен обзор использования
RecyclerView
с шаблоном адаптера для отображения данных о сне пользователю.
- На основе данных, введенных пользователем, приложение создает список объектов
SleepNight
. Каждый объектSleepNight
представляет одну ночь сна, ее продолжительность и качество. -
SleepNightAdapter
адаптирует список объектовSleepNight
во что-то, чтоRecyclerView
может использовать и отображать. - Адаптер
SleepNightAdapter
создаетViewHolders
, которые содержат представления, данные и метаинформацию для представления recycler для отображения данных. -
RecyclerView
используетSleepNightAdapter
для определения количества отображаемых элементов (getItemCount()
).RecyclerView
используетonCreateViewHolder()
иonBindViewHolder()
для привязки держателей представлений к отображаемым данным.
Метод notifyDataSetChanged() неэффективен
Чтобы сообщить RecyclerView
, что элемент в списке изменился и его необходимо обновить, текущий код вызывает notifyDataSetChanged()
в SleepNightAdapter
, как показано ниже.
var data = listOf<SleepNight>()
set(value) {
field = value
notifyDataSetChanged()
}
Однако notifyDataSetChanged()
сообщает RecyclerView
, что весь список потенциально недействителен. В результате RecyclerView
перепривязывает и перерисовывает каждый элемент списка, включая те, которые не видны на экране. Это лишнее. Для больших или сложных списков этот процесс может занять достаточно много времени, из-за чего изображение на экране будет мерцать или зависать при прокрутке списка пользователем.
Чтобы решить эту проблему, вы можете сообщить RecyclerView
, что именно изменилось. После этого RecyclerView
сможет обновлять только те представления на экране, которые изменились.
RecyclerView
имеет богатый API для обновления одного элемента. Вы можете использовать notifyItemChanged()
чтобы сообщить RecyclerView
об изменении элемента, а также использовать аналогичные функции для добавления, удаления или перемещения элементов. Всё это можно сделать вручную, но эта задача будет нетривиальной и потребует написания значительного объёма кода.
К счастью, есть способ лучше.
DiffUtil эффективен и выполняет сложную работу за вас.
RecyclerView
есть класс DiffUtil
, предназначенный для вычисления разницы между двумя списками. DiffUtil
берёт старый и новый списки и определяет разницу. Он находит элементы, которые были добавлены, удалены или изменены. Затем он использует алгоритм, называемый алгоритмом разностей Юджина У. Майерса, чтобы определить минимальное количество изменений, которые нужно внести в старый список для создания нового списка.
Как только DiffUtil
выяснит, что изменилось, RecyclerView
сможет использовать эту информацию для обновления только тех элементов, которые были изменены, добавлены, удалены или перемещены, что гораздо эффективнее, чем переделывать весь список.
В этой задаче вы обновите SleepNightAdapter
, чтобы использовать DiffUtil
для оптимизации RecyclerView
с учетом изменений данных.
Шаг 1: Реализация SleepNightDiffCallback
Чтобы использовать функциональность класса DiffUtil
, расширьте DiffUtil.ItemCallback
.
- Откройте
SleepNightAdapter.kt
. - Ниже полного определения класса
SleepNightAdapter
создайте новый класс верхнего уровняSleepNightDiffCallback
, расширяющийDiffUtil.ItemCallback
. ПередайтеSleepNight
как универсальный параметр.
class SleepNightDiffCallback : DiffUtil.ItemCallback<SleepNight>() {
}
- Установите курсор на имя класса
SleepNightDiffCallback
. - Нажмите
Alt+Enter
(Option+Enter
на Mac) и выберите Реализовать элементы . - В открывшемся диалоговом окне щелкните левой кнопкой мыши, удерживая клавишу Shift, чтобы выбрать методы
areItemsTheSame()
иareContentsTheSame()
, затем нажмите кнопку OK .
Это создаёт заглушки внутриSleepNightDiffCallback
для двух методов, как показано ниже.DiffUtil
использует эти два метода, чтобы определить, как изменились список и его элементы.
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
кодом, который проверяет, одинаковы ли два переданных элемента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
предоставляет класс адаптера ListAdapter
, который помогает создать адаптер RecyclerView
на основе списка.
ListAdapter
отслеживает список и уведомляет адаптер об его обновлении.
Шаг 1: Измените свой адаптер, чтобы расширить ListAdapter
- В файле
SleepNightAdapter.kt
измените сигнатуру классаSleepNightAdapter
, чтобы расширитьListAdapter
. - При появлении запроса импортируйте
androidx.recyclerview.widget.ListAdapter
. - Добавьте
SleepNight
в качестве первого аргументаListAdapter
передSleepNightAdapter.ViewHolder
. - Добавьте
SleepNightDiffCallback()
в качестве параметра конструктора.ListAdapter
будет использовать его для определения изменений в списке. Готовая сигнатура классаSleepNightAdapter
должна выглядеть так, как показано ниже.
class SleepNightAdapter : ListAdapter<SleepNight, SleepNightAdapter.ViewHolder>(SleepNightDiffCallback()) {
- Внутри класса
SleepNightAdapter
удалите полеdata
, включая сеттер. Оно вам больше не понадобится, посколькуListAdapter
отслеживает список автоматически. - Удалите переопределение
getItemCount()
, посколькуListAdapter
реализует этот метод за вас. - Чтобы избавиться от ошибки в
onBindViewHolder()
, измените переменнуюitem
. Вместо использованияdata
для полученияitem
вызовите методgetItem(position)
предоставляемыйListAdapter
.
val item = getItem(position)
Шаг 2: Используйте метод submitList() для обновления списка.
Ваш код должен сообщать ListAdapter
о доступности изменённого списка. ListAdapter
предоставляет метод submitList()
который сообщает ListAdapter
о доступности новой версии списка. При вызове этого метода ListAdapter
сравнивает новый список со старым и обнаруживает элементы, которые были добавлены, удалены, перемещены или изменены. Затем ListAdapter
обновляет элементы, отображаемые RecyclerView
.
- Откройте
SleepTrackerFragment.kt
. - В
onCreateView()
, в наблюдателеsleepTrackerViewModel
, найдите ошибку, в которой ссылается переменнаяdata
, которую вы удалили. - Замените
adapter.data = it
вызовомadapter.submitList(it)
. Обновлённый код показан ниже.
sleepTrackerViewModel.nights.observe(viewLifecycleOwner, Observer {
it?.let {
adapter.submitList(it)
}
})
- Запустите приложение. Оно работает быстрее, возможно, незаметно, если список небольшой.
В этом задании вы используете ту же технику, что и в предыдущих практикумах, для настройки привязки данных и исключаете вызовы findViewById()
.
Шаг 1: Добавьте привязку данных к файлу макета
- Откройте файл макета
list_item_sleep_night.xml
на вкладке Текст . - Установите курсор на тег
ConstraintLayout
и нажмитеAlt+Enter
(Option+Enter
(на Mac) Откроется меню намерений (меню «быстрого исправления»). - Выберите «Преобразовать в макет привязки данных» . Макет будет упакован в тег
<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
, определите новую переменную с именемbinding
, которая расширяет объект привязкиListItemSleepNightBinding
, как показано ниже. Выполните необходимый импорт объекта привязки.
val binding =
ListItemSleepNightBinding.inflate(layoutInflater, parent, false)
- В конце функции вместо возврата
view
вернитеbinding
.
return ViewHolder(binding)
- Чтобы устранить ошибку, наведите курсор на слово
binding
. НажмитеAlt+Enter
(Option+Enter
на Mac), чтобы открыть меню намерений.
- Выберите «Изменить тип параметра itemView основного конструктора класса ViewHolder» на ListItemSleepNightBinding» . Это обновит тип параметра класса
ViewHolder
.
- Прокрутите страницу вверх до определения класса
ViewHolder
, чтобы увидеть изменение в сигнатуре. Вы видите ошибку дляitemView
, поскольку вы изменилиitemView
наbinding
в методеfrom()
.
В определении классаViewHolder
щёлкните правой кнопкой мыши по одному из вхожденийitemView
и выберите «Рефакторинг» > «Переименовать» . Измените имя наbinding
. - Добавьте префикс
val
binding
параметра конструктора, чтобы сделать его свойством. - В вызове родительского класса
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
, чтобы использовать объект binding
вместо findViewById()
.
- Измените инициализацию
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
. Выберите «Рефакторинг» > «Встроить » или нажмитеControl+Command+N
(Option+Command+N
на Mac). - Запустите приложение. (Возможно, вам придется очистить и пересобрать проект, если в нем есть ошибки.)
В этой задаче вы обновите свое приложение, чтобы использовать привязку данных с адаптерами привязки для установки данных в ваших представлениях.
В предыдущей лабораторной работе вы использовали класс Transformations
для работы LiveData
и генерации форматированных строк для отображения в текстовых представлениях. Однако, если вам нужно привязать различные типы данных или сложные типы, вы можете предоставить адаптеры привязки, которые помогут привязке данных использовать эти типы. Адаптеры привязки — это адаптеры, которые принимают ваши данные и преобразуют их в нечто, что можно использовать при привязке данных для привязки представления, например, текст или изображение.
Вам предстоит реализовать три адаптера привязки: один для изображения качества и по одному для каждого текстового поля. Подводя итог, чтобы объявить адаптер привязки, вы определяете метод, принимающий элемент и представление, и аннотируете его @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()
, а затем установите форматированныйtext
в качестве значения дляTextView
. (Поскольку это функция расширенияTextView
, вы можете напрямую обращаться к свойствуtext
.)
text = convertDurationToFormatted(item.startTimeMilli, item.endTimeMilli, context.resources)
- Чтобы сообщить привязке данных об этом адаптере привязки, аннотируйте функцию с помощью
@BindingAdapter
. - Эта функция является адаптером для атрибута
sleepDurationFormatted
, поэтому передайтеsleepDurationFormatted
в качестве аргумента@BindingAdapter
.
@BindingAdapter("sleepDurationFormatted")
- Второй адаптер устанавливает качество сна на основе значения объекта
SleepNight
. Создайте функцию расширенияsetSleepQualityString()
дляTextView
и передайте ейSleepNight
. - В теле привяжите данные к представлению, как это было сделано в
ViewHolder.bind()
. ВызовитеconvertNumericQualityToString
и задайтеtext
. - Добавьте к функции аннотацию
@BindingAdapter("sleepQualityString")
.
@BindingAdapter("sleepQualityString")
fun TextView.setSleepQualityString(item: SleepNight) {
text = convertNumericQualityToString(item.sleepQuality, context.resources)
}
- Третий адаптер привязки устанавливает изображение в поле представления. Создайте функцию расширения для
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()
назначьте sleepitem
, поскольку вам нужно сообщить объекту привязки о вашем новомSleepNight
.
binding.sleep = item
- Ниже этой строки добавьте
binding.executePendingBindings()
. Этот вызов представляет собой оптимизацию, которая требует от привязки данных немедленно выполнить все ожидающие привязки. Всегда рекомендуется вызыватьexecutePendingBindings()
при использовании адаптеров привязки вRecyclerView
, поскольку это может немного ускорить изменение размера представлений.
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
. Все эти шаблоны работают, и у каждого есть свои преимущества. Выбор зависит от того, что именно вы разрабатываете.
Поздравляем! Вы на верном пути к освоению RecyclerView
на Android.
Проект Android Studio: RecyclerViewDiffUtilDataBinding .
DiffUtil
:
-
RecyclerView
имеет классDiffUtil
, который предназначен для вычисления разницы между двумя списками. - У
DiffUtil
есть классItemCallBack
, который вы расширяете, чтобы выяснить разницу между двумя списками. - В классе
ItemCallback
необходимо переопределить методыareItemsTheSame()
иareContentsTheSame()
.
ListAdapter
:
- Чтобы получить возможность управлять списками бесплатно, вы можете использовать класс
ListAdapter
вместоRecyclerView.Adapter
. Однако при использованииListAdapter
вам придётся написать собственный адаптер для других макетов, поэтому в этой лабораторной работе показано, как это сделать. - Чтобы открыть меню намерений в Android Studio, наведите курсор на любой элемент кода и нажмите
Alt+Enter
(Option+Enter
на Mac). Это меню особенно полезно для рефакторинга кода и создания заглушек для реализации методов. Меню контекстно-зависимо, поэтому для получения нужного меню необходимо точно установить курсор.
Привязка данных:
- Используйте привязку данных в макете элемента для привязки данных к представлениям.
Адаптеры для привязки:
- Ранее вы использовали
Transformations
для создания строк из данных. Если вам нужно связать данные разных или сложных типов, предоставьте адаптеры связывания, которые помогут механизму связывания данных использовать их. - Чтобы объявить адаптер привязки, определите метод, принимающий элемент и представление, и аннотируйте его
@BindingAdapter
. В Kotlin адаптер привязки можно написать как функцию расширения дляView
. Передайте имя свойства, которое адаптирует адаптер. Например:
@BindingAdapter("sleepDurationFormatted")
- В XML-макете задайте свойство
app
с тем же именем, что и у адаптера привязки. Передайте переменную с данными. Например:
.app:sleepDurationFormatted="@{sleep}"
Курсы Udacity:
Документация для разработчиков Android:
- Создайте список с помощью RecyclerView
-
RecyclerView
-
DiffUtil
- Библиотека привязки данных
- Адаптеры для привязки
-
notifyDataSetChanged()
-
Transformations
Другие ресурсы:
В этом разделе перечислены возможные домашние задания для студентов, работающих над этой лабораторной работой в рамках курса, проводимого преподавателем. Преподаватель должен выполнить следующие действия:
- При необходимости задавайте домашнее задание.
- Объясните учащимся, как следует сдавать домашние задания.
- Оцените домашние задания.
Преподаватели могут использовать эти предложения так часто или редко, как пожелают, и могут свободно задавать любые другие домашние задания, которые они сочтут подходящими.
Если вы работаете с этой лабораторной работой самостоятельно, можете использовать эти домашние задания для проверки своих знаний.
Ответьте на эти вопросы
Вопрос 1
Что из перечисленного необходимо для использования DiffUtil
? Выберите все подходящие варианты.
▢ Расширьте класс ItemCallBack
.
▢ Переопределить areItemsTheSame()
.
▢ Переопределить areContentsTheSame()
.
▢ Используйте привязку данных для отслеживания различий между элементами.
Вопрос 2
Какие из следующих утверждений относительно адаптеров привязки верны?
▢ Адаптер привязки — это функция, аннотированная @BindingAdapter
.
▢ Использование адаптера привязки позволяет отделить форматирование данных от держателя представления.
▢ Если вы хотите использовать адаптеры привязки, необходимо использовать RecyclerViewAdapter
.
▢ Адаптеры привязки являются хорошим решением, когда вам нужно преобразовать сложные данные.
Вопрос 3
Когда следует использовать Transformations
вместо адаптера привязки? Выберите все подходящие варианты.
▢ Ваши данные просты.
▢ Вы форматируете строку.
▢ Ваш список очень длинный.
▢ Ваш ViewHolder
содержит только одно представление.
Начните следующий урок: