RecyclerView
Компонент RecyclerView появился в Android 5.0 Lollipop и находится в разделе Containers. Для простоты буду называть его списком, хотя на самом деле это универсальный элемент управления с большими возможностями.
Раньше для отображения прокручиваемого списка использовался ListView. Со временем у него обнаружилось множество недостатков, которые было трудно исправить. Тогда решили создать новый элемент с нуля.
Вначале это был сырой продукт, потом его доработали. На данном этапе можно считать, что он стал полноценной заменой устаревшего ListView.
Схематично работу RecyclerView можно представить следующим образом. На экране отображаются видимые элементы списка. Когда при прокрутке списка верхний элемент уходит за пределы экрана и становится невидимым, его содержимое очищается. При этом сам «чистый» элемент помещается вниз экрана и заполняется новыми данными, иными словами переиспользуется, отсюда название Recycle.

Компонент RecyclerView не является родственником ListView и относится к семейству ViewGroup. Он часто используется как замена ListView, но его возможности шире.
Следует сказать, что при работе с ним приходится писать много кода, который пугает новичков. Если с RecyclerView работать не постоянно, то порой забываются детали и сложно вспомнить необходимые шаги. Многие просто сохраняют отдельные шаблоны и копируют в разные проекты.
Внешний вид можно представить не только в виде обычного списка, но и в виде сетки. При необходимости можно быстро переключиться между разными типами отображения.
Для размещения своих дочерних элементов используется специальный менеджер макетов LayoutManager. Он может быть трёх видов.
- LinearLayoutManager — дочерние элементы размещаются вертикально (как в ListView) или горизонтально
- GridLayoutManager — дочерние элементы размещаются в сетке, как в GridView
- StaggeredGridLayoutManager — неравномерная сетка
Можно создать собственный менеджер на основе RecyclerView.LayoutManager.
RecyclerView.ItemDecoration позволяет работать с дочерними элементами: отступы, внешний вид.
ItemAnimator — отвечает за анимацию элементов при добавлении, удалении и других операций.
RecyclerView.Adapter связывает данные с компонентом и отслеживает изменения.
- notifyItemInserted(), notifyItemRemoved(), notifyItemChanged() — методы, отслеживающие добавление, удаление или изменение позиции одного элемента
- notifyItemRangeInserted(), notifyItemRangeRemoved(), notifyItemRangeChanged() — методы, отслеживающие изменение порядка элеметов
Стандартный метод notifyDataSetChanged() поддерживается, но он не приводит к внешнему изменению элементов на экране.
Программисты со стажем знают, что для создания «правильного» ListView нужно было создавать класс ViewHolder. В старых списках его можно было игнорировать. Теперь это стало необходимым условием.
Общая модель работы компонента.

Мы рассмотрим только базовый пример для быстрого знакомства. В реальных проектах примеры будут гораздо сложнее, чтобы обеспечить другие функциональные возможности — обработка жестов, анимация, динамическое удаление и добавление элементов.
Размещаем компонент в макете экрана через панель инструментов. Но сначала добавим зависимость.
implementation 'androidx.recyclerview:recyclerview:1.2.1'
Создадим макет для отдельного элемента списка. Варианты могут быть самыми разными — можно использовать один TextView для отображения строк (имена котов), можно использовать связку ImageView и TextView (имена котов и их наглые морды). Мы возьмём для примера две текстовые метки. Создадим новый файл res/layout/recyclerview_item.xml.
Добавим компонент в разметку экрана активности.
Если вы хотите видеть макет в режиме дизайна, то добавьте в RecyclerView атрибут tools:listitem=»@layout/recyclerview_item». Тогда вам будет доступен предварительный просмотр.
Существуют и другие полезные атрибуты, которые вы можете применить к компоненту. Например, вы можете добавить полосу прокрутки:
android:scrollbars="horizontal"
Минимальный код для запуска.
// в onCreate() setContentView(R.layout.activity_main) val recyclerView: RecyclerView = findViewById(R.id.recyclerView) recyclerView.layoutManager = LinearLayoutManager(this)
Пока ничего сложного, но выводить такой список ничего не будет. Нужен адаптер и данные для отображения. В адаптере описывается способ связи между данными и компонентом.
Начнём по порядку, чтобы запомнить последовательность. Для начала создадим обычный класс и в конструкторе передадим список строк. Список будет содержать имена котов.
package ru.alexanderklimov.recyclerviewdemo class CustomRecyclerAdapter(private val names: List)
Класс MyViewHolder на основе ViewHolder служит для оптимизации ресурсов. Он служит своеобразным контейнером для всех компонентов, которые входят в элемент списка. При этом RecyclerView создаёт ровно столько контейнеров, сколько нужно для отображения на экране. Новый класс добавим в состав нашего созданного ранее класса. В скобках указываем название для элемента списка на основе View и этот же параметр указываем и для RecycleView.ViewHolder.
package ru.alexanderklimov.recyclerviewdemo import android.view.View import androidx.recyclerview.widget.RecyclerView class CustomRecyclerAdapter(private val names: List) < class MyViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) >
В созданном классе-контейнере нужно просто перечислить используемые компоненты из макета для отдельного элемента списка. В нашем примере задействованы два TextView, инициализируем их через идентификаторы.
package ru.alexanderklimov.recyclerviewdemo import android.view.View import android.widget.TextView import androidx.recyclerview.widget.RecyclerView class CustomRecyclerAdapter(private val names: List) < class MyViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) < val largeTextView: TextView = itemView.findViewById(R.id.textViewLarge) val smallTextView: TextView = itemView.findViewById(R.id.textViewSmall) >>
Создадим адаптер — наследуем наш класс от класса RecyclerView.Adapter и в качестве параметра указываем созданный нами MyViewHolder. Студия попросит реализовать три метода.
class CustomRecyclerAdapter(private val names: List) : RecyclerView.Adapter() < class MyViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) < val largeTextView: TextView = itemView.findViewById(R.id.textViewLarge) val smallTextView: TextView = itemView.findViewById(R.id.textViewSmall) > override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): MyViewHolder < TODO("Not yet implemented") >override fun onBindViewHolder(holder: MyViewHolder, position: Int) < TODO("Not yet implemented") >override fun getItemCount(): Int >
getItemCount()
Возвращает количество элементов списка. Как правило данные являются однотипными, например, список или массив строк. Адаптеру нужно знать, сколько элементов нужно предоставить компоненту, чтобы распределить ресурсы и подготовиться к отображению на экране. При работе с коллекциями или массивом мы можем просто вычислить его длину и передать это значение методу адаптера getItemCount(). В простых случаях мы можем записать код в одну строчку.
// Длинный вариант override fun getItemCount(): Int < return names.size >// Короткий вариант override fun getItemCount() = names.size
onCreateViewHolder
Нам нужно указать идентификатор макета для отдельного элемента списка, созданный нами ранее в файле recyclerview_item.xml. А также вернуть наш объект класса ViewHolder. Допустим, устройство может отобразить на экране 9 элементов списка. RecyclerView создаст 11-12 элементов (с запасом). Неважно, каким большим будет ваш список, но все данные будут размещаться в тех же 11 элементах, автоматически меняя содержимое при прокрутке.
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): MyViewHolder
onBindViewHolder()
В методе адаптера onBindViewHolder() связываем используемые текстовые метки с данными — в одном случае это значения из списка, во втором используется одна и та же строка. Параметр position отвечает за позицию в списке (индекс), по которой можно получить нужные данные.
override fun onBindViewHolder(holder: MyViewHolder, position: Int)
Должно получиться следующее.
package ru.alexanderklimov.recyclerviewdemo import android.view.LayoutInflater import android.view.View import android.view.ViewGroup import android.widget.TextView import androidx.recyclerview.widget.RecyclerView class CustomRecyclerAdapter(private val names: List) : RecyclerView .Adapter() < class MyViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) < val largeTextView: TextView = itemView.findViewById(R.id.textViewLarge) val smallTextView: TextView = itemView.findViewById(R.id.textViewSmall) >override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): MyViewHolder < val itemView = LayoutInflater.from(parent.context) .inflate(R.layout.recycler_item, parent, false) return MyViewHolder(itemView) >override fun onBindViewHolder(holder: MyViewHolder, position: Int) < holder.largeTextView.text = names[position] holder.smallTextView.text = "кот" >override fun getItemCount() = names.size >
Подключаем в активности. Создадим пока бессмысленный список строк, который передадим в адаптер.
// Если этот код работает, его написал Александр Климов, // а если нет, то не знаю, кто его писал. package ru.alexanderklimov.recyclerviewdemo import android.os.Bundle import androidx.appcompat.app.AppCompatActivity import androidx.recyclerview.widget.LinearLayoutManager import androidx.recyclerview.widget.RecyclerView class MainActivity : AppCompatActivity() < override fun onCreate(savedInstanceState: Bundle?) < super.onCreate(savedInstanceState) setContentView(R.layout.activity_main) val recyclerView: RecyclerView = findViewById(R.id.recyclerView) recyclerView.layoutManager = LinearLayoutManager(this) recyclerView.adapter = CustomRecyclerAdapter(fillList()) >private fun fillList(): List < val data = mutableListOf() (0..30).forEach < i ->data.add("$i element") > return data > >
Запускаем ещё раз.

Вариант с числами нам не интересен, поэтому добавим котов. Имена котов и кошек разместим в ресурсах в виде массива в файле res/values/strings.xml.
- Рыжик
- Барсик
- Мурзик
- Мурка
- Васька
- Томасина
- Кристина
- Пушок
- Дымка
- Кузя
- Китти
- Масяня
- Симба
Создадим новую функцию для получения списка котов из ресурсов и передадим его адаптеру.
private fun getCatList(): List < return this.resources.getStringArray(R.array.cat_names).toList() >recyclerView.adapter = CustomRecyclerAdapter(getCatList())
Горизонтальная прокрутка
Можем указать горизонтальный вариант прокрутки. Остальной код менять не придётся.
recyclerView.layoutManager = LinearLayoutManager(this, LinearLayoutManager.HORIZONTAL, false)
А можно вообще обойтись только XML-декларацией.
Оптимизация
При прокрутке под капотом выполняется сложная работа по обновлению контента. Поэтому не следует перегружать элементы списка сложной структурой и не перебарщивайте с вложенными элементами, а также нужно следить за оптимизацией, чтобы избежать лагов.
При работе с изображениями старайтесь использовать готовые библиотеки Picasso, Glide, Fresco и т.д.
Если картинки загружаются с сервера, неплохо заранее вычислять их размеры и пропорции. В некоторых случаях желательно позаботиться, чтобы картинки были одного размера (если это возможно).
Не перегружайте лишним кодом метод onBindViewHolder(). Только самое необходимое.
Если вы используете определённое неизменяемое число элементов (число месяцев в году, знаки зодиака, число команд в турнире), то добавьте вызов метода recyclerView.setHasFixedSize(true).
В отдельных случаях можно поэкспериментировать с кэшем через recyclerView.setItemViewCacheSize(cacheSize).
Дополнительные материалы
Дополнительное чтение
Знакомство с RecyclerView (Вы здесь)
Adapter and listeners
Для создания динамического списка элементов, например RecyclerView, необходимо создать Adapter, чтобы связать элементы со списком. Задача адаптера — Adapters provide a binding from an app-specific data set to views that are displayed within a RecyclerView из документации т.е. лишь связать данные с отображением на UI
Как должен выглядеть адаптер:
- адаптер не должен содержать никакой логики по взаимодействию с элементами списка. Если нужно установить действие, например по нажатию на элемент, то адаптер должен получать лямбду при создании, которую потом привяжет к действию над элементом
- единственная задача адаптера — это привязать данные к RecyclerView
Пример простого адаптера
Разберем пример, у нас есть Activity со списком элементов, мы хотим, чтобы по нажатию на элемент что-то происходило, для демонстрации просто покажем Toast.
Сначала создадим ячейку нашего списка text_row_item.xml :
TextView xmlns:android="http://schemas.android.com/apk/res/android" xmlns:tools="http://schemas.android.com/tools" android:id="@+id/textView" android:layout_width="match_parent" android:layout_height="wrap_content" android:text="" tools:text="sample" />
Затем, добавим список на activity_main.xml
androidx.recyclerview.widget.RecyclerView android:id="@+id/recyclerView" android:layout_width="match_parent" android:layout_height="match_parent" tools:listitem="@layout/text_row_item" />
Создадим CustomAdapter , который при создании будет принимать onClick-лямбду . Значения будут устанавливаться не в конструкторе, а просто в переменную, как только установятся — вызовется notifyDataSetChanged()
class CustomAdapter(private val onItemClick: (Int) -> Unit) : RecyclerView.Adapter() var items: List = emptyList() set(value) field = value notifyDataSetChanged() > class ViewHolder(view: View) : RecyclerView.ViewHolder(view) val textView: TextView = view.findViewById(R.id.textView) > override fun onCreateViewHolder(viewGroup: ViewGroup, viewType: Int): ViewHolder val view = LayoutInflater.from(viewGroup.context) .inflate(R.layout.text_row_item, viewGroup, false) return ViewHolder(view) > override fun onBindViewHolder(viewHolder: ViewHolder, position: Int) viewHolder.textView.text = items[position] viewHolder.itemView.setOnClickListener > override fun getItemCount() = items.size >
Зачем нужен вьюхолдер?
Цель RecyclerView — переиспользовать вьюхи списка, а не грузить сразу все. Для этого, проверяется, возможно ли взять уже существующую, или нужно создавать новую. Если получилось взять существующую — то в ней лежит объект viewHolder , через который можно обратиться к элементам переиспользуемой вьюхи, которые мы собираемся заполнять новыми данными. т.е. нет необходимости вызывать метод findViewById , чтобы получить доступ к вьюхам элемента списка.
Хоть у нас и есть доступ к view элемента в методе onCreateViewHolder , устанавливать там onClickListener нельзя, потому что задача viewHolder-a — это только держать ссылки на вьюхи элементов. Изучите документацию
Еще одна причина, почему привязка onClick должна быть в onBindViewHolder — это то, что viewHolder-ы можно переиспользовать между несколькими recyclerView , например для составных списков (вертикальный список из горизонтальных списков). Будет создано несколько recyclerView , и, если они будут объединены в один RecycledViewPool то они смогут обмениваться viewHolder-ами , именно поэтому viewHolder не должен привязываться к конкретному объекту адаптеру, который его создал, потому что потом его может использовать другой адаптер. И именно в методе onBindViewHolder есть гарантия, что переданный viewHolder относится к нашим данным.
Наконец, создадим наш адаптер, проинициализируем элементами с лямбдой и привяжем к recyclerView :
class MainActivity : AppCompatActivity() override fun onCreate(savedInstanceState: Bundle?) super.onCreate(savedInstanceState) setContentView(R.layout.activity_main) val dataSet = listOf("aaa", "bbb", "ccc", "ddd", "fff", "jjj","aaa", "bbb", "ccc", "ddd", "fff", "jjj") val customAdapter = CustomAdapter customAdapter.items = dataSet val recyclerView: RecyclerView = findViewById(R.id.recyclerView) recyclerView.layoutManager = LinearLayoutManager(this) recyclerView.adapter = customAdapter > private fun onItemClick(title: String) Toast.makeText(this, "tap on $title", Toast.LENGTH_SHORT).show() > >
Заключение
- передача элементов в адаптер должна быть не через конструктор, а через изменяемую переменную
- в адаптере не должно содержаться никакой логики, только реализация привязки данных
- задача viewHolder-а — держать ссылки на вьюхи элемента списка
- установка onClick-лямбды должна быть в onBindViewHolder
How to initialize viewHolder in RecyclerView adapter for Android in Kotlin
How should I initialize viewHolder? I have this error: What I want to do is to get selected item in recyclerView but without using onClick method. When I get this selected item I need to show Toast message. Item is data class.
Process: com.pors.coopreaderlast, PID: 7862 kotlin.UninitializedPropertyAccessException: lateinit property viewHolder has not been initialized at com.pors.coopreaderlast.features.polozka.PolozkaAdapter.getViewHolder(PolozkaAdapter.kt:18) at com.pors.coopreaderlast.features.polozka.PolozkaAdapter.getCurrentItem(PolozkaAdapter.kt:46) at com.pors.coopreaderlast.features.polozka.PolozkaActivity.onStart(PolozkaActivity.kt:213)
this is for line where viewHolder is set in Adapter: lateinit var viewHolder: PolozkaViewHolder This is Adapter
class PolozkaAdapter(val chosen_item: Int, private val listener: OnItemClickListener): ListAdapter(DiffCallback()) < var selectedItemPosition: Int = chosen_item lateinit var viewHolder: PolozkaViewHolder override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): PolozkaViewHolder < val binding = PolozkyItemBinding.inflate(LayoutInflater.from(parent.context), parent, false) viewHolder = PolozkaViewHolder(binding) return viewHolder >override fun onBindViewHolder(holder: PolozkaViewHolder, position: Int) < val currentItem = getItem(position) holder.bind(currentItem) if (selectedItemPosition == position)< holder.itemView.setBackgroundColor(Color.parseColor("#DA745A")) >else < holder.itemView.setBackgroundColor(Color.TRANSPARENT) >> fun getCurrentItem(): Polozka = super.getItem(viewHolder.bindingAdapterPosition) inner class PolozkaViewHolder(private val binding: PolozkyItemBinding): RecyclerView.ViewHolder(binding.root) < init < binding.root.setOnClickListener< val position = bindingAdapterPosition if (position != RecyclerView.NO_POSITION)< val item = getItem(position) if (item != null)< listener.onItemClick(item, position) >> notifyItemChanged(selectedItemPosition) selectedItemPosition = bindingAdapterPosition notifyItemChanged(selectedItemPosition) > > fun bind(polozkaPolozka: Polozka) < binding.apply < tvKDE.text = polozkaPolozka.znacky tvREG.text = polozkaPolozka.reg tvVB.text = polozkaPolozka.veb.toString() >> > > interface OnItemClickListener < fun onItemClick(polozkaDoklad: Polozka, position: Int) >class DiffCallback: DiffUtil.ItemCallback() < override fun areItemsTheSame(oldItem: Polozka, newItem: Polozka) = oldItem.pvp06pk == newItem.pvp06pk override fun areContentsTheSame(oldItem: Polozka, newItem: Polozka) = oldItem == newItem >>
This is onStart method but it can be in onCreate method also.
override fun onStart()
Android RecyclerView с использованием котлиновских sealed классов

RecyclerView — это один из самых лучших инструментов для отображения больших списков на Android. Как разработчики, вы, скорее всего понимаете о чем я говорю. У нас есть много дополнительных фич, таких как шаблоны вью холдеров, сложная анимация, Diff-Utils колбек для повышения производительности и т. д. Такие приложения, как WhatsApp и Gmail, используют RecyclerView для отображения бесконечного количества сообщений.
Одна из важнейших фич RecyclerView , которые я использую, — это типы представлений (view types). В RecyclerView мы можем отобразить несколько типов представлений. Раньше разработчики делали это с помощью флага типа представления в модели списка, который возвращали в функции getViewType адаптера RecyclerView .
Почему sealed классы Kotlin?
После появления Kotlin для разработки приложений под Android наши подходы к реализации кода кардинально изменились. То есть такие фичи, как расширения, почти заменили потребность в поддержании базовых классов для компонентов Android. Делегаты Kotlin внесли изменения в нашу работу с сеттерами и геттерами.
Теперь пришло время обновлений в работе адаптера RecyclerView . Sealed классы из Kotlin оказывают значительное влияние на управление состояниями. Подробнее прочитать об этом вы можете в этой статье.
Вдохновившись этой статьей, я хочу показать вам реализацию типов представлений в RecyclerView с использованием sealed классов. Мы постараемся развить сравнение случайных чисел или лейаутов до типов классов. Если вы фанат Kotlin, я уверен, что вам понравится эта реализация.
Создание sealed классов в Kotlin
Первое, что нам нужно сделать при этом подходе — это создать все классы данных, которые мы намерены использовать в адаптере, а затем необходимо связать их в sealed классе. Давайте создадим группу классов данных:
data class FeedItem(val title: String, val desp : String, val businessName : String, . ) data class PromotionItem(val title: String, val desp : String, val image : String) data class RatingCardItem(val title: String, val desp : String, val link : String, val button_tittle : String) data class LoadingStateItem(val isLoading: Boolean, val isRetry : Boolean, val error_message : String)
Это несколько классов данных, которые я хотел отобразить в списке, основываясь на данных с серверов. Вы можете создать столько классов данных, сколько захотите. Этот подход хорошо масштабируется.
То, что мы можем работать с состояниями загрузки, хедерами, футерами и многим другим без написания дополнительных классов — это одно из крутых преимуществ данного метода. Вы скоро узнаете, как это сделать. Следующим шагом является создание sealed классов, содержащих все необходимые классы данных:
sealed class UIModel
Sealed класс с пользовательскими моделями
Как я упоминал ранее, мы можем без дополнительных сложностей добавлять хидеры и футеры из RecyclerView , используя объект Kotlin:
sealed class UIModel
Sealed класс с хидером и футером
На этом этапе заканчивается реализация нашего sealed класса.
Создание адаптера RecyclerView
После того, как мы разобрались с sealed классом, пришло время создать адаптер RecyclerView с UIModel списком. Это простой RecyclerView , но с sealed классом arraylist :
class FeedAdapter(context: Context) : RecyclerView.Adapter() < private var arrayList : ArrayList<UIModel> = ArrayList() fun submitData(list : ArrayList<UIModel>)< arrayList.clear() arrayList.addAll(list) >override fun getItemCount(): Int = arrayList.size override fun getItemViewType(position: Int): Int < return super.getItemViewType(position) >override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): RecyclerView.ViewHolder < TODO("not implemented") //To change body of created functions use File | Settings | File Templates. >override fun onBindViewHolder(holder: RecyclerView.ViewHolder, position: Int) < TODO("not implemented") //To change body of created functions use File | Settings | File Templates. >>
В приведенном выше коде показана базовая реализация адаптера RecyclerView без какой-либо логики sealed классов. Как можно заметить, мы объявили sealed классы arraylist (UIModel). Следующим шагом является возврат соответствующего типа представления на основе позиции:
override fun getItemViewType(position: Int) = when (arrayList[position]) < is UIModel.FeedyModel ->R.layout.adapter_feed is UIModel.PromotionModel -> R.layout.adapter_promotion is UIModel.RatingCardModel -> R.layout.adapter_rating is UIModel.LoadingModel -> R.layout.adapter_loading is UIModel.Header -> R.layout.adapter_header is UIModel.Footer -> R.layout.adapter_footor null -> throw IllegalStateException("Unknown view") >
Сравнение модели sealed класса для получения типа представления
Теперь, когда мы успешно вернули правильный лейаут на основе модели sealed класса, нам нужно создать соответствующий ViewHolder в функции onCreateViewHolder на основе viewtype :
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): RecyclerView.ViewHolder < val layoutInflater = LayoutInflater.from(parent.context) val v = layoutInflater.inflate(viewType, parent, false) return when (viewType) < R.layout.adapter_feed ->FeedViewHolder(v) R.layout.adapter_promotion -> PromotionalCardViweHolder(v) R.layout.adapter_rating -> RatingCardViweHolder(v) R.layout.adapter_header -> HeaderViweHolder(v) R.layout.adapter_footor -> FootorViweHolder(v) else -> LoadingViewholder(v) > >
Создание вью холдера с учетом типа представления из sealed классов
Последний шаг — обновить вью холдер на основе текущих данных элемента, чтобы адаптер мог отображать данные в пользовательском интерфейсе. Поскольку у адаптера есть несколько представлений, мы должны классифицировать тип, а затем вызвать соответствующий ViewHolder :
override fun onBindViewHolder(holder: RecyclerView.ViewHolder, position: Int) < val item = arrayList[position] when (holder) < is FeedViewHolder ->holder.onBindView(item as UIModel.FeedyModel) is PromotionalCardViweHolder -> holder.onBindView(item as UIModel.PromotionModel is RatingCardViweHolder -> holder.onBindView(item as UIModel.RatingCardModel) is HeaderViweHolder -> holder.onBindView(item as UIModel.Header) is FootorViweHolder -> holder.onBindView(item as UIModel.Footer) is LoadingViewholder -> holder.onBindView(item as UIModel.LoadingModel) > >
После объединения всех частей кода, он выглядит так:
class FeedAdapter(context: Context) : RecyclerView.Adapter() < private var arrayList : ArrayList<UIModel> = ArrayList() fun submitData(list : ArrayList<UIModel>)< arrayList.clear() arrayList.addAll(list) >override fun getItemCount(): Int = arrayList.size override fun getItemViewType(position: Int) = when (arrayList[position]) < is UIModel.FeedyModel -> R.layout.adapter_feed is UIModel.PromotionModel -> R.layout.adapter_promotion is UIModel.RatingCardModel -> R.layout.adapter_rating is UIModel.LoadingModel -> R.layout.adapter_loading is UIModel.Header -> R.layout.adapter_header is UIModel.Footer -> R.layout.adapter_footor null -> throw IllegalStateException("Unknown view") >override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): RecyclerView.ViewHolder < val layoutInflater = LayoutInflater.from(parent.context) val v = layoutInflater.inflate(viewType, parent, false) return when (viewType) < R.layout.adapter_feed -> FeedViewHolder(v) R.layout.adapter_promotion -> PromotionalCardViweHolder(v) R.layout.adapter_rating -> RatingCardViweHolder(v) R.layout.adapter_header -> HeaderViweHolder(v) R.layout.adapter_footor -> FootorViweHolder(v) else -> LoadingViewholder(v) >> override fun onBindViewHolder(holder: RecyclerView.ViewHolder, position: Int) < val item = arrayList[position] when (holder) < is FeedViewHolder -> holder.onBindView(item as UIModel.FeedyModel) is PromotionalCardViweHolder -> holder.onBindView(item as UIModel.PromotionModel) is RatingCardViweHolder -> holder.onBindView(item as UIModel.RatingCardModel) is HeaderViweHolder -> holder.onBindView(item as UIModel.Header) is FootorViweHolder -> holder.onBindView(item as UIModel.Footer) is LoadingViewholder -> holder.onBindView(item as UIModel.LoadingModel) >> >
Финальная версия адаптера
На этом этапе мы закончили. Мы реализовали все необходимое. Вы можете создать инстанс адаптера в Activity/Fragment и присвоить его RecyclerView . Как только вы получите данные, вам нужно вызвать функцию submitData с ArrayList :
lateinit var adapter: FeedAdapter fun assignAdapter() < adapter = FeedAdapter(this) categories_recyclerView?.adapter = adapter feedViewModel.scope.launch < feedViewModel.getFeed().collectLatest < adapter.submitData(it) >> >
Публикация данных в адаптер RecyclerView
DiffCallback
«DiffUtil — это вспомогательный класс, который может вычислять разницу между двумя списками и выводить список операций обновления, который преобразует первый список во второй». — Android Developer
Реализация diffcallback не является обязательной, но она повысит производительность, если вы работаете с большими наборами данных. Итак, чтобы реализовать difCallback в нашем адаптере, нам нужно различать модели и сравнивать нужные переменные:
companion object < object diffCallback : DiffUtil.ItemCallback() < override fun areItemsTheSame(oldItem: UIModel, newItem: UIModel): Boolean < val isSameRepoItem = oldItem is UIModel.FeedyModel && newItem is UIModel.FeedyModel && oldItem.feedItem.businessName == newItem.feedItem.businessName val isSameSeparatorItem = oldItem is UIModel.PromotionModel && newItem is UIModel.PromotionModel && oldItem.promotionItem.title == newItem.promotionItem.title return isSameRepoItem || isSameSeparatorItem >override fun areContentsTheSame(oldItem: UIModel, newItem: UIModel) = oldItem == newItem > >
Она похожа на стандартную реализацию diffCallback , но нам необходимо разделять типы. Создав ее, свяжите ее с адаптером в конструкторе.
Это все. Надеюсь, эта статья была для вас полезной. Спасибо за внимание!
Материал подготовлен в рамках специализации «Android Developer».
Всех желающих приглашаем на двухдневный онлайн-интенсив «Делаем мобильную мини-игру за 2 дня». За 2 дня вы сделаете мобильную версию PopIt на языке Kotlin. В приложении будет простая анимация, звук хлопка, вибрация, таймер как соревновательный элемент. Интенсив подойдет для тех, кто хочет попробовать себя в роли Android-разработчика.
>> РЕГИСТРАЦИЯ
- Блог компании OTUS
- Программирование
- Разработка под Android
