Эта практическая работа входит в курс «Основы Android Kotlin». Вы получите максимальную пользу от этого курса, если будете выполнять практические работы последовательно. Все практические работы курса перечислены на целевой странице практической работы «Основы Android Kotlin» .
Введение
В этой практической работе вы научитесь использовать RecyclerView для отображения списков элементов. Развивая приложение для отслеживания сна из предыдущей серии практических занятий, вы изучите более эффективный и универсальный способ отображения данных с помощью RecyclerView с рекомендуемой архитектурой.
Что вам уже следует знать
Вам должно быть знакомо:
- Создание базового пользовательского интерфейса (UI) с использованием активности, фрагментов и представлений.
- Навигация между фрагментами и использование
safeArgsдля передачи данных между фрагментами. - Использование моделей представлений, фабрик моделей представлений, преобразований, а также
LiveDataи их наблюдателей. - Создание базы данных
Room, создание DAO и определение сущностей. - Использование сопрограмм для задач базы данных и других длительных задач.
Чему вы научитесь
- Как использовать
RecyclerViewсAdapterиViewHolderдля отображения списка элементов.
Что ты будешь делать?
- Измените приложение TrackMySleepQuality из предыдущего урока так, чтобы оно использовало
RecyclerViewдля отображения данных о качестве сна.
В этой лабораторной работе вы создадите компонент RecyclerView приложения, отслеживающего качество сна. Приложение использует базу данных Room для хранения данных о сне с течением времени.
Стартовое приложение для отслеживания сна имеет два экрана, представленных фрагментами, как показано на рисунке ниже.

На первом экране, показанном слева, расположены кнопки для запуска и остановки отслеживания. Здесь также отображаются все данные о сне пользователя. Кнопка «Очистить» безвозвратно удаляет все данные, собранные приложением. Второй экран, показанный справа, предназначен для выбора оценки качества сна.
Это приложение использует упрощённую архитектуру с контроллером пользовательского интерфейса, ViewModel и LiveData . Приложение также использует базу данных Room для сохранения данных о сне.

Список ночей сна, отображаемый на первом экране, функционален, но не слишком привлекателен. Приложение использует сложный форматировщик для создания текстовых строк для текстового представления и числовых значений для оценки качества. Кроме того, этот дизайн не масштабируется. После исправления всех этих проблем в этой практической работе финальное приложение будет иметь ту же функциональность, а главный экран будет выглядеть так:

Отображение списка или таблицы данных — одна из самых распространённых задач пользовательского интерфейса в Android. Списки могут быть как простыми, так и очень сложными. Список текстовых представлений может отображать простые данные, например, список покупок. Сложный список, например, аннотированный список мест отдыха, может отображать множество данных внутри прокручиваемой сетки с заголовками.
Для поддержки всех этих вариантов использования Android предоставляет виджет RecyclerView .

Наибольшее преимущество RecyclerView заключается в том, что он очень эффективен для больших списков:
- По умолчанию
RecyclerViewобрабатывает и отображает только те элементы, которые в данный момент видны на экране. Например, если ваш список содержит тысячу элементов, но видны только 10,RecyclerViewвыполняет ровно столько работы, сколько необходимо для отображения 10 элементов на экране. При прокрутке страницы пользователемRecyclerViewопределяет, какие новые элементы должны быть на экране, и выполняет ровно столько работы, сколько необходимо для их отображения. - Когда элемент прокручивается за пределы экрана, его представления перезаписываются. Это означает, что элемент заполняется новым контентом, который прокручивается на экран. Такое поведение
RecyclerViewзначительно экономит время обработки и обеспечивает плавную прокрутку списков. - При изменении элемента
RecyclerViewможет обновить только один элемент, не перерисовывая весь список. Это значительно повышает эффективность при отображении сложных списков!
В последовательности, показанной ниже, видно, что одно представление заполнено данными, ABC . После того, как это представление прокручивается за пределы экрана, RecyclerView повторно использует его для новых данных, XYZ .

Шаблон адаптера
Если вы когда-либо путешествовали между странами, где используются разные розетки, вы, вероятно, знаете, как подключать устройства к розеткам с помощью адаптера. Адаптер позволяет преобразовать один тип вилки в другой, то есть фактически преобразовать один интерфейс в другой.
Шаблон «адаптер» в программной инженерии помогает объекту работать с другим API. RecyclerView использует адаптер для преобразования данных приложения в то, что RecyclerView может отобразить, не изменяя способ хранения и обработки данных приложением. Для приложения для отслеживания сна вы создаете адаптер, который адаптирует данные из базы данных Room к тому, что RecyclerView умеет отображать, не изменяя ViewModel .
Реализация RecyclerView

Для отображения данных в RecyclerView вам понадобятся следующие компоненты:
- Данные для отображения.
- Экземпляр
RecyclerView, определенный в файле макета, который будет действовать как контейнер для представлений. - Макет для одного элемента данных.
Если все элементы списка выглядят одинаково, вы можете использовать для них один и тот же макет, но это не обязательно. Макет элемента должен быть создан отдельно от макета фрагмента, чтобы можно было создавать и заполнять данными только одно представление элемента за раз. - Менеджер по макетированию.
Менеджер макетов управляет организацией (макетом) компонентов пользовательского интерфейса в представлении. - Обладатель мнения.
Держатель представления расширяет классViewHolder. Он содержит информацию о представлении для отображения одного элемента из макета. Держатели представления также добавляют информацию, которуюRecyclerViewиспользует для эффективного перемещения представлений по экрану. - Адаптер.
Адаптер подключает ваши данные кRecyclerView. Он адаптирует данные для отображения вViewHolder.RecyclerViewиспользует адаптер для определения способа отображения данных на экране.
В этой задаче вы добавите RecyclerView в свой файл макета и настроите Adapter для предоставления данных о сне в RecyclerView .
Шаг 1: Добавьте RecyclerView с LayoutManager
На этом этапе вы заменяете ScrollView на RecyclerView в файле fragment_sleep_tracker.xml .
- Загрузите приложение RecyclerViewFundamentals-Starter с GitHub.
- Создайте и запустите приложение. Обратите внимание, что данные отображаются в виде простого текста.
- Откройте файл макета
fragment_sleep_tracker.xmlна вкладке «Дизайн» в Android Studio. - На панели «Дерево компонентов» удалите
ScrollView. Это действие также удалитTextView, находящийся внутриScrollView. - На панели «Палитра» прокрутите список типов компонентов слева, чтобы найти «Контейнеры» , затем выберите его.
- Перетащите
RecyclerViewиз панели «Палитра» на панель «Дерево компонентов» . ПоместитеRecyclerViewвConstraintLayout.

- Если откроется диалоговое окно с вопросом о добавлении зависимости, нажмите «ОК» , чтобы Android Studio добавила зависимость
recyclerviewв ваш Gradle-файл. Это может занять несколько секунд, после чего ваше приложение будет синхронизировано.

- Откройте файл модуля
build.gradle, прокрутите до конца и обратите внимание на новую зависимость, которая выглядит примерно так, как показано на коде ниже:
implementation 'androidx.recyclerview:recyclerview:1.0.0'
- Вернитесь к
fragment_sleep_tracker.xml. - На вкладке «Текст» найдите код
RecyclerView, показанный ниже:
<androidx.recyclerview.widget.RecyclerView
android:layout_width="match_parent"
android:layout_height="match_parent" />- Присвойте
RecyclerViewidsleep_list.
android:id="@+id/sleep_list"- Разместите
RecyclerViewтак, чтобы он занимал оставшуюся часть экрана внутриConstraintLayout. Для этого ограничьте верхнюю частьRecyclerViewкнопкой «Пуск» , нижнюю — кнопкой « Очистить », а каждую сторону — родительским элементом. Установите ширину и высоту макета равными 0 dp в редакторе макетов или в XML, используя следующий код:
android:layout_width="0dp"
android:layout_height="0dp"
app:layout_constraintBottom_toTopOf="@+id/clear_button"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/stop_button"- Добавьте менеджер макета в XML-файл
RecyclerView. КаждомуRecyclerViewнужен менеджер макета, который определяет расположение элементов в списке. В Android естьLinearLayoutManager, который по умолчанию размещает элементы в вертикальном списке строк полной ширины.
app:layoutManager="androidx.recyclerview.widget.LinearLayoutManager"- Перейдите на вкладку «Конструктор» и обратите внимание, что добавленные ограничения привели к тому, что
RecyclerViewрасширился, чтобы заполнить все доступное пространство.

Шаг 2: Создайте макет элемента списка и держатель текстового представления.
RecyclerView — это всего лишь контейнер. На этом этапе вы создаёте макет и инфраструктуру для элементов, отображаемых внутри RecyclerView .
Чтобы как можно быстрее получить работающий RecyclerView , сначала используется простой элемент списка, отображающий только качество сна в виде числа. Для этого необходим контейнер представления TextItemViewHolder . Также необходим контейнер представления TextView для данных. (На следующем этапе вы узнаете больше о контейнерах представления и о том, как организовать все данные о сне.)
- Создайте файл макета с именем
text_item_view.xml. Неважно, какой элемент вы используете в качестве корневого, так как вы замените код шаблона. - В
text_item_view.xmlудалите весь указанный код. - Добавьте
TextViewс отступами16dpв начале и конце и размером текста24sp. Ширина должна соответствовать родительскому элементу, а высота должна обтекать содержимое. Поскольку это представление отображается внутриRecyclerView, вам не нужно помещать его внутрьViewGroup.
<?xml version="1.0" encoding="utf-8"?>
<TextView xmlns:android="http://schemas.android.com/apk/res/android"
android:textSize="24sp"
android:paddingStart="16dp"
android:paddingEnd="16dp"
android:layout_width="match_parent"
android:layout_height="wrap_content" />- Откройте
Util.ktПрокрутите до конца и добавьте показанное ниже определение, которое создаёт классTextItemViewHolder. Добавьте код в конец файла, после последней закрывающей скобки. Код добавляется вUtil.ktпоскольку этот контейнер представления временный, и вы замените его позже.
class TextItemViewHolder(val textView: TextView): RecyclerView.ViewHolder(textView)- При появлении соответствующего запроса импортируйте
android.widget.TextViewиandroidx.recyclerview.widget.RecyclerView.
Шаг 3: Создайте SleepNightAdapter
Основная задача при реализации RecyclerView — создание адаптера. У вас есть простой держатель представления для представления элементов и макет для каждого элемента. Теперь вы можете создать адаптер. Адаптер создаёт держатель представления и заполняет его данными для отображения RecyclerView .
- В пакете
sleeptrackerсоздайте новый класс Kotlin с именемSleepNightAdapter. - Сделайте класс
SleepNightAdapterрасширениемRecyclerView.Adapter. Класс называетсяSleepNightAdapter, поскольку он адаптирует объектSleepNightк типу, который может использоватьRecyclerView. Адаптеру необходимо знать, какой держатель представления использовать, поэтому передайтеTextItemViewHolder. Импортируйте необходимые компоненты при появлении запроса. В этом случае вы увидите ошибку, поскольку существуют обязательные методы для реализации.
class SleepNightAdapter: RecyclerView.Adapter<TextItemViewHolder>() {}- На верхнем уровне
SleepNightAdapterсоздайте переменнуюlistOfSleepNightдля хранения данных.
var data = listOf<SleepNight>()- В
SleepNightAdapterпереопределитеgetItemCount(), чтобы он возвращал размер списка ночей сна вdata.RecyclerViewнеобходимо знать, сколько элементов есть в адаптере, чтобы отобразить их, и он делает это, вызываяgetItemCount().
override fun getItemCount() = data.size- В
SleepNightAdapterпереопределите функциюonBindViewHolder(), как показано ниже.
ФункцияonBindViewHolder()вызываетсяRecyclerViewдля отображения данных одного элемента списка в указанной позиции. Таким образом, методonBindViewHolder()принимает два аргумента: держатель представления и позицию привязываемых данных. В данном приложении держателем являетсяTextItemViewHolder, а позицией — позиция в списке.
override fun onBindViewHolder(holder: TextItemViewHolder, position: Int) {
}- Внутри
onBindViewHolder()создайте переменную для одного элемента в заданной позиции данных.
val item = data[position]- У созданного вами
ViewHolderесть свойствоtextView. В методеonBindViewHolder()установитеtexttextViewзначение, соответствующее качеству сна. Этот код отображает только список чисел, но этот простой пример позволяет увидеть, как адаптер передаёт данные в ViewHolder и выводит их на экран.
holder.textView.text = item.sleepQuality.toString()- В
SleepNightAdapterпереопределите и реализуйтеonCreateViewHolder(), который вызывается, когдаRecyclerViewтребуется держатель представления для представления элемента.
Эта функция принимает два параметра и возвращаетViewHolder.parentпараметр, представляющий собой группу представлений, содержащую держатель представления, всегда являетсяRecyclerView. ПараметрviewTypeиспользуется, когда в одномRecyclerViewесть несколько представлений. Например, если вы поместите список текстовых представлений, изображение и видео в одинRecyclerView, функцияonCreateViewHolder()должна знать, какой тип представления использовать.
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): TextItemViewHolder {
}- В
onCreateViewHolder()создайте экземплярLayoutInflater.
Модуль расширения макета умеет создавать представления из XML-макетов.contextсодержит информацию о том, как правильно расширить представление. В адаптере для представления Recycler всегда передаётся контекстparentгруппы представлений, то естьRecyclerView.
val layoutInflater = LayoutInflater.from(parent.context)- В
onCreateViewHolder()создайтеview, попросивlayoutinflaterрасширить его.
Передайте XML-макет для представления иparentгруппу представлений для представления. Третий аргумент, логический, —attachToRoot. Этот аргумент должен бытьfalse, посколькуRecyclerViewавтоматически добавит этот элемент в иерархию представлений, когда придёт время.
val view = layoutInflater
.inflate(R.layout.text_item_view, parent, false) as TextView- В
onCreateViewHolder()вернитеTextItemViewHolder, созданный с помощьюview.
return TextItemViewHolder(view)- Адаптер должен сообщать
RecyclerViewоб измененииdata, посколькуRecyclerViewничего не знает об этих данных. Он знает только о владельцах представлений, которых ему предоставляет адаптер.
Чтобы сообщитьRecyclerViewоб изменении отображаемых данных, добавьте пользовательский сеттер к переменнойdataв верхней части классаSleepNightAdapter. В сеттере присвойтеdataновое значение, а затем вызовитеnotifyDataSetChanged()чтобы инициировать перерисовку списка с новыми данными.
var data = listOf<SleepNight>()
set(value) {
field = value
notifyDataSetChanged()
}Шаг 4: Сообщите RecyclerView об адаптере
RecyclerView необходимо знать об адаптере, который будет использоваться для получения держателей представлений.
- Откройте
SleepTrackerFragment.kt. - В
onCreateview()создайте адаптер. Разместите этот код после создания моделиViewModelи перед операторомreturn.
val adapter = SleepNightAdapter()- Свяжите
adapterсRecyclerView.
binding.sleepList.adapter = adapter- Очистите и перестройте свой проект, чтобы обновить объект
binding.
Если ошибки, связанные сbinding.sleepListилиbinding.FragmentSleepTrackerBinding, по-прежнему возникают, отмените кэширование и перезапустите систему. (Выберите Файл > Недействительное кэширование / Перезапустить .)
Если вы запустите приложение сейчас, ошибок не возникнет, но вы не увидите никаких данных, когда нажмете «Пуск» , а затем «Остановить» .
Шаг 5: Загрузите данные в адаптер
Итак, у вас есть адаптер и способ передачи данных из адаптера в RecyclerView . Теперь нужно передать данные в адаптер из ViewModel .
- Откройте
SleepTrackerViewModel. - Найдите переменную
nights, в которой хранятся все ночи сна, и эти данные нужно отобразить. Переменнаяnightsустанавливается вызовом методаgetAllNights()в базе данных. - Удалите
privateизnights, так как вы создадите наблюдателя, которому потребуется доступ к этой переменной. Ваше объявление должно выглядеть так:
val nights = database.getAllNights()- В пакете
databaseоткройтеSleepDatabaseDao. - Найдите функцию
getAllNights(). Обратите внимание, что эта функция возвращает список значенийSleepNightв видеLiveData. Это означает, что переменнаяnightsсодержитLiveData, обновляемыеRoom, и вы можете наблюдать заnights, чтобы знать, когда они меняются. - Откройте
SleepTrackerFragment. - В
onCreateView(), под созданиемadapter, создайте наблюдателя для переменнойnights.
УказавviewLifecycleOwnerфрагмента в качестве владельца жизненного цикла, можно гарантировать, что этот наблюдатель будет активен только тогда, когда на экране отображаетсяRecyclerView.
sleepTrackerViewModel.nights.observe(viewLifecycleOwner, Observer {
})- Внутри наблюдателя, всякий раз, когда вы получаете ненулевое значение (для
nights), присваивайте это значение адаптеруdata. Вот готовый код для наблюдателя и установки данных:
sleepTrackerViewModel.nights.observe(viewLifecycleOwner, Observer {
it?.let {
adapter.data = it
}
})- Создайте и запустите свой код.
Если ваш адаптер работает, вы увидите показатели качества сна в виде списка. На снимке экрана слева после нажатия кнопки « Старт» отображается значение -1. На снимке экрана справа показан обновлённый показатель качества сна после нажатия кнопки «Стоп» и выбора оценки качества.

Шаг 6: Изучите, как перерабатываются держатели изображений
RecyclerView повторно использует держатели представлений. Когда представление прокручивается за пределы экрана, RecyclerView повторно использует его для представления, которое должно появиться на экране.
Поскольку эти держатели представлений повторно используются, убедитесь, что onBindViewHolder() устанавливает или сбрасывает все настройки, которые предыдущие элементы могли установить для держателя представления.
Например, вы можете установить красный цвет текста для объектов, рейтинг качества которых меньше или равен 1 и которые символизируют плохой сон.
- В классе
SleepNightAdapterдобавьте следующий код в конецonBindViewHolder().
if (item.sleepQuality <= 1) {
holder.textView.setTextColor(Color.RED) // red
}- Запустите приложение.
- Добавьте данные о низком качестве сна, и число станет красным.
- Добавляйте высокие оценки качества сна, пока на экране не появится красная высокая цифра.
ПосколькуRecyclerViewповторно использует держатели представлений, в конечном итоге один из красных держателей представлений повторно используется для высокого рейтинга качества. Высокий рейтинг ошибочно отображается красным цветом.

- Чтобы исправить это, добавьте оператор
else, чтобы установить черный цвет, если качество не меньше или равно единице.
Если оба условия указаны явно, владелец представления будет использовать правильный цвет текста для каждого элемента.
if (item.sleepQuality <= 1) {
holder.textView.setTextColor(Color.RED) // red
} else {
// reset
holder.textView.setTextColor(Color.BLACK) // black
}- Запустите приложение, и цифры всегда будут иметь правильный цвет.
Поздравляем! Теперь у вас есть полностью функциональный базовый RecyclerView .
В этой задаче вы замените простой держатель представления на тот, который может отображать больше данных о сне ночью.
Простой ViewHolder , который вы добавили в Util.kt просто оборачивает TextView в TextItemViewHolder .
class TextItemViewHolder(val textView: TextView): RecyclerView.ViewHolder(textView)Так почему же RecyclerView не использует TextView напрямую? Эта строка кода обеспечивает массу функций. ViewHolder описывает представление элемента и метаданные о его месте в RecyclerView . RecyclerView использует эту функциональность для правильного позиционирования представления при прокрутке списка и для реализации интересных функций, таких как анимация представлений при добавлении или удалении элементов в Adapter .
Если RecyclerView требуется доступ к представлениям, хранящимся во ViewHolder , он может сделать это, используя свойство itemView держателя представления. RecyclerView использует itemView при привязке элемента к отображению на экране, при отрисовке декоративных элементов вокруг представления, например, рамки, а также для реализации специальных возможностей.
Шаг 1: Создайте макет элемента
На этом этапе вы создадите файл макета для одного элемента. Макет состоит из ConstraintLayout с ImageView для качества сна, TextView для продолжительности сна и TextView для качества сна в текстовом формате. Поскольку вы уже работали с макетами, скопируйте и вставьте предоставленный XML-код.
- Создайте новый файл ресурсов макета и назовите его
list_item_sleep_night. - Замените весь код в файле кодом ниже. Затем ознакомьтесь с созданным вами макетом.
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout 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="wrap_content">
<ImageView
android:id="@+id/quality_image"
android:layout_width="@dimen/icon_size"
android:layout_height="60dp"
android:layout_marginStart="16dp"
android:layout_marginTop="8dp"
android:layout_marginBottom="8dp"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
tools:srcCompat="@drawable/ic_sleep_5" />
<TextView
android:id="@+id/sleep_length"
android:layout_width="0dp"
android:layout_height="20dp"
android:layout_marginStart="8dp"
android:layout_marginTop="8dp"
android:layout_marginEnd="16dp"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toEndOf="@+id/quality_image"
app:layout_constraintTop_toTopOf="@+id/quality_image"
tools:text="Wednesday" />
<TextView
android:id="@+id/quality_string"
android:layout_width="0dp"
android:layout_height="20dp"
android:layout_marginTop="8dp"
app:layout_constraintEnd_toEndOf="@+id/sleep_length"
app:layout_constraintHorizontal_bias="0.0"
app:layout_constraintStart_toStartOf="@+id/sleep_length"
app:layout_constraintTop_toBottomOf="@+id/sleep_length"
tools:text="Excellent!!!" />
</androidx.constraintlayout.widget.ConstraintLayout>- Перейдите на вкладку «Дизайн» в Android Studio. В режиме дизайна ваш макет выглядит, как на скриншоте слева. В режиме чертежа он выглядит, как на скриншоте справа.

Шаг 2: Создание ViewHolder
- Откройте
SleepNightAdapter.kt. - Создайте внутри
SleepNightAdapterкласс с названиемViewHolderи сделайте его расширениемRecyclerView.ViewHolder.
class ViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView){}- Внутри
ViewHolderполучите ссылки на представления. Вам нужна ссылка на представления, которые этотViewHolderбудет обновлять. Каждый раз при привязке этогоViewHolderвам необходимо получать доступ к изображению и обоим текстовым представлениям. (Позже вы преобразуете этот код для использования привязки данных.)
val sleepLength: TextView = itemView.findViewById(R.id.sleep_length)
val quality: TextView = itemView.findViewById(R.id.quality_string)
val qualityImage: ImageView = itemView.findViewById(R.id.quality_image)Шаг 3: использование ViewHolder в SleepNightAdapter
- В определении
SleepNightAdapterвместоTextItemViewHolderиспользуйтеSleepNightAdapter.ViewHolder, который вы только что создали.
class SleepNightAdapter: RecyclerView.Adapter<SleepNightAdapter.ViewHolder>() {Обновление onCreateViewHolder() :
- Измените сигнатуру
onCreateViewHolder()так, чтобы она возвращалаViewHolder. - Измените инфлятор макета, чтобы использовать правильный ресурс макета,
list_item_sleep_night. - Удалить приведение к
TextView. - Вместо возврата
TextItemViewHolderвернитеViewHolder.
Вот готовая обновленная функцияonCreateViewHolder():
override fun onCreateViewHolder(
parent: ViewGroup, viewType: Int): ViewHolder {
val layoutInflater =
LayoutInflater.from(parent.context)
val view = layoutInflater
.inflate(R.layout.list_item_sleep_night,
parent, false)
return ViewHolder(view)
}Обновление onBindViewHolder() :
- Измените сигнатуру
onBindViewHolder()так, чтобы параметромholderбылViewHolderвместоTextItemViewHolder. - Внутри
onBindViewHolder()удалите весь код, кроме определенияitem. - Определите
valres, которое содержит ссылку наresourcesдля этого представления.
val res = holder.itemView.context.resources- Установите текст в текстовом представлении
sleepLengthравным длительности. Скопируйте код ниже, который вызывает функцию форматирования, предоставленную вместе с кодом запуска.
holder.sleepLength.text = convertDurationToFormatted(item.startTimeMilli, item.endTimeMilli, res)- Это приводит к ошибке, поскольку необходимо определить
convertDurationToFormatted(). ОткройтеUtil.ktи раскомментируйте код и связанные с ним импорты. (Выберите «Код» > «Комментировать строкой» .) - Вернитесь в
onBindViewHolder()и используйтеconvertNumericQualityToString()для установки качества.
holder.quality.text= convertNumericQualityToString(item.sleepQuality, res)- Возможно, вам придется вручную импортировать эти функции.
import com.example.android.trackmysleepquality.convertDurationToFormatted
import com.example.android.trackmysleepquality.convertNumericQualityToString- Установите правильный значок для качества. Новый значок
ic_sleep_activeпредоставлен в стартовом коде.
holder.qualityImage.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
})- Вот готовая обновленная функция
onBindViewHolder(), устанавливающая все данные дляViewHolder:
override fun onBindViewHolder(holder: ViewHolder, position: Int) {
val item = data[position]
val res = holder.itemView.context.resources
holder.sleepLength.text = convertDurationToFormatted(item.startTimeMilli, item.endTimeMilli, res)
holder.quality.text= convertNumericQualityToString(item.sleepQuality, res)
holder.qualityImage.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
})
}- Запустите приложение. Ваш экран должен выглядеть, как на скриншоте ниже: значок качества сна, а также текст, отображающий продолжительность и качество сна.

Ваш RecyclerView готов! Вы узнали, как реализовать Adapter и ViewHolder , и объединили их для отображения списка с помощью Adapter RecyclerView .
Ваш код пока демонстрирует процесс создания адаптера и держателя представления. Однако вы можете улучшить этот код. Код для отображения и код для управления держателями представления перепутаны, а onBindViewHolder() знает подробности об обновлении ViewHolder .
В производственном приложении может быть несколько держателей представлений, более сложные адаптеры и несколько разработчиков, вносящих изменения. Структурируйте код так, чтобы всё, что связано с держателем представления, находилось только в нём.
Шаг 1: Рефакторинг onBindViewHolder()
На этом этапе вы проводите рефакторинг кода и переносите весь функционал View Holder во ViewHolder . Цель этого рефакторинга — не изменить внешний вид приложения для пользователя, а сделать работу с кодом проще и безопаснее для разработчиков. К счастью, в Android Studio есть инструменты, которые помогут вам в этом.
- В
SleepNightAdapter, вonBindViewHolder(), выберите все, кроме оператора объявления переменнойitem. - Щелкните правой кнопкой мыши, затем выберите Рефакторинг > Извлечь > Функция .
- Назовите функцию
bindи примите предложенные параметры. Нажмите OK .
Функцияbind()размещена нижеonBindViewHolder().
private fun bind(holder: ViewHolder, item: SleepNight) {
val res = holder.itemView.context.resources
holder.sleepLength.text = convertDurationToFormatted(item.startTimeMilli, item.endTimeMilli, res)
holder.quality.text = convertNumericQualityToString(item.sleepQuality, res)
holder.qualityImage.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
})
}- Установите курсор на слово-
holderпараметра-holderфункцииbind(). НажмитеAlt+Enter(Option+Enterна Mac), чтобы открыть меню намерений. Выберите «Преобразовать параметр в приемник» , чтобы преобразовать его в функцию расширения со следующей сигнатурой:
private fun ViewHolder.bind(item: SleepNight) {...}- Вырежьте и вставьте функцию
bind()воViewHolder. - Сделайте
bind()публичным. - При необходимости импортируйте
bind()в адаптер. - Поскольку теперь он находится в
ViewHolder, вы можете удалить частьViewHolderиз сигнатуры. Вот окончательный код функцииbind()в классеViewHolder.
fun bind(item: SleepNight) {
val res = itemView.context.resources
sleepLength.text = convertDurationToFormatted(
item.startTimeMilli, item.endTimeMilli, res)
quality.text = convertNumericQualityToString(
item.sleepQuality, res)
qualityImage.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: Рефакторинг onCreateViewHolder
Метод onCreateViewHolder() в адаптере в настоящее время расширяет представление из ресурса макета для ViewHolder . Однако расширение никак не связано с адаптером, а связано исключительно с ViewHolder . Расширение должно происходить во ViewHolder .
- В
onCreateViewHolder()выделите весь код в теле функции. - Щелкните правой кнопкой мыши, затем выберите Рефакторинг > Извлечь > Функция .
- Назовите функцию
fromпримите предложенные параметры. Нажмите «ОК» . - Установите курсор на имя функции
from. НажмитеAlt+Enter(Option+Enterна Mac), чтобы открыть меню намерений. - Выберите «Переместить в сопутствующий объект» . Функция
from()должна находиться в сопутствующем объекте, чтобы её можно было вызвать в классеViewHolder, а не в экземпляреViewHolder. - Переместите
companionобъект в классViewHolder. - Сделать
from()публичным. - В
onCreateViewHolder()измените операторreturnтак, чтобы он возвращал результат вызоваfrom()в классеViewHolder.
Ваши готовые методыonCreateViewHolder()иfrom()должны выглядеть так, как показано ниже, а код должен компилироваться и запускаться без ошибок.
override fun onCreateViewHolder(parent: ViewGroup, viewType:
Int): ViewHolder {
return ViewHolder.from(parent)
}companion object {
fun from(parent: ViewGroup): ViewHolder {
val layoutInflater = LayoutInflater.from(parent.context)
val view = layoutInflater
.inflate(R.layout.list_item_sleep_night, parent, false)
return ViewHolder(view)
}
}- Измените сигнатуру класса
ViewHolderтак, чтобы конструктор стал приватным. Поскольку методfrom()теперь возвращает новый экземплярViewHolder, больше нет причин вызывать конструкторViewHolder.
class ViewHolder private constructor(itemView: View) : RecyclerView.ViewHolder(itemView){- Запустите приложение. Ваше приложение должно собраться и работать так же, как и раньше, что и является желаемым результатом после рефакторинга.
Проект Android Studio: RecyclerViewFundamentals
- Отображение списка или таблицы данных — одна из самых распространённых задач пользовательского интерфейса в Android.
RecyclerViewразработан для эффективного отображения даже очень больших списков. -
RecyclerViewвыполняет только ту работу, которая необходима для обработки или отрисовки элементов, которые в данный момент видны на экране. - Когда элемент прокручивается за пределы экрана, его представления перезаписываются. Это означает, что элемент заполняется новым контентом, который прокручивается на экран.
- Шаблон «адаптер» в программной инженерии помогает объекту взаимодействовать с другим API.
RecyclerViewиспользует адаптер для преобразования данных приложения в то, что оно может отображать, без необходимости изменения способа хранения и обработки данных приложением.
Для отображения данных в RecyclerView вам понадобятся следующие компоненты:
- RecyclerView
Чтобы создать экземплярRecyclerView, определите элемент<RecyclerView>в файле макета. - LayoutManager
RecyclerViewиспользуетLayoutManagerдля организации расположения элементов вRecyclerView, например, для их размещения в сетке или в линейном списке.
В<RecyclerView>файла макета задайте атрибутapp:layoutManagerдля менеджера макета (например,LinearLayoutManagerилиGridLayoutManager).
Вы также можете программно настроитьLayoutManagerдляRecyclerView. (Этот метод рассматривается в следующей лабораторной работе.) - Макет для каждого элемента
Создайте макет для одного элемента данных в файле макета XML. - Адаптер
Создайте адаптер, который подготавливает данные и определяет, как они будут отображаться вViewHolder. Свяжите адаптер сRecyclerView.
При запускеRecyclerViewон будет использовать адаптер, чтобы определить, как отображать данные на экране.
Адаптер требует реализации следующих методов:
–getItemCount()для возврата количества элементов.
–onCreateViewHolder()для возвратаViewHolderдля элемента в списке.
–onBindViewHolder()для адаптации данных к представлениям элемента в списке. - ViewHolder
ViewHolderсодержит информацию о представлении для отображения одного элемента из макета элемента. - Метод
onBindViewHolder()в адаптере адаптирует данные к представлениям. Этот метод всегда переопределяется. Как правило,onBindViewHolder()расширяет макет элемента и помещает данные в представления макета. - Поскольку
RecyclerViewничего не знает о данных,Adapterдолжен информироватьRecyclerViewоб изменении этих данных. ИспользуйтеnotifyDataSetChanged(), чтобы уведомитьAdapterоб изменении данных.
Курс Udacity:
Документация для разработчиков Android:
В этом разделе перечислены возможные домашние задания для студентов, работающих над этой лабораторной работой в рамках курса, проводимого преподавателем. Преподаватель должен выполнить следующие действия:
- При необходимости задавайте домашнее задание.
- Объясните учащимся, как следует сдавать домашние задания.
- Оцените домашние задания.
Преподаватели могут использовать эти предложения так часто или редко, как пожелают, и могут свободно задавать любые другие домашние задания, которые они сочтут подходящими.
Если вы работаете с этой лабораторной работой самостоятельно, можете использовать эти домашние задания для проверки своих знаний.
Ответьте на эти вопросы
Вопрос 1
Как RecyclerView отображает элементы? Выберите все подходящие варианты.
▢ Отображает элементы в виде списка или сетки.
▢ Прокручивается вертикально или горизонтально.
▢ Прокручивается по диагонали на больших устройствах, таких как планшеты.
▢ Позволяет создавать пользовательские макеты, когда списка или сетки недостаточно для конкретного случая использования.
Вопрос 2
Каковы преимущества использования RecyclerView ? Выберите все подходящие варианты.
▢ Эффективно отображает большие списки.
▢ Автоматически обновляет данные.
▢ Минимизирует необходимость обновлений при обновлении, удалении или добавлении элемента в список.
▢ Повторно использует представление, прокручивающееся за пределы экрана, для отображения следующего элемента, прокручивающегося на экране.
Вопрос 3
Каковы причины использования адаптеров? Выберите все подходящие варианты.
▢ Разделение задач упрощает изменение и тестирование кода.
▢ RecyclerView не зависит от отображаемых данных.
▢ Уровни обработки данных не должны беспокоиться о том, как будут отображаться данные.
▢ Приложение будет работать быстрее.
Вопрос 4
Какие из следующих утверждений верны для ViewHolder ? Выберите все подходящие варианты.
▢ Макет ViewHolder определяется в файлах макета XML.
▢ Для каждой единицы данных в наборе данных существует один ViewHolder .
▢ В RecyclerView может быть более одного ViewHolder .
▢ Adapter привязывает данные к ViewHolder .
Начните следующий урок: