{"id":25507427,"url":"https://github.com/coder-chekunkov/article-recyclerview","last_synced_at":"2025-04-10T12:33:15.235Z","repository":{"id":189534523,"uuid":"573789878","full_name":"coder-chekunkov/article-recyclerView","owner":"coder-chekunkov","description":"article. recyclerView for beginner android developer.","archived":false,"fork":false,"pushed_at":"2024-01-08T22:40:18.000Z","size":4433,"stargazers_count":8,"open_issues_count":1,"forks_count":6,"subscribers_count":2,"default_branch":"main","last_synced_at":"2025-03-24T11:13:11.936Z","etag":null,"topics":["android","android-app","article","recyclerview"],"latest_commit_sha":null,"homepage":"","language":"Kotlin","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":null,"status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/coder-chekunkov.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":null,"code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null}},"created_at":"2022-12-03T12:49:07.000Z","updated_at":"2025-02-03T12:26:46.000Z","dependencies_parsed_at":null,"dependency_job_id":"90c546ed-b0ab-43ae-8c75-98771abf59f4","html_url":"https://github.com/coder-chekunkov/article-recyclerView","commit_stats":null,"previous_names":["coder-chekunkov/recyclerview-article","coder-chekunkov/article-recyclerview"],"tags_count":0,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/coder-chekunkov%2Farticle-recyclerView","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/coder-chekunkov%2Farticle-recyclerView/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/coder-chekunkov%2Farticle-recyclerView/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/coder-chekunkov%2Farticle-recyclerView/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/coder-chekunkov","download_url":"https://codeload.github.com/coder-chekunkov/article-recyclerView/tar.gz/refs/heads/main","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":248217145,"owners_count":21066633,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["android","android-app","article","recyclerview"],"created_at":"2025-02-19T07:31:48.533Z","updated_at":"2025-04-10T12:33:15.216Z","avatar_url":"https://github.com/coder-chekunkov.png","language":"Kotlin","funding_links":[],"categories":[],"sub_categories":[],"readme":"### :book: RecyclerView для начинающего Android-разработчика.\n\nДанная статья была написана [coder-chekunkov](https://github.com/coder-chekunkov) для начинающих Android-разработчиков. Тема: RecyclerView. \u003cbr/\u003e\n[Ссылка](https://habr.com/ru/sandbox/181814/) на статью на Habr.\n\n\n\u003cp align=\"center\"\u003e\n \u003cimg alt=\"GIF\" src=\"https://github.com/coder-chekunkov/RecyclerView-Article/blob/main/wiki_images/001.jpg\" width=\"220\"/\u003e\n \u003cimg alt=\"GIF\" src=\"https://github.com/coder-chekunkov/RecyclerView-Article/blob/main/wiki_images/002.jpg\" width=\"220\"/\u003e\n \u003cimg alt=\"GIF\" src=\"https://github.com/coder-chekunkov/RecyclerView-Article/blob/main/wiki_images/003.gif\" width=\"220\"/\u003e \u003cbr/\u003e\n\u003c/p\u003e\n\n---\n\nЗдравствуй, дорогой читатель. Каждый Android-разработчик сталкивался с задачей, в которой необходимо создать какой-то список, для отображения данных. Данная статья поможет новичку разобраться с таким очень важным и интересным компонентом, как RecyclerView.\n\nВ статье будет рассказано о том, почему необходимо использовать именно RecyclerView, описаны его основные компоненты и также будет разобран базовый, не очень сложный пример.\n\nСтатья предназначена для новичков, которые хотят разобраться со списками в Android.\n\n#### ListView или RecyclerView?\n\nДля реализации какого-то прокручиваемого списка у Android разработчика существуют два пути - [ListView](https://developer.android.com/reference/android/widget/ListView) и [RecyclerView](https://developer.android.com/reference/kotlin/androidx/recyclerview/widget/RecyclerView).\n\nПервый виджет интуитивно понятен и довольно прост. Но, к сожалению, имеет много недостатков, например, ListView позволяет создать только вертикальный список.\n\nВ свою же очередь RecyclerView \"из коробки\" предоставляет гораздо больше инструментов для кастомизации и оптимизации списка, чем ListView. Если кратко характеризовать RecyclerView, то можно сказать, что это список на стероидах.\n\nRecyclerView работает следующим образом: на экране устройства отображаются видимые элементы списка; при прокрутке списка верхний элемент уходит за пределы экрана и очищается, а после помещается вниз экрана и заполняется новыми данными.\n\n#### Основные компоненты RecyclerView.\n\nДля корректной работы RecyckerView необходимо реализовать следующие компоненты:\n\n- `RecyclerView`, который необходимо добавить в макет нашего Activity;\n- `Adapter`, который содержит, обрабатывает и связывает данные со списком;\n- `ViewHolder`, который служит для оптимизации ресурсов и является своеобразным контейнером для всех элементов, входящих в список;\n- `ItemDecorator`, который позволяет отрисовать весь декор;\n- `ItemAnimator`, который отвечает за анимацию элементов при добавлении, редактировании и других операций;\n- `DiffUtil`, который служит для оптимизации списка и добавления стандартных анимаций.\n\n#### Практический пример.\n\nВ качестве не сложного примера, создадим приложение со списком, в котором будут отображены данные о людях. Каждый человек будет иметь имя, название компании, фотографию и несколько операций над ним (показать уникальный номер, удалить, переместить вверх/вниз, лайкнуть).\n\nРеализация примера будет выполнена на языке Kotlin. Также будут использованы библиотеки [Glide](https://github.com/bumptech/glide) и [Faker](https://github.com/DiUS/java-faker), которые никак не относятся к RecyclerView.\n\nВ первую очередь **укажем все зависимости**, которые будут использованы приложением, в файл сборки `build.gradle` нашего приложения:\n\n```groovy\n    implementation 'androidx.recyclerview:recyclerview:1.2.1'\n    implementation 'com.github.javafaker:javafaker:1.0.2'\n    implementation 'com.github.bumptech.glide:glide:4.14.2'\n```\n\n*Примечание: в последних версиях AndroidStudio не обязательно подключать библиотеку RecyclerView. Доступен в библиотеке Material.*\n\nИ необходимо **подключить** [ViewBinding](https://developer.android.com/topic/libraries/view-binding) в файле сборки `build.gradle` нашего приложения:\n\n```groovy\nbuildFeatures {\n        viewBinding = true\n}\n```\n\nТакже необходимо указать **разрешение** на доступ в Интернет в файле `AndroidManifest.xml` (для работы с библиотекой Glide):\n\n```xml\n\u003cuses-permission android:name=\"android.permission.INTERNET\"/\u003e\n\u003cuses-permission android:name=\"android.permission.ACCESS_NETWORK_STATE\"/\u003e\n```\n\nПосле **создадим макетные файлы**: первый для ActivityMain, который хранит RecyclerView, второй для элемента списка (человек).\n\n*activity_main.xml*:\n\n```xml\n\u003c?xml version=\"1.0\" encoding=\"utf-8\"?\u003e\n\u003cFrameLayout xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    xmlns:tools=\"http://schemas.android.com/tools\"\n    android:id=\"@+id/activityMain\"\n    android:layout_width=\"match_parent\"\n    android:layout_height=\"match_parent\"\n    tools:context=\".MainActivity\"\u003e\n\n    \u003candroidx.recyclerview.widget.RecyclerView\n        android:id=\"@+id/recyclerView\"\n        android:layout_width=\"match_parent\"\n        android:layout_height=\"match_parent\" /\u003e\n\n\u003c/FrameLayout\u003e\n```\n\n*item_person.xml*\n\n```xml\n\u003c?xml version=\"1.0\" encoding=\"utf-8\"?\u003e\n\u003candroidx.constraintlayout.widget.ConstraintLayout xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    xmlns:app=\"http://schemas.android.com/apk/res-auto\"\n    xmlns:tools=\"http://schemas.android.com/tools\"\n    android:layout_width=\"match_parent\"\n    android:layout_height=\"wrap_content\"\n    android:background=\"?selectableItemBackground\"\n    android:paddingStart=\"10dp\"\n    android:paddingTop=\"5dp\"\n    android:paddingEnd=\"10dp\"\n    android:paddingBottom=\"5dp\"\u003e\n\n    \u003cImageView\n        android:id=\"@+id/imageView\"\n        android:layout_width=\"wrap_content\"\n        android:layout_height=\"match_parent\"\n        android:src=\"@drawable/ic_person\"\n        app:layout_constraintBottom_toBottomOf=\"parent\"\n        app:layout_constraintStart_toStartOf=\"parent\"\n        app:layout_constraintTop_toTopOf=\"parent\" /\u003e\n\n    \u003cTextView\n        android:id=\"@+id/nameTextView\"\n        style=\"@style/TextAppearance.AppCompat.Body2\"\n        android:layout_width=\"wrap_content\"\n        android:layout_height=\"wrap_content\"\n        android:layout_marginStart=\"15dp\"\n        app:layout_constraintStart_toEndOf=\"@id/imageView\"\n        app:layout_constraintTop_toTopOf=\"parent\" /\u003e\n\n    \u003cTextView\n        android:id=\"@+id/companyTextView\"\n        android:layout_width=\"wrap_content\"\n        android:layout_height=\"wrap_content\"\n        android:layout_marginStart=\"15dp\"\n        app:layout_constraintStart_toEndOf=\"@id/imageView\"\n        app:layout_constraintTop_toBottomOf=\"@id/nameTextView\" /\u003e\n\n    \u003cImageView\n        android:id=\"@+id/likedImageView\"\n        android:layout_width=\"wrap_content\"\n        android:layout_height=\"wrap_content\"\n        android:layout_marginEnd=\"15dp\"\n        android:src=\"@drawable/ic_like\"\n        app:layout_constraintBottom_toBottomOf=\"parent\"\n        app:layout_constraintEnd_toStartOf=\"@id/more\"\n        app:layout_constraintTop_toTopOf=\"parent\" /\u003e\n\n    \u003cImageView\n        android:id=\"@+id/more\"\n        android:layout_width=\"wrap_content\"\n        android:layout_height=\"wrap_content\"\n        android:src=\"@drawable/ic_more\"\n        app:layout_constraintBottom_toBottomOf=\"parent\"\n        app:layout_constraintEnd_toEndOf=\"parent\"\n        app:layout_constraintTop_toTopOf=\"parent\" /\u003e\n\n\u003c/androidx.constraintlayout.widget.ConstraintLayout\u003e\n```\n\nПосле того, как все библиотеки были подключены, все макеты созданы и разрешения получены, необходимо **создать данные о людях** (в настоящем, боевом примере эти данные будут приходить, например, с сервера, но в нашем случае мы создадим их самостоятельно). Для этого создадим класс *PersonService* и data-class *Person*:\n\n```kotlin\ndata class Person(\n    val id: Long, // Уникальный номер пользователя\n    val name: String, // Имя человека\n    val companyName: String, // Название комании\n    val photo: String, // Ссылка на фото человека\n    val isLiked: Boolean // Был ли лайкнут пользователь\n)\n```\n\n```kotlin\nclass PersonService {\n\n    private var persons = mutableListOf\u003cPerson\u003e() // Все пользователи\n\n    init {\n        val faker = Faker.instance() // Переменная для создания случайных данных\n\n        persons = (1..50).map {\n            Person(\n                id = it.toLong(),\n                name = faker.name().fullName(),\n                companyName = faker.company().name(),\n                photo = IMAGES[it % IMAGES.size],\n                isLiked = false\n            )\n        }.toMutableList()\n    }\n\n    companion object {\n        private val IMAGES = mutableListOf(\n            \"https://images.unsplash.com/photo-1600267185393-e158a98703de?crop=entropy\u0026cs=tinysrgb\u0026fit=crop\u0026fm=jpg\u0026h=600\u0026ixid=MnwxfDB8MXxyYW5kb218fHx8fHx8fHwxNjI0MDE0NjQ0\u0026ixlib=rb-1.2.1\u0026q=80\u0026utm_campaign=api-credit\u0026utm_medium=referral\u0026utm_source=unsplash_source\u0026w=800\",\n            \"https://images.unsplash.com/photo-1579710039144-85d6bdffddc9?crop=entropy\u0026cs=tinysrgb\u0026fit=crop\u0026fm=jpg\u0026h=600\u0026ixid=MnwxfDB8MXxyYW5kb218fHx8fHx8fHwxNjI0MDE0Njk1\u0026ixlib=rb-1.2.1\u0026q=80\u0026utm_campaign=api-credit\u0026utm_medium=referral\u0026utm_source=unsplash_source\u0026w=800\",\n            \"https://images.unsplash.com/photo-1488426862026-3ee34a7d66df?crop=entropy\u0026cs=tinysrgb\u0026fit=crop\u0026fm=jpg\u0026h=600\u0026ixid=MnwxfDB8MXxyYW5kb218fHx8fHx8fHwxNjI0MDE0ODE0\u0026ixlib=rb-1.2.1\u0026q=80\u0026utm_campaign=api-credit\u0026utm_medium=referral\u0026utm_source=unsplash_source\u0026w=800\",\n            \"https://images.unsplash.com/photo-1620252655460-080dbec533ca?crop=entropy\u0026cs=tinysrgb\u0026fit=crop\u0026fm=jpg\u0026h=600\u0026ixid=MnwxfDB8MXxyYW5kb218fHx8fHx8fHwxNjI0MDE0NzQ1\u0026ixlib=rb-1.2.1\u0026q=80\u0026utm_campaign=api-credit\u0026utm_medium=referral\u0026utm_source=unsplash_source\u0026w=800\",\n            \"https://images.unsplash.com/photo-1613679074971-91fc27180061?crop=entropy\u0026cs=tinysrgb\u0026fit=crop\u0026fm=jpg\u0026h=600\u0026ixid=MnwxfDB8MXxyYW5kb218fHx8fHx8fHwxNjI0MDE0NzUz\u0026ixlib=rb-1.2.1\u0026q=80\u0026utm_campaign=api-credit\u0026utm_medium=referral\u0026utm_source=unsplash_source\u0026w=800\",\n            \"https://images.unsplash.com/photo-1485795959911-ea5ebf41b6ae?crop=entropy\u0026cs=tinysrgb\u0026fit=crop\u0026fm=jpg\u0026h=600\u0026ixid=MnwxfDB8MXxyYW5kb218fHx8fHx8fHwxNjI0MDE0NzU4\u0026ixlib=rb-1.2.1\u0026q=80\u0026utm_campaign=api-credit\u0026utm_medium=referral\u0026utm_source=unsplash_source\u0026w=800\",\n            \"https://images.unsplash.com/photo-1545996124-0501ebae84d0?crop=entropy\u0026cs=tinysrgb\u0026fit=crop\u0026fm=jpg\u0026h=600\u0026ixid=MnwxfDB8MXxyYW5kb218fHx8fHx8fHwxNjI0MDE0NzY1\u0026ixlib=rb-1.2.1\u0026q=80\u0026utm_campaign=api-credit\u0026utm_medium=referral\u0026utm_source=unsplash_source\u0026w=800\",\n            \"https://images.unsplash.com/flagged/photo-1568225061049-70fb3006b5be?crop=entropy\u0026cs=tinysrgb\u0026fit=crop\u0026fm=jpg\u0026h=600\u0026ixid=MnwxfDB8MXxyYW5kb218fHx8fHx8fHwxNjI0MDE0Nzcy\u0026ixlib=rb-1.2.1\u0026q=80\u0026utm_campaign=api-credit\u0026utm_medium=referral\u0026utm_source=unsplash_source\u0026w=800\",\n            \"https://images.unsplash.com/photo-1567186937675-a5131c8a89ea?crop=entropy\u0026cs=tinysrgb\u0026fit=crop\u0026fm=jpg\u0026h=600\u0026ixid=MnwxfDB8MXxyYW5kb218fHx8fHx8fHwxNjI0MDE0ODYx\u0026ixlib=rb-1.2.1\u0026q=80\u0026utm_campaign=api-credit\u0026utm_medium=referral\u0026utm_source=unsplash_source\u0026w=800\",\n            \"https://images.unsplash.com/photo-1546456073-92b9f0a8d413?crop=entropy\u0026cs=tinysrgb\u0026fit=crop\u0026fm=jpg\u0026h=600\u0026ixid=MnwxfDB8MXxyYW5kb218fHx8fHx8fHwxNjI0MDE0ODY1\u0026ixlib=rb-1.2.1\u0026q=80\u0026utm_campaign=api-credit\u0026utm_medium=referral\u0026utm_source=unsplash_source\u0026w=800\"\n        )\n    }\n}\n```\n\nВ классе PersonService хранится лист пользователей, который мы заполняем в `init` (initializers blocks), и лист ссылок на фотографии.\n\nПосле создания классов необходимо класс PersonService сделать `Singleton` для корректной работы. Для этого создадим класс *App*, и укажем в нем следующее:\n\n```kotlin\nclass App : Application() {\n    val personService = PersonService()\n}\n```\n\nТеперь **реализуем адаптер** *PersonAdapter*, который будет обрабатывать наши данные и связывать их со списком.\n\nДанный класс будет реализовать `RecyclerView.Adapter`, которому нужен ViewHolder. Соответственно необходимо создать *PersonViewHolder*, который будет реализовывать `RecyclerView.ViewHolder` и принимать наш binding.\n\nТакже PersonAdapter должен иметь данные, с которыми ему предстоит работать. Для этого создадим пустой список и перепишем его сеттер. В итоге получаем:\n\n```kotlin\nclass PersonAdapter : RecyclerView.Adapter\u003cPersonAdapter.PersonViewHolder\u003e() {\n\n    var data: List\u003cPerson\u003e = emptyList()\n        set(newValue) {\n            field = newValue\n            notifyDataSetChanged()\n        }\n\n    class PersonViewHolder(val binding: ItemPersonBinding) : RecyclerView.ViewHolder(binding.root)\n}\n```\n\nНо для работы адаптера необходимо переопределить минимум три метода (AndroidStudio подскажет нам).\n\nМетод `getItemCount`, который будет возвращать количество элементов нашего списка с данными; \u003cbr/\u003e\nМетод `onCreateViewHolder`, в котором будет происходить создание ViewHolder. Данный метод принимает в себя *parent* и *viewType* (используется в том случае, если в списке будут разные типы элементов списка); \u003cbr/\u003e\nМетод `onBindViewHolder`, в котором будет происходить отрисовка всех элементов в объекте списка (имя человека, компания и т.д.):\n\nПосле переопределения методов и их реализации получаем:\n\n```kotlin\n    override fun getItemCount(): Int = data.size // Количество элементов в списке данных\n\n    override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): PersonViewHolder {\n        val inflater = LayoutInflater.from(parent.context)\n        val binding = ItemPersonBinding.inflate(inflater, parent, false)\n\n        return PersonViewHolder(binding)\n    }\n\n    override fun onBindViewHolder(holder: PersonViewHolder, position: Int) {\n        val person = data[position] // Получение человека из списка данных по позиции\n        val context = holder.itemView.context\n\n        with(holder.binding) {\n            val color = if (person.isLiked) R.color.red else R.color.grey // Цвет \"сердца\", если пользователь был лайкнут\n\n            nameTextView.text = person.name // Отрисовка имени пользователя\n            companyTextView.text = person.companyName // Отрисовка компании пользователя\n            likedImageView.setColorFilter( // Отрисовка цвета \"сердца\"\n                ContextCompat.getColor(context, color),\n                android.graphics.PorterDuff.Mode.SRC_IN\n            )\n            Glide.with(context).load(person.photo).circleCrop() // Отрисовка фотографии пользователя с помощью библиотеки Glide\n                .error(R.drawable.ic_person) \n                .placeholder(R.drawable.ic_person).into(imageView)\n        }\n    }\n```\n\nНа этом наш простой адаптер, который будет просто выводить горизонтальный список готов.\n\nТеперь необходимо повесить на наш RecyclerView созданный адаптер и LayoutManager. Для этого в классе MainActivity пропишем следующее:\n\n```kotlin\nclass MainActivity : AppCompatActivity() {\n\n    private lateinit var binding: ActivityMainBinding\n    private lateinit var adapter: PersonAdapter // Объект Adapter\n    private val personService: PersonService // Объект PersonService\n        get() = (applicationContext as App).personService\n\n    override fun onCreate(savedInstanceState: Bundle?) {\n        super.onCreate(savedInstanceState)\n        binding = ActivityMainBinding.inflate(layoutInflater)\n        setContentView(binding.root)\n\n        val manager = LinearLayoutManager(this) // LayoutManager\n        adapter = PersonAdapter() // Создание объекта\n        adapter.data = personService.getPersons() // Заполнение данными\n\n        binding.recyclerView.layoutManager = manager // Назначение LayoutManager для RecyclerView\n        binding.recyclerView.adapter = adapter // Назначение адаптера для RecyclerView\n    }\n}\n```\n\nЗапускаем наше приложение на устройстве и получаем список пользователей!\n\nНа данном этапе наше приложение просто выводит данные, которые мы можем прокручивать, но взаимодействовать с ними у нас не получится. Исправим это.\n\nВ классе PersonService добавим три метода:\n\n- *likePerson* - лайкаем человека;\n- *removePerson* - удаляем человека; \n- *movePerson* - перемещаем человека (принимает человека и куда надо переместить: \"1\" - вниз, \"-1\" - вверх).\n  \n\n```kotlin\n    fun likePerson(person: Person) {\n        val index = persons.indexOfFirst { it.id == person.id } // Находим индекс человека в списке\n        if (index == -1) return // Останавливаемся, если не находим такого человека\n\n        persons = ArrayList(persons) // Создаем новый список\n        persons[index] = persons[index].copy(isLiked = !persons[index].isLiked) // Меняем значение \"лайка\" на противоположное\n    }\n\n    fun removePerson(person: Person) {\n        val index = persons.indexOfFirst { it.id == person.id } // Находим индекс человека в списке\n        if (index == -1) return // Останавливаемся, если не находим такого человека\n\n        persons = ArrayList(persons) // Создаем новый список\n        persons.removeAt(index) // Удаляем человека\n    }\n\n    fun movePerson(person: Person, moveBy: Int) {\n        val oldIndex = persons.indexOfFirst { it.id == person.id } // Находим индекс человека в списке\n        if (oldIndex == -1) return // Останавливаемся, если не находим такого человека\n\n        val newIndex = oldIndex + moveBy // Вычисляем новый индекс, на котором должен находится человек\n        persons = ArrayList(persons) // Создаем новый список\n        Collections.swap(persons, oldIndex, newIndex) // Меняем местами людей        \n    }\n```\n\nПосле того, как были созданы методы взаимодействия с людьми, в классе PersonService необходимо **объявить слушателя**:\n\n```kotlin\ntypealias PersonListener = (persons: List\u003cPerson\u003e) -\u003e Unit\n```\n\nТак же **создадим** список слушателей, два метода, которые будут добавлять и удалять слушателей, и один, который будет \"регистрировать изменения\":\n\n```kotlin\n    private var listeners = mutableListOf\u003cPersonListener\u003e() // Все слушатели\n\n    fun addListener(listener: PersonListener) {\n        listeners.add(listener)\n        listener.invoke(persons)\n    }\n\n    fun removeListener(listener: PersonListener) {\n        listeners.remove(listener)\n        listener.invoke(persons)\n    }\n\n    private fun notifyChanges() = listeners.forEach { it.invoke(persons) }\n```\n\nМетод *notifyChanges* необходимо обязательно вызвать в методах, в которых происходит модификация данных, то есть в методах likePerson, removePerson и movePerson.\n\nНа этом наш сервис людей полностью готов. Перейдем в PersonAdapter, в котором **реализуем обработку событий** наших людей. Создадим интерфейс *PersonActionListener*, в котором буду четыре метода:\n\n- *onPersonGetId* - получить уникальный номер выбранного человека;\n- *onPersonLike* - человек был лайкнут;\n- *onPersonRemove* - удалить человека;\n- *onPersonMove* - переместить человека.\n  \n\n```kotlin\ninterface PersonActionListener {\n    fun onPersonGetId(person: Person)\n    fun onPersonLike(person: Person)\n    fun onPersonRemove(person: Person)\n    fun onPersonMove(person: Person, moveBy: Int)\n}\n```\n\nКласс PersonAdapter во входные параметры будет принимать наш интерфейс. Также данный класс должен реализовать интерфейс *OnClickListener*. В итоге сигнатура объявления класса PersonAdaper выглядит следующим образом:\n\n```kotlin\nclass PersonAdapter(private val personActionListener: PersonActionListener) :\n    RecyclerView.Adapter\u003cPersonAdapter.PersonViewHolder\u003e(), View.OnClickListener {\n```\n\nТеперь в классе PersonAdapter в методе onBindViewHolder кладём в tag каждого view, на которую будет происходить нажатие, нужного человека:\n\n```kotlin\nholder.itemView.tag = person\nholder.binding.likedImageView.tag = person\nholder.binding.more.tag = person\n```\n\nТеперь в методе onCreateViewHolder необходимо проинициализировать слушателей при нажатии. В данном примере будет слушатель на нажатие на элемент списка, кнопку more (три точки) и likedImageView (сердце):\n\n```kotlin\nbinding.root.setOnClickListener(this)\nbinding.more.setOnClickListener(this)\nbinding.likedImageView.setOnClickListener(this)\n```\n\nТеперь создадим метод *showPopupMenu*, который будет \"рисовать\" выпадающее меню с доступными действиями, а именно: удалить пользователя, переместить вверх, переместить вниз:\n\n```kotlin\n    private fun showPopupMenu(view: View) {\n        val popupMenu = PopupMenu(view.context, view)\n        val person = view.tag as Person\n        val position = data.indexOfFirst { it.id == person.id }\n\n        popupMenu.menu.add(0, ID_MOVE_UP, Menu.NONE, \"Up\").apply {\n            isEnabled = position \u003e 0\n        }\n        popupMenu.menu.add(0, ID_MOVE_DOWN, Menu.NONE, \"Down\").apply {\n            isEnabled = position \u003c data.size - 1\n        }\n        popupMenu.menu.add(0, ID_REMOVE, Menu.NONE, \"Remove\")\n\n        popupMenu.setOnMenuItemClickListener {\n            when (it.itemId) {\n                ID_MOVE_UP -\u003e personActionListener.onPersonMove(person, -1)\n                ID_MOVE_DOWN -\u003e personActionListener.onPersonMove(person, 1)\n                ID_REMOVE -\u003e personActionListener.onPersonRemove(person)\n            }\n            return@setOnMenuItemClickListener true\n        }\n\n        popupMenu.show()\n    }\n\n    companion object {\n        private const val ID_MOVE_UP = 1\n        private const val ID_MOVE_DOWN = 2\n        private const val ID_REMOVE = 3\n    }\n```\n\nВ методе *onClick* обработаем нажатия на элементы списка:\n\n```kotlin\n    override fun onClick(view: View) {\n        val person: Person = view.tag as Person // Получаем из тэга человека\n\n        when (view.id) {\n            R.id.more -\u003e showPopupMenu(view)\n            R.id.likedImageView -\u003e personActionListener.onPersonLike(person)\n            else -\u003e personActionListener.onPersonGetId(person)\n        }\n    }\n```\n\nНа этом наш адаптер готов. Теперь перейдем в MainActivity и, при инициализации нашего адаптера, передадим реализацию интерфейса:\n\n```kotlin\nadapter = PersonAdapter(object : PersonActionListener { // Создание объекта\n   override fun onPersonGetId(person: Person) =\n      Toast.makeText(this@MainActivity, \"Persons ID: ${person.id}\", Toast.LENGTH_SHORT).show()\n\n   override fun onPersonLike(person: Person) = personService.likePerson(person)\n\n   override fun onPersonRemove(person: Person) = personService.removePerson(person)\n\n   override fun onPersonMove(person: Person, moveBy: Int) = personService.movePerson(person, moveBy)\n\n})\n```\n\nТакже добавим слушателя в MainActivity, который будет прослушивать изменения, происходящие в PersonService:\n\n```kotlin\nprivate val listener: PersonListener = {adapter.data = it}\n```\n\nИ в методе onCreate добавим этого слушателя:\n\n```kotlin\npersonService.addListener(listener)\n```\n\nНа этом наша работа выполнена. Запускаем проект и смотрим результат:\n\n\u003cp align=\"center\"\u003e\n \u003cimg alt=\"GIF\" src=\"https://github.com/coder-chekunkov/RecyclerView-Article/blob/main/wiki_images/003.gif\" width=\"220\"/\u003e \u003cbr/\u003e\n\u003c/p\u003e\n\nМы рассмотрели основы RecyclerView. Естественно это далеко не все, что позволяет сделать этот мощный инструмент. Всегда можно добавить DiffUtil, который поможет оптимизировать список, ItemDecorator, для декора наших элементов и т.д. \u003cbr/\u003e\n[Ссылка](https://habr.com/ru/sandbox/181814/) на статью на Habr.\n\n---\n\n🏆 Я надеюсь, что данная работа помогла Вам. \u003cbr/\u003e\n📧 При возникновении каких-либо вопросов и предложений - свяжитесь со мной. \u003cbr/\u003e\n🤝 Спасибо, что заинтересовались данной работой.\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fcoder-chekunkov%2Farticle-recyclerview","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fcoder-chekunkov%2Farticle-recyclerview","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fcoder-chekunkov%2Farticle-recyclerview/lists"}