Skip to content

πŸ“– RecyclerView for beginner android developer. Article. // RecyclerView для Π½Π°Ρ‡ΠΈΠ½Π°ΡŽΡ‰Π΅Π³ΠΎ Android-Ρ€Π°Π·Ρ€Π°Π±ΠΎΡ‚Ρ‡ΠΈΠΊΠ°. Π‘Ρ‚Π°Ρ‚ΡŒΡ.

Notifications You must be signed in to change notification settings

sofijasternad/RecyclerView-Article

This branch is up to date with coder-chekunkov/article-recyclerView:main.

Repository files navigation

πŸ“– RecyclerView для Π½Π°Ρ‡ΠΈΠ½Π°ΡŽΡ‰Π΅Π³ΠΎ Android-Ρ€Π°Π·Ρ€Π°Π±ΠΎΡ‚Ρ‡ΠΈΠΊΠ°.

Данная ΡΡ‚Π°Ρ‚ΡŒΡ Π±Ρ‹Π»Π° написана coder-chekunkov для Π½Π°Ρ‡ΠΈΠ½Π°ΡŽΡ‰ΠΈΡ… Android-Ρ€Π°Π·Ρ€Π°Π±ΠΎΡ‚Ρ‡ΠΈΠΊΠΎΠ². Π’Π΅ΠΌΠ°: RecyclerView.
Бсылка Π½Π° ΡΡ‚Π°Ρ‚ΡŒΡŽ Π½Π° Habr.

GIF GIF GIF


Здравствуй, Π΄ΠΎΡ€ΠΎΠ³ΠΎΠΉ Ρ‡ΠΈΡ‚Π°Ρ‚Π΅Π»ΡŒ. ΠšΠ°ΠΆΠ΄Ρ‹ΠΉ Android-Ρ€Π°Π·Ρ€Π°Π±ΠΎΡ‚Ρ‡ΠΈΠΊ сталкивался с Π·Π°Π΄Π°Ρ‡Π΅ΠΉ, Π² ΠΊΠΎΡ‚ΠΎΡ€ΠΎΠΉ Π½Π΅ΠΎΠ±Ρ…ΠΎΠ΄ΠΈΠΌΠΎ ΡΠΎΠ·Π΄Π°Ρ‚ΡŒ ΠΊΠ°ΠΊΠΎΠΉ-Ρ‚ΠΎ список, для отобраТСния Π΄Π°Π½Π½Ρ‹Ρ…. Данная ΡΡ‚Π°Ρ‚ΡŒΡ ΠΏΠΎΠΌΠΎΠΆΠ΅Ρ‚ Π½ΠΎΠ²ΠΈΡ‡ΠΊΡƒ Ρ€Π°Π·ΠΎΠ±Ρ€Π°Ρ‚ΡŒΡΡ с Ρ‚Π°ΠΊΠΈΠΌ ΠΎΡ‡Π΅Π½ΡŒ Π²Π°ΠΆΠ½Ρ‹ΠΌ ΠΈ интСрСсным ΠΊΠΎΠΌΠΏΠΎΠ½Π΅Π½Ρ‚ΠΎΠΌ, ΠΊΠ°ΠΊ RecyclerView.

Π’ ΡΡ‚Π°Ρ‚ΡŒΠ΅ Π±ΡƒΠ΄Π΅Ρ‚ рассказано ΠΎ Ρ‚ΠΎΠΌ, ΠΏΠΎΡ‡Π΅ΠΌΡƒ Π½Π΅ΠΎΠ±Ρ…ΠΎΠ΄ΠΈΠΌΠΎ ΠΈΡΠΏΠΎΠ»ΡŒΠ·ΠΎΠ²Π°Ρ‚ΡŒ ΠΈΠΌΠ΅Π½Π½ΠΎ RecyclerView, описаны Π΅Π³ΠΎ основныС ΠΊΠΎΠΌΠΏΠΎΠ½Π΅Π½Ρ‚Ρ‹ ΠΈ Ρ‚Π°ΠΊΠΆΠ΅ Π±ΡƒΠ΄Π΅Ρ‚ Ρ€Π°Π·ΠΎΠ±Ρ€Π°Π½ Π±Π°Π·ΠΎΠ²Ρ‹ΠΉ, Π½Π΅ ΠΎΡ‡Π΅Π½ΡŒ слоТный ΠΏΡ€ΠΈΠΌΠ΅Ρ€.

Π‘Ρ‚Π°Ρ‚ΡŒΡ ΠΏΡ€Π΅Π΄Π½Π°Π·Π½Π°Ρ‡Π΅Π½Π° для Π½ΠΎΠ²ΠΈΡ‡ΠΊΠΎΠ², ΠΊΠΎΡ‚ΠΎΡ€Ρ‹Π΅ хотят Ρ€Π°Π·ΠΎΠ±Ρ€Π°Ρ‚ΡŒΡΡ со списками Π² Android.

ListView ΠΈΠ»ΠΈ RecyclerView?

Для Ρ€Π΅Π°Π»ΠΈΠ·Π°Ρ†ΠΈΠΈ ΠΊΠ°ΠΊΠΎΠ³ΠΎ-Ρ‚ΠΎ ΠΏΡ€ΠΎΠΊΡ€ΡƒΡ‡ΠΈΠ²Π°Π΅ΠΌΠΎΠ³ΠΎ списка Ρƒ Android Ρ€Π°Π·Ρ€Π°Π±ΠΎΡ‚Ρ‡ΠΈΠΊΠ° ΡΡƒΡ‰Π΅ΡΡ‚Π²ΡƒΡŽΡ‚ Π΄Π²Π° ΠΏΡƒΡ‚ΠΈ - ListView ΠΈ RecyclerView.

ΠŸΠ΅Ρ€Π²Ρ‹ΠΉ Π²ΠΈΠ΄ΠΆΠ΅Ρ‚ ΠΈΠ½Ρ‚ΡƒΠΈΡ‚ΠΈΠ²Π½ΠΎ понятСн ΠΈ довольно прост. Но, ΠΊ соТалСнию, ΠΈΠΌΠ΅Π΅Ρ‚ ΠΌΠ½ΠΎΠ³ΠΎ нСдостатков, Π½Π°ΠΏΡ€ΠΈΠΌΠ΅Ρ€, ListView позволяСт ΡΠΎΠ·Π΄Π°Ρ‚ΡŒ Ρ‚ΠΎΠ»ΡŒΠΊΠΎ Π²Π΅Ρ€Ρ‚ΠΈΠΊΠ°Π»ΡŒΠ½Ρ‹ΠΉ список.

Π’ свою ΠΆΠ΅ ΠΎΡ‡Π΅Ρ€Π΅Π΄ΡŒ RecyclerView "ΠΈΠ· ΠΊΠΎΡ€ΠΎΠ±ΠΊΠΈ" прСдоставляСт Π³ΠΎΡ€Π°Π·Π΄ΠΎ большС инструмСнтов для кастомизации ΠΈ ΠΎΠΏΡ‚ΠΈΠΌΠΈΠ·Π°Ρ†ΠΈΠΈ списка, Ρ‡Π΅ΠΌ ListView. Если ΠΊΡ€Π°Ρ‚ΠΊΠΎ Ρ…Π°Ρ€Π°ΠΊΡ‚Π΅Ρ€ΠΈΠ·ΠΎΠ²Π°Ρ‚ΡŒ RecyclerView, Ρ‚ΠΎ ΠΌΠΎΠΆΠ½ΠΎ ΡΠΊΠ°Π·Π°Ρ‚ΡŒ, Ρ‡Ρ‚ΠΎ это список Π½Π° стСроидах.

RecyclerView Ρ€Π°Π±ΠΎΡ‚Π°Π΅Ρ‚ ΡΠ»Π΅Π΄ΡƒΡŽΡ‰ΠΈΠΌ ΠΎΠ±Ρ€Π°Π·ΠΎΠΌ: Π½Π° экранС устройства ΠΎΡ‚ΠΎΠ±Ρ€Π°ΠΆΠ°ΡŽΡ‚ΡΡ Π²ΠΈΠ΄ΠΈΠΌΡ‹Π΅ элСмСнты списка; ΠΏΡ€ΠΈ ΠΏΡ€ΠΎΠΊΡ€ΡƒΡ‚ΠΊΠ΅ списка Π²Π΅Ρ€Ρ…Π½ΠΈΠΉ элСмСнт ΡƒΡ…ΠΎΠ΄ΠΈΡ‚ Π·Π° ΠΏΡ€Π΅Π΄Π΅Π»Ρ‹ экрана ΠΈ очищаСтся, Π° послС помСщаСтся Π²Π½ΠΈΠ· экрана ΠΈ заполняСтся Π½ΠΎΠ²Ρ‹ΠΌΠΈ Π΄Π°Π½Π½Ρ‹ΠΌΠΈ.

ΠžΡΠ½ΠΎΠ²Π½Ρ‹Π΅ ΠΊΠΎΠΌΠΏΠΎΠ½Π΅Π½Ρ‚Ρ‹ RecyclerView.

Для ΠΊΠΎΡ€Ρ€Π΅ΠΊΡ‚Π½ΠΎΠΉ Ρ€Π°Π±ΠΎΡ‚Ρ‹ RecyckerView Π½Π΅ΠΎΠ±Ρ…ΠΎΠ΄ΠΈΠΌΠΎ Ρ€Π΅Π°Π»ΠΈΠ·ΠΎΠ²Π°Ρ‚ΡŒ ΡΠ»Π΅Π΄ΡƒΡŽΡ‰ΠΈΠ΅ ΠΊΠΎΠΌΠΏΠΎΠ½Π΅Π½Ρ‚Ρ‹:

  • RecyclerView, ΠΊΠΎΡ‚ΠΎΡ€Ρ‹ΠΉ Π½Π΅ΠΎΠ±Ρ…ΠΎΠ΄ΠΈΠΌΠΎ Π΄ΠΎΠ±Π°Π²ΠΈΡ‚ΡŒ Π² ΠΌΠ°ΠΊΠ΅Ρ‚ нашСго Activity;
  • Adapter, ΠΊΠΎΡ‚ΠΎΡ€Ρ‹ΠΉ содСрТит, ΠΎΠ±Ρ€Π°Π±Π°Ρ‚Ρ‹Π²Π°Π΅Ρ‚ ΠΈ связываСт Π΄Π°Π½Π½Ρ‹Π΅ со списком;
  • ViewHolder, ΠΊΠΎΡ‚ΠΎΡ€Ρ‹ΠΉ слуТит для ΠΎΠΏΡ‚ΠΈΠΌΠΈΠ·Π°Ρ†ΠΈΠΈ рСсурсов ΠΈ являСтся своСобразным ΠΊΠΎΠ½Ρ‚Π΅ΠΉΠ½Π΅Ρ€ΠΎΠΌ для всСх элСмСнтов, входящих Π² список;
  • ItemDecorator, ΠΊΠΎΡ‚ΠΎΡ€Ρ‹ΠΉ позволяСт ΠΎΡ‚Ρ€ΠΈΡΠΎΠ²Π°Ρ‚ΡŒ вСсь Π΄Π΅ΠΊΠΎΡ€;
  • ItemAnimator, ΠΊΠΎΡ‚ΠΎΡ€Ρ‹ΠΉ ΠΎΡ‚Π²Π΅Ρ‡Π°Π΅Ρ‚ Π·Π° Π°Π½ΠΈΠΌΠ°Ρ†ΠΈΡŽ элСмСнтов ΠΏΡ€ΠΈ Π΄ΠΎΠ±Π°Π²Π»Π΅Π½ΠΈΠΈ, Ρ€Π΅Π΄Π°ΠΊΡ‚ΠΈΡ€ΠΎΠ²Π°Π½ΠΈΠΈ ΠΈ Π΄Ρ€ΡƒΠ³ΠΈΡ… ΠΎΠΏΠ΅Ρ€Π°Ρ†ΠΈΠΉ;
  • DiffUtil, ΠΊΠΎΡ‚ΠΎΡ€Ρ‹ΠΉ слуТит для ΠΎΠΏΡ‚ΠΈΠΌΠΈΠ·Π°Ρ†ΠΈΠΈ списка ΠΈ добавлСния стандартных Π°Π½ΠΈΠΌΠ°Ρ†ΠΈΠΉ.

ΠŸΡ€Π°ΠΊΡ‚ΠΈΡ‡Π΅ΡΠΊΠΈΠΉ ΠΏΡ€ΠΈΠΌΠ΅Ρ€.

Π’ качСствС Π½Π΅ слоТного ΠΏΡ€ΠΈΠΌΠ΅Ρ€Π°, создадим ΠΏΡ€ΠΈΠ»ΠΎΠΆΠ΅Π½ΠΈΠ΅ со списком, Π² ΠΊΠΎΡ‚ΠΎΡ€ΠΎΠΌ Π±ΡƒΠ΄ΡƒΡ‚ ΠΎΡ‚ΠΎΠ±Ρ€Π°ΠΆΠ΅Π½Ρ‹ Π΄Π°Π½Π½Ρ‹Π΅ ΠΎ Π»ΡŽΠ΄ΡΡ…. ΠšΠ°ΠΆΠ΄Ρ‹ΠΉ Ρ‡Π΅Π»ΠΎΠ²Π΅ΠΊ Π±ΡƒΠ΄Π΅Ρ‚ ΠΈΠΌΠ΅Ρ‚ΡŒ имя, Π½Π°Π·Π²Π°Π½ΠΈΠ΅ ΠΊΠΎΠΌΠΏΠ°Π½ΠΈΠΈ, Ρ„ΠΎΡ‚ΠΎΠ³Ρ€Π°Ρ„ΠΈΡŽ ΠΈ нСсколько ΠΎΠΏΠ΅Ρ€Π°Ρ†ΠΈΠΉ Π½Π°Π΄ Π½ΠΈΠΌ (ΠΏΠΎΠΊΠ°Π·Π°Ρ‚ΡŒ ΡƒΠ½ΠΈΠΊΠ°Π»ΡŒΠ½Ρ‹ΠΉ Π½ΠΎΠΌΠ΅Ρ€, ΡƒΠ΄Π°Π»ΠΈΡ‚ΡŒ, ΠΏΠ΅Ρ€Π΅ΠΌΠ΅ΡΡ‚ΠΈΡ‚ΡŒ Π²Π²Π΅Ρ€Ρ…/Π²Π½ΠΈΠ·, Π»Π°ΠΉΠΊΠ½ΡƒΡ‚ΡŒ).

РСализация ΠΏΡ€ΠΈΠΌΠ΅Ρ€Π° Π±ΡƒΠ΄Π΅Ρ‚ Π²Ρ‹ΠΏΠΎΠ»Π½Π΅Π½Π° Π½Π° языкС Kotlin. Π’Π°ΠΊΠΆΠ΅ Π±ΡƒΠ΄ΡƒΡ‚ ΠΈΡΠΏΠΎΠ»ΡŒΠ·ΠΎΠ²Π°Π½Ρ‹ Π±ΠΈΠ±Π»ΠΈΠΎΡ‚Π΅ΠΊΠΈ Glide ΠΈ Faker, ΠΊΠΎΡ‚ΠΎΡ€Ρ‹Π΅ Π½ΠΈΠΊΠ°ΠΊ Π½Π΅ относятся ΠΊ RecyclerView.

Π’ ΠΏΠ΅Ρ€Π²ΡƒΡŽ ΠΎΡ‡Π΅Ρ€Π΅Π΄ΡŒ ΡƒΠΊΠ°ΠΆΠ΅ΠΌ всС зависимости, ΠΊΠΎΡ‚ΠΎΡ€Ρ‹Π΅ Π±ΡƒΠ΄ΡƒΡ‚ ΠΈΡΠΏΠΎΠ»ΡŒΠ·ΠΎΠ²Π°Π½Ρ‹ ΠΏΡ€ΠΈΠ»ΠΎΠΆΠ΅Π½ΠΈΠ΅ΠΌ, Π² Ρ„Π°ΠΉΠ» сборки build.gradle нашСго прилоТСния:

    implementation 'androidx.recyclerview:recyclerview:1.2.1'
    implementation 'com.github.javafaker:javafaker:1.0.2'
    implementation 'com.github.bumptech.glide:glide:4.14.2'

ΠŸΡ€ΠΈΠΌΠ΅Ρ‡Π°Π½ΠΈΠ΅: Π² послСдних вСрсиях AndroidStudio Π½Π΅ ΠΎΠ±ΡΠ·Π°Ρ‚Π΅Π»ΡŒΠ½ΠΎ ΠΏΠΎΠ΄ΠΊΠ»ΡŽΡ‡Π°Ρ‚ΡŒ Π±ΠΈΠ±Π»ΠΈΠΎΡ‚Π΅ΠΊΡƒ RecyclerView. ДоступСн Π² Π±ΠΈΠ±Π»ΠΈΠΎΡ‚Π΅ΠΊΠ΅ Material.

И Π½Π΅ΠΎΠ±Ρ…ΠΎΠ΄ΠΈΠΌΠΎ ΠΏΠΎΠ΄ΠΊΠ»ΡŽΡ‡ΠΈΡ‚ΡŒ ViewBinding Π² Ρ„Π°ΠΉΠ»Π΅ сборки build.gradle нашСго прилоТСния:

buildFeatures {
        viewBinding = true
}

Π’Π°ΠΊΠΆΠ΅ Π½Π΅ΠΎΠ±Ρ…ΠΎΠ΄ΠΈΠΌΠΎ ΡƒΠΊΠ°Π·Π°Ρ‚ΡŒ Ρ€Π°Π·Ρ€Π΅ΡˆΠ΅Π½ΠΈΠ΅ Π½Π° доступ Π² Π˜Π½Ρ‚Π΅Ρ€Π½Π΅Ρ‚ Π² Ρ„Π°ΠΉΠ»Π΅ AndroidManifest.xml (для Ρ€Π°Π±ΠΎΡ‚Ρ‹ с Π±ΠΈΠ±Π»ΠΈΠΎΡ‚Π΅ΠΊΠΎΠΉ Glide):

<uses-permission android:name="android.permission.INTERNET"/>
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE"/>

ПослС создадим ΠΌΠ°ΠΊΠ΅Ρ‚Π½Ρ‹Π΅ Ρ„Π°ΠΉΠ»Ρ‹: ΠΏΠ΅Ρ€Π²Ρ‹ΠΉ для ActivityMain, ΠΊΠΎΡ‚ΠΎΡ€Ρ‹ΠΉ Ρ…Ρ€Π°Π½ΠΈΡ‚ RecyclerView, Π²Ρ‚ΠΎΡ€ΠΎΠΉ для элСмСнта списка (Ρ‡Π΅Π»ΠΎΠ²Π΅ΠΊ).

activity_main.xml:

<?xml version="1.0" encoding="utf-8"?>
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:id="@+id/activityMain"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context=".MainActivity">

    <androidx.recyclerview.widget.RecyclerView
        android:id="@+id/recyclerView"
        android:layout_width="match_parent"
        android:layout_height="match_parent" />

</FrameLayout>

item_person.xml

<?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"
    android:background="?selectableItemBackground"
    android:paddingStart="10dp"
    android:paddingTop="5dp"
    android:paddingEnd="10dp"
    android:paddingBottom="5dp">

    <ImageView
        android:id="@+id/imageView"
        android:layout_width="wrap_content"
        android:layout_height="match_parent"
        android:src="@drawable/ic_person"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toTopOf="parent" />

    <TextView
        android:id="@+id/nameTextView"
        style="@style/TextAppearance.AppCompat.Body2"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_marginStart="15dp"
        app:layout_constraintStart_toEndOf="@id/imageView"
        app:layout_constraintTop_toTopOf="parent" />

    <TextView
        android:id="@+id/companyTextView"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_marginStart="15dp"
        app:layout_constraintStart_toEndOf="@id/imageView"
        app:layout_constraintTop_toBottomOf="@id/nameTextView" />

    <ImageView
        android:id="@+id/likedImageView"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_marginEnd="15dp"
        android:src="@drawable/ic_like"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintEnd_toStartOf="@id/more"
        app:layout_constraintTop_toTopOf="parent" />

    <ImageView
        android:id="@+id/more"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:src="@drawable/ic_more"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintTop_toTopOf="parent" />

</androidx.constraintlayout.widget.ConstraintLayout>

ПослС Ρ‚ΠΎΠ³ΠΎ, ΠΊΠ°ΠΊ всС Π±ΠΈΠ±Π»ΠΈΠΎΡ‚Π΅ΠΊΠΈ Π±Ρ‹Π»ΠΈ ΠΏΠΎΠ΄ΠΊΠ»ΡŽΡ‡Π΅Π½Ρ‹, всС ΠΌΠ°ΠΊΠ΅Ρ‚Ρ‹ созданы ΠΈ Ρ€Π°Π·Ρ€Π΅ΡˆΠ΅Π½ΠΈΡ ΠΏΠΎΠ»ΡƒΡ‡Π΅Π½Ρ‹, Π½Π΅ΠΎΠ±Ρ…ΠΎΠ΄ΠΈΠΌΠΎ ΡΠΎΠ·Π΄Π°Ρ‚ΡŒ Π΄Π°Π½Π½Ρ‹Π΅ ΠΎ Π»ΡŽΠ΄ΡΡ… (Π² настоящСм, Π±ΠΎΠ΅Π²ΠΎΠΌ ΠΏΡ€ΠΈΠΌΠ΅Ρ€Π΅ эти Π΄Π°Π½Π½Ρ‹Π΅ Π±ΡƒΠ΄ΡƒΡ‚ ΠΏΡ€ΠΈΡ…ΠΎΠ΄ΠΈΡ‚ΡŒ, Π½Π°ΠΏΡ€ΠΈΠΌΠ΅Ρ€, с сСрвСра, Π½ΠΎ Π² нашСм случаС ΠΌΡ‹ создадим ΠΈΡ… ΡΠ°ΠΌΠΎΡΡ‚ΠΎΡΡ‚Π΅Π»ΡŒΠ½ΠΎ). Для этого создадим класс PersonService ΠΈ data-class Person:

data class Person(
    val id: Long, // Π£Π½ΠΈΠΊΠ°Π»ΡŒΠ½Ρ‹ΠΉ Π½ΠΎΠΌΠ΅Ρ€ ΠΏΠΎΠ»ΡŒΠ·ΠΎΠ²Π°Ρ‚Π΅Π»Ρ
    val name: String, // Имя Ρ‡Π΅Π»ΠΎΠ²Π΅ΠΊΠ°
    val companyName: String, // НазваниС комании
    val photo: String, // Бсылка Π½Π° Ρ„ΠΎΡ‚ΠΎ Ρ‡Π΅Π»ΠΎΠ²Π΅ΠΊΠ°
    val isLiked: Boolean // Π‘Ρ‹Π» Π»ΠΈ Π»Π°ΠΉΠΊΠ½ΡƒΡ‚ ΠΏΠΎΠ»ΡŒΠ·ΠΎΠ²Π°Ρ‚Π΅Π»ΡŒ
)
class PersonService {

    private var persons = mutableListOf<Person>() // ВсС ΠΏΠΎΠ»ΡŒΠ·ΠΎΠ²Π°Ρ‚Π΅Π»ΠΈ

    init {
        val faker = Faker.instance() // ΠŸΠ΅Ρ€Π΅ΠΌΠ΅Π½Π½Π°Ρ для создания случайных Π΄Π°Π½Π½Ρ‹Ρ…

        persons = (1..50).map {
            Person(
                id = it.toLong(),
                name = faker.name().fullName(),
                companyName = faker.company().name(),
                photo = IMAGES[it % IMAGES.size],
                isLiked = false
            )
        }.toMutableList()
    }

    companion object {
        private val IMAGES = mutableListOf(
            "https://images.unsplash.com/photo-1600267185393-e158a98703de?crop=entropy&cs=tinysrgb&fit=crop&fm=jpg&h=600&ixid=MnwxfDB8MXxyYW5kb218fHx8fHx8fHwxNjI0MDE0NjQ0&ixlib=rb-1.2.1&q=80&utm_campaign=api-credit&utm_medium=referral&utm_source=unsplash_source&w=800",
            "https://images.unsplash.com/photo-1579710039144-85d6bdffddc9?crop=entropy&cs=tinysrgb&fit=crop&fm=jpg&h=600&ixid=MnwxfDB8MXxyYW5kb218fHx8fHx8fHwxNjI0MDE0Njk1&ixlib=rb-1.2.1&q=80&utm_campaign=api-credit&utm_medium=referral&utm_source=unsplash_source&w=800",
            "https://images.unsplash.com/photo-1488426862026-3ee34a7d66df?crop=entropy&cs=tinysrgb&fit=crop&fm=jpg&h=600&ixid=MnwxfDB8MXxyYW5kb218fHx8fHx8fHwxNjI0MDE0ODE0&ixlib=rb-1.2.1&q=80&utm_campaign=api-credit&utm_medium=referral&utm_source=unsplash_source&w=800",
            "https://images.unsplash.com/photo-1620252655460-080dbec533ca?crop=entropy&cs=tinysrgb&fit=crop&fm=jpg&h=600&ixid=MnwxfDB8MXxyYW5kb218fHx8fHx8fHwxNjI0MDE0NzQ1&ixlib=rb-1.2.1&q=80&utm_campaign=api-credit&utm_medium=referral&utm_source=unsplash_source&w=800",
            "https://images.unsplash.com/photo-1613679074971-91fc27180061?crop=entropy&cs=tinysrgb&fit=crop&fm=jpg&h=600&ixid=MnwxfDB8MXxyYW5kb218fHx8fHx8fHwxNjI0MDE0NzUz&ixlib=rb-1.2.1&q=80&utm_campaign=api-credit&utm_medium=referral&utm_source=unsplash_source&w=800",
            "https://images.unsplash.com/photo-1485795959911-ea5ebf41b6ae?crop=entropy&cs=tinysrgb&fit=crop&fm=jpg&h=600&ixid=MnwxfDB8MXxyYW5kb218fHx8fHx8fHwxNjI0MDE0NzU4&ixlib=rb-1.2.1&q=80&utm_campaign=api-credit&utm_medium=referral&utm_source=unsplash_source&w=800",
            "https://images.unsplash.com/photo-1545996124-0501ebae84d0?crop=entropy&cs=tinysrgb&fit=crop&fm=jpg&h=600&ixid=MnwxfDB8MXxyYW5kb218fHx8fHx8fHwxNjI0MDE0NzY1&ixlib=rb-1.2.1&q=80&utm_campaign=api-credit&utm_medium=referral&utm_source=unsplash_source&w=800",
            "https://images.unsplash.com/flagged/photo-1568225061049-70fb3006b5be?crop=entropy&cs=tinysrgb&fit=crop&fm=jpg&h=600&ixid=MnwxfDB8MXxyYW5kb218fHx8fHx8fHwxNjI0MDE0Nzcy&ixlib=rb-1.2.1&q=80&utm_campaign=api-credit&utm_medium=referral&utm_source=unsplash_source&w=800",
            "https://images.unsplash.com/photo-1567186937675-a5131c8a89ea?crop=entropy&cs=tinysrgb&fit=crop&fm=jpg&h=600&ixid=MnwxfDB8MXxyYW5kb218fHx8fHx8fHwxNjI0MDE0ODYx&ixlib=rb-1.2.1&q=80&utm_campaign=api-credit&utm_medium=referral&utm_source=unsplash_source&w=800",
            "https://images.unsplash.com/photo-1546456073-92b9f0a8d413?crop=entropy&cs=tinysrgb&fit=crop&fm=jpg&h=600&ixid=MnwxfDB8MXxyYW5kb218fHx8fHx8fHwxNjI0MDE0ODY1&ixlib=rb-1.2.1&q=80&utm_campaign=api-credit&utm_medium=referral&utm_source=unsplash_source&w=800"
        )
    }
}

Π’ классС PersonService хранится лист ΠΏΠΎΠ»ΡŒΠ·ΠΎΠ²Π°Ρ‚Π΅Π»Π΅ΠΉ, ΠΊΠΎΡ‚ΠΎΡ€Ρ‹ΠΉ ΠΌΡ‹ заполняСм Π² init (initializers blocks), ΠΈ лист ссылок Π½Π° Ρ„ΠΎΡ‚ΠΎΠ³Ρ€Π°Ρ„ΠΈΠΈ.

ПослС создания классов Π½Π΅ΠΎΠ±Ρ…ΠΎΠ΄ΠΈΠΌΠΎ класс PersonService ΡΠ΄Π΅Π»Π°Ρ‚ΡŒ Singleton для ΠΊΠΎΡ€Ρ€Π΅ΠΊΡ‚Π½ΠΎΠΉ Ρ€Π°Π±ΠΎΡ‚Ρ‹. Для этого создадим класс App, ΠΈ ΡƒΠΊΠ°ΠΆΠ΅ΠΌ Π² Π½Π΅ΠΌ ΡΠ»Π΅Π΄ΡƒΡŽΡ‰Π΅Π΅:

class App : Application() {
    val personService = PersonService()
}

Π’Π΅ΠΏΠ΅Ρ€ΡŒ Ρ€Π΅Π°Π»ΠΈΠ·ΡƒΠ΅ΠΌ Π°Π΄Π°ΠΏΡ‚Π΅Ρ€ PersonAdapter, ΠΊΠΎΡ‚ΠΎΡ€Ρ‹ΠΉ Π±ΡƒΠ΄Π΅Ρ‚ ΠΎΠ±Ρ€Π°Π±Π°Ρ‚Ρ‹Π²Π°Ρ‚ΡŒ наши Π΄Π°Π½Π½Ρ‹Π΅ ΠΈ ΡΠ²ΡΠ·Ρ‹Π²Π°Ρ‚ΡŒ ΠΈΡ… со списком.

Π”Π°Π½Π½Ρ‹ΠΉ класс Π±ΡƒΠ΄Π΅Ρ‚ Ρ€Π΅Π°Π»ΠΈΠ·ΠΎΠ²Π°Ρ‚ΡŒ RecyclerView.Adapter, ΠΊΠΎΡ‚ΠΎΡ€ΠΎΠΌΡƒ Π½ΡƒΠΆΠ΅Π½ ViewHolder. БоотвСтствСнно Π½Π΅ΠΎΠ±Ρ…ΠΎΠ΄ΠΈΠΌΠΎ ΡΠΎΠ·Π΄Π°Ρ‚ΡŒ PersonViewHolder, ΠΊΠΎΡ‚ΠΎΡ€Ρ‹ΠΉ Π±ΡƒΠ΄Π΅Ρ‚ Ρ€Π΅Π°Π»ΠΈΠ·ΠΎΠ²Ρ‹Π²Π°Ρ‚ΡŒ RecyclerView.ViewHolder ΠΈ ΠΏΡ€ΠΈΠ½ΠΈΠΌΠ°Ρ‚ΡŒ наш binding.

Π’Π°ΠΊΠΆΠ΅ PersonAdapter Π΄ΠΎΠ»ΠΆΠ΅Π½ ΠΈΠΌΠ΅Ρ‚ΡŒ Π΄Π°Π½Π½Ρ‹Π΅, с ΠΊΠΎΡ‚ΠΎΡ€Ρ‹ΠΌΠΈ Π΅ΠΌΡƒ прСдстоит Ρ€Π°Π±ΠΎΡ‚Π°Ρ‚ΡŒ. Для этого создадим пустой список ΠΈ ΠΏΠ΅Ρ€Π΅ΠΏΠΈΡˆΠ΅ΠΌ Π΅Π³ΠΎ сСттСр. Π’ ΠΈΡ‚ΠΎΠ³Π΅ ΠΏΠΎΠ»ΡƒΡ‡Π°Π΅ΠΌ:

class PersonAdapter : RecyclerView.Adapter<PersonAdapter.PersonViewHolder>() {

    var data: List<Person> = emptyList()
        set(newValue) {
            field = newValue
            notifyDataSetChanged()
        }

    class PersonViewHolder(val binding: ItemPersonBinding) : RecyclerView.ViewHolder(binding.root)
}

Но для Ρ€Π°Π±ΠΎΡ‚Ρ‹ Π°Π΄Π°ΠΏΡ‚Π΅Ρ€Π° Π½Π΅ΠΎΠ±Ρ…ΠΎΠ΄ΠΈΠΌΠΎ ΠΏΠ΅Ρ€Π΅ΠΎΠΏΡ€Π΅Π΄Π΅Π»ΠΈΡ‚ΡŒ ΠΌΠΈΠ½ΠΈΠΌΡƒΠΌ Ρ‚Ρ€ΠΈ ΠΌΠ΅Ρ‚ΠΎΠ΄Π° (AndroidStudio подскаТСт Π½Π°ΠΌ).

ΠœΠ΅Ρ‚ΠΎΠ΄ getItemCount, ΠΊΠΎΡ‚ΠΎΡ€Ρ‹ΠΉ Π±ΡƒΠ΄Π΅Ρ‚ Π²ΠΎΠ·Π²Ρ€Π°Ρ‰Π°Ρ‚ΡŒ количСство элСмСнтов нашСго списка с Π΄Π°Π½Π½Ρ‹ΠΌΠΈ;
ΠœΠ΅Ρ‚ΠΎΠ΄ onCreateViewHolder, Π² ΠΊΠΎΡ‚ΠΎΡ€ΠΎΠΌ Π±ΡƒΠ΄Π΅Ρ‚ ΠΏΡ€ΠΎΠΈΡΡ…ΠΎΠ΄ΠΈΡ‚ΡŒ созданиС ViewHolder. Π”Π°Π½Π½Ρ‹ΠΉ ΠΌΠ΅Ρ‚ΠΎΠ΄ ΠΏΡ€ΠΈΠ½ΠΈΠΌΠ°Π΅Ρ‚ Π² сСбя parent ΠΈ viewType (ΠΈΡΠΏΠΎΠ»ΡŒΠ·ΡƒΠ΅Ρ‚ΡΡ Π² Ρ‚ΠΎΠΌ случаС, Ссли Π² спискС Π±ΡƒΠ΄ΡƒΡ‚ Ρ€Π°Π·Π½Ρ‹Π΅ Ρ‚ΠΈΠΏΡ‹ элСмСнтов списка);
ΠœΠ΅Ρ‚ΠΎΠ΄ onBindViewHolder, Π² ΠΊΠΎΡ‚ΠΎΡ€ΠΎΠΌ Π±ΡƒΠ΄Π΅Ρ‚ ΠΏΡ€ΠΎΠΈΡΡ…ΠΎΠ΄ΠΈΡ‚ΡŒ отрисовка всСх элСмСнтов Π² ΠΎΠ±ΡŠΠ΅ΠΊΡ‚Π΅ списка (имя Ρ‡Π΅Π»ΠΎΠ²Π΅ΠΊΠ°, компания ΠΈ Ρ‚.Π΄.):

ПослС пСрСопрСдСлСния ΠΌΠ΅Ρ‚ΠΎΠ΄ΠΎΠ² ΠΈ ΠΈΡ… Ρ€Π΅Π°Π»ΠΈΠ·Π°Ρ†ΠΈΠΈ ΠΏΠΎΠ»ΡƒΡ‡Π°Π΅ΠΌ:

    override fun getItemCount(): Int = data.size // ΠšΠΎΠ»ΠΈΡ‡Π΅ΡΡ‚Π²ΠΎ элСмСнтов Π² спискС Π΄Π°Π½Π½Ρ‹Ρ…

    override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): PersonViewHolder {
        val inflater = LayoutInflater.from(parent.context)
        val binding = ItemPersonBinding.inflate(inflater, parent, false)

        return PersonViewHolder(binding)
    }

    override fun onBindViewHolder(holder: PersonViewHolder, position: Int) {
        val person = data[position] // ΠŸΠΎΠ»ΡƒΡ‡Π΅Π½ΠΈΠ΅ Ρ‡Π΅Π»ΠΎΠ²Π΅ΠΊΠ° ΠΈΠ· списка Π΄Π°Π½Π½Ρ‹Ρ… ΠΏΠΎ ΠΏΠΎΠ·ΠΈΡ†ΠΈΠΈ
        val context = holder.itemView.context

        with(holder.binding) {
            val color = if (person.isLiked) R.color.red else R.color.grey // Π¦Π²Π΅Ρ‚ "сСрдца", Ссли ΠΏΠΎΠ»ΡŒΠ·ΠΎΠ²Π°Ρ‚Π΅Π»ΡŒ Π±Ρ‹Π» Π»Π°ΠΉΠΊΠ½ΡƒΡ‚

            nameTextView.text = person.name // ΠžΡ‚Ρ€ΠΈΡΠΎΠ²ΠΊΠ° ΠΈΠΌΠ΅Π½ΠΈ ΠΏΠΎΠ»ΡŒΠ·ΠΎΠ²Π°Ρ‚Π΅Π»Ρ
            companyTextView.text = person.companyName // ΠžΡ‚Ρ€ΠΈΡΠΎΠ²ΠΊΠ° ΠΊΠΎΠΌΠΏΠ°Π½ΠΈΠΈ ΠΏΠΎΠ»ΡŒΠ·ΠΎΠ²Π°Ρ‚Π΅Π»Ρ
            likedImageView.setColorFilter( // ΠžΡ‚Ρ€ΠΈΡΠΎΠ²ΠΊΠ° Ρ†Π²Π΅Ρ‚Π° "сСрдца"
                ContextCompat.getColor(context, color),
                android.graphics.PorterDuff.Mode.SRC_IN
            )
            Glide.with(context).load(person.photo).circleCrop() // ΠžΡ‚Ρ€ΠΈΡΠΎΠ²ΠΊΠ° Ρ„ΠΎΡ‚ΠΎΠ³Ρ€Π°Ρ„ΠΈΠΈ ΠΏΠΎΠ»ΡŒΠ·ΠΎΠ²Π°Ρ‚Π΅Π»Ρ с ΠΏΠΎΠΌΠΎΡ‰ΡŒΡŽ Π±ΠΈΠ±Π»ΠΈΠΎΡ‚Π΅ΠΊΠΈ Glide
                .error(R.drawable.ic_person) 
                .placeholder(R.drawable.ic_person).into(imageView)
        }
    }

На этом наш простой Π°Π΄Π°ΠΏΡ‚Π΅Ρ€, ΠΊΠΎΡ‚ΠΎΡ€Ρ‹ΠΉ Π±ΡƒΠ΄Π΅Ρ‚ просто Π²Ρ‹Π²ΠΎΠ΄ΠΈΡ‚ΡŒ Π³ΠΎΡ€ΠΈΠ·ΠΎΠ½Ρ‚Π°Π»ΡŒΠ½Ρ‹ΠΉ список Π³ΠΎΡ‚ΠΎΠ².

Π’Π΅ΠΏΠ΅Ρ€ΡŒ Π½Π΅ΠΎΠ±Ρ…ΠΎΠ΄ΠΈΠΌΠΎ ΠΏΠΎΠ²Π΅ΡΠΈΡ‚ΡŒ Π½Π° наш RecyclerView созданный Π°Π΄Π°ΠΏΡ‚Π΅Ρ€ ΠΈ LayoutManager. Для этого Π² классС MainActivity ΠΏΡ€ΠΎΠΏΠΈΡˆΠ΅ΠΌ ΡΠ»Π΅Π΄ΡƒΡŽΡ‰Π΅Π΅:

class MainActivity : AppCompatActivity() {

    private lateinit var binding: ActivityMainBinding
    private lateinit var adapter: PersonAdapter // ΠžΠ±ΡŠΠ΅ΠΊΡ‚ Adapter
    private val personService: PersonService // ΠžΠ±ΡŠΠ΅ΠΊΡ‚ PersonService
        get() = (applicationContext as App).personService

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        binding = ActivityMainBinding.inflate(layoutInflater)
        setContentView(binding.root)

        val manager = LinearLayoutManager(this) // LayoutManager
        adapter = PersonAdapter() // Π‘ΠΎΠ·Π΄Π°Π½ΠΈΠ΅ ΠΎΠ±ΡŠΠ΅ΠΊΡ‚Π°
        adapter.data = personService.getPersons() // Π—Π°ΠΏΠΎΠ»Π½Π΅Π½ΠΈΠ΅ Π΄Π°Π½Π½Ρ‹ΠΌΠΈ

        binding.recyclerView.layoutManager = manager // НазначСниС LayoutManager для RecyclerView
        binding.recyclerView.adapter = adapter // НазначСниС Π°Π΄Π°ΠΏΡ‚Π΅Ρ€Π° для RecyclerView
    }
}

ЗапускаСм нашС ΠΏΡ€ΠΈΠ»ΠΎΠΆΠ΅Π½ΠΈΠ΅ Π½Π° устройствС ΠΈ ΠΏΠΎΠ»ΡƒΡ‡Π°Π΅ΠΌ список ΠΏΠΎΠ»ΡŒΠ·ΠΎΠ²Π°Ρ‚Π΅Π»Π΅ΠΉ!

На Π΄Π°Π½Π½ΠΎΠΌ этапС нашС ΠΏΡ€ΠΈΠ»ΠΎΠΆΠ΅Π½ΠΈΠ΅ просто Π²Ρ‹Π²ΠΎΠ΄ΠΈΡ‚ Π΄Π°Π½Π½Ρ‹Π΅, ΠΊΠΎΡ‚ΠΎΡ€Ρ‹Π΅ ΠΌΡ‹ ΠΌΠΎΠΆΠ΅ΠΌ ΠΏΡ€ΠΎΠΊΡ€ΡƒΡ‡ΠΈΠ²Π°Ρ‚ΡŒ, Π½ΠΎ Π²Π·Π°ΠΈΠΌΠΎΠ΄Π΅ΠΉΡΡ‚Π²ΠΎΠ²Π°Ρ‚ΡŒ с Π½ΠΈΠΌΠΈ Ρƒ нас Π½Π΅ получится. Π˜ΡΠΏΡ€Π°Π²ΠΈΠΌ это.

Π’ классС PersonService Π΄ΠΎΠ±Π°Π²ΠΈΠΌ Ρ‚Ρ€ΠΈ ΠΌΠ΅Ρ‚ΠΎΠ΄Π°:

  • likePerson - Π»Π°ΠΉΠΊΠ°Π΅ΠΌ Ρ‡Π΅Π»ΠΎΠ²Π΅ΠΊΠ°;
  • removePerson - удаляСм Ρ‡Π΅Π»ΠΎΠ²Π΅ΠΊΠ°;
  • movePerson - ΠΏΠ΅Ρ€Π΅ΠΌΠ΅Ρ‰Π°Π΅ΠΌ Ρ‡Π΅Π»ΠΎΠ²Π΅ΠΊΠ° (ΠΏΡ€ΠΈΠ½ΠΈΠΌΠ°Π΅Ρ‚ Ρ‡Π΅Π»ΠΎΠ²Π΅ΠΊΠ° ΠΈ ΠΊΡƒΠ΄Π° Π½Π°Π΄ΠΎ ΠΏΠ΅Ρ€Π΅ΠΌΠ΅ΡΡ‚ΠΈΡ‚ΡŒ: "1" - Π²Π½ΠΈΠ·, "-1" - Π²Π²Π΅Ρ€Ρ…).
    fun likePerson(person: Person) {
        val index = persons.indexOfFirst { it.id == person.id } // Находим индСкс Ρ‡Π΅Π»ΠΎΠ²Π΅ΠΊΠ° Π² спискС
        if (index == -1) return // ΠžΡΡ‚Π°Π½Π°Π²Π»ΠΈΠ²Π°Π΅ΠΌΡΡ, Ссли Π½Π΅ Π½Π°Ρ…ΠΎΠ΄ΠΈΠΌ Ρ‚Π°ΠΊΠΎΠ³ΠΎ Ρ‡Π΅Π»ΠΎΠ²Π΅ΠΊΠ°

        persons = ArrayList(persons) // Π‘ΠΎΠ·Π΄Π°Π΅ΠΌ Π½ΠΎΠ²Ρ‹ΠΉ список
        persons[index] = persons[index].copy(isLiked = !persons[index].isLiked) // МСняСм Π·Π½Π°Ρ‡Π΅Π½ΠΈΠ΅ "Π»Π°ΠΉΠΊΠ°" Π½Π° ΠΏΡ€ΠΎΡ‚ΠΈΠ²ΠΎΠΏΠΎΠ»ΠΎΠΆΠ½ΠΎΠ΅
    }

    fun removePerson(person: Person) {
        val index = persons.indexOfFirst { it.id == person.id } // Находим индСкс Ρ‡Π΅Π»ΠΎΠ²Π΅ΠΊΠ° Π² спискС
        if (index == -1) return // ΠžΡΡ‚Π°Π½Π°Π²Π»ΠΈΠ²Π°Π΅ΠΌΡΡ, Ссли Π½Π΅ Π½Π°Ρ…ΠΎΠ΄ΠΈΠΌ Ρ‚Π°ΠΊΠΎΠ³ΠΎ Ρ‡Π΅Π»ΠΎΠ²Π΅ΠΊΠ°

        persons = ArrayList(persons) // Π‘ΠΎΠ·Π΄Π°Π΅ΠΌ Π½ΠΎΠ²Ρ‹ΠΉ список
        persons.removeAt(index) // УдаляСм Ρ‡Π΅Π»ΠΎΠ²Π΅ΠΊΠ°
    }

    fun movePerson(person: Person, moveBy: Int) {
        val oldIndex = persons.indexOfFirst { it.id == person.id } // Находим индСкс Ρ‡Π΅Π»ΠΎΠ²Π΅ΠΊΠ° Π² спискС
        if (oldIndex == -1) return // ΠžΡΡ‚Π°Π½Π°Π²Π»ΠΈΠ²Π°Π΅ΠΌΡΡ, Ссли Π½Π΅ Π½Π°Ρ…ΠΎΠ΄ΠΈΠΌ Ρ‚Π°ΠΊΠΎΠ³ΠΎ Ρ‡Π΅Π»ΠΎΠ²Π΅ΠΊΠ°

        val newIndex = oldIndex + moveBy // ВычисляСм Π½ΠΎΠ²Ρ‹ΠΉ индСкс, Π½Π° ΠΊΠΎΡ‚ΠΎΡ€ΠΎΠΌ Π΄ΠΎΠ»ΠΆΠ΅Π½ находится Ρ‡Π΅Π»ΠΎΠ²Π΅ΠΊ
        persons = ArrayList(persons) // Π‘ΠΎΠ·Π΄Π°Π΅ΠΌ Π½ΠΎΠ²Ρ‹ΠΉ список
        Collections.swap(persons, oldIndex, newIndex) // МСняСм мСстами людСй        
    }

ПослС Ρ‚ΠΎΠ³ΠΎ, ΠΊΠ°ΠΊ Π±Ρ‹Π»ΠΈ созданы ΠΌΠ΅Ρ‚ΠΎΠ΄Ρ‹ взаимодСйствия с людьми, Π² классС PersonService Π½Π΅ΠΎΠ±Ρ…ΠΎΠ΄ΠΈΠΌΠΎ ΠΎΠ±ΡŠΡΠ²ΠΈΡ‚ΡŒ ΡΠ»ΡƒΡˆΠ°Ρ‚Π΅Π»Ρ:

typealias PersonListener = (persons: List<Person>) -> Unit

Π’Π°ΠΊ ΠΆΠ΅ создадим список ΡΠ»ΡƒΡˆΠ°Ρ‚Π΅Π»Π΅ΠΉ, Π΄Π²Π° ΠΌΠ΅Ρ‚ΠΎΠ΄Π°, ΠΊΠΎΡ‚ΠΎΡ€Ρ‹Π΅ Π±ΡƒΠ΄ΡƒΡ‚ Π΄ΠΎΠ±Π°Π²Π»ΡΡ‚ΡŒ ΠΈ ΡƒΠ΄Π°Π»ΡΡ‚ΡŒ ΡΠ»ΡƒΡˆΠ°Ρ‚Π΅Π»Π΅ΠΉ, ΠΈ ΠΎΠ΄ΠΈΠ½, ΠΊΠΎΡ‚ΠΎΡ€Ρ‹ΠΉ Π±ΡƒΠ΄Π΅Ρ‚ "Ρ€Π΅Π³ΠΈΡΡ‚Ρ€ΠΈΡ€ΠΎΠ²Π°Ρ‚ΡŒ измСнСния":

    private var listeners = mutableListOf<PersonListener>() // ВсС ΡΠ»ΡƒΡˆΠ°Ρ‚Π΅Π»ΠΈ

    fun addListener(listener: PersonListener) {
        listeners.add(listener)
        listener.invoke(persons)
    }

    fun removeListener(listener: PersonListener) {
        listeners.remove(listener)
        listener.invoke(persons)
    }

    private fun notifyChanges() = listeners.forEach { it.invoke(persons) }

ΠœΠ΅Ρ‚ΠΎΠ΄ notifyChanges Π½Π΅ΠΎΠ±Ρ…ΠΎΠ΄ΠΈΠΌΠΎ ΠΎΠ±ΡΠ·Π°Ρ‚Π΅Π»ΡŒΠ½ΠΎ Π²Ρ‹Π·Π²Π°Ρ‚ΡŒ Π² ΠΌΠ΅Ρ‚ΠΎΠ΄Π°Ρ…, Π² ΠΊΠΎΡ‚ΠΎΡ€Ρ‹Ρ… происходит модификация Π΄Π°Π½Π½Ρ‹Ρ…, Ρ‚ΠΎ Π΅ΡΡ‚ΡŒ Π² ΠΌΠ΅Ρ‚ΠΎΠ΄Π°Ρ… likePerson, removePerson ΠΈ movePerson.

На этом наш сСрвис людСй ΠΏΠΎΠ»Π½ΠΎΡΡ‚ΡŒΡŽ Π³ΠΎΡ‚ΠΎΠ². ΠŸΠ΅Ρ€Π΅ΠΉΠ΄Π΅ΠΌ Π² PersonAdapter, Π² ΠΊΠΎΡ‚ΠΎΡ€ΠΎΠΌ Ρ€Π΅Π°Π»ΠΈΠ·ΡƒΠ΅ΠΌ ΠΎΠ±Ρ€Π°Π±ΠΎΡ‚ΠΊΡƒ событий Π½Π°ΡˆΠΈΡ… людСй. Π‘ΠΎΠ·Π΄Π°Π΄ΠΈΠΌ интСрфСйс PersonActionListener, Π² ΠΊΠΎΡ‚ΠΎΡ€ΠΎΠΌ Π±ΡƒΠ΄Ρƒ Ρ‡Π΅Ρ‚Ρ‹Ρ€Π΅ ΠΌΠ΅Ρ‚ΠΎΠ΄Π°:

  • onPersonGetId - ΠΏΠΎΠ»ΡƒΡ‡ΠΈΡ‚ΡŒ ΡƒΠ½ΠΈΠΊΠ°Π»ΡŒΠ½Ρ‹ΠΉ Π½ΠΎΠΌΠ΅Ρ€ Π²Ρ‹Π±Ρ€Π°Π½Π½ΠΎΠ³ΠΎ Ρ‡Π΅Π»ΠΎΠ²Π΅ΠΊΠ°;
  • onPersonLike - Ρ‡Π΅Π»ΠΎΠ²Π΅ΠΊ Π±Ρ‹Π» Π»Π°ΠΉΠΊΠ½ΡƒΡ‚;
  • onPersonRemove - ΡƒΠ΄Π°Π»ΠΈΡ‚ΡŒ Ρ‡Π΅Π»ΠΎΠ²Π΅ΠΊΠ°;
  • onPersonMove - ΠΏΠ΅Ρ€Π΅ΠΌΠ΅ΡΡ‚ΠΈΡ‚ΡŒ Ρ‡Π΅Π»ΠΎΠ²Π΅ΠΊΠ°.
interface PersonActionListener {
    fun onPersonGetId(person: Person)
    fun onPersonLike(person: Person)
    fun onPersonRemove(person: Person)
    fun onPersonMove(person: Person, moveBy: Int)
}

Класс PersonAdapter Π²ΠΎ Π²Ρ…ΠΎΠ΄Π½Ρ‹Π΅ ΠΏΠ°Ρ€Π°ΠΌΠ΅Ρ‚Ρ€Ρ‹ Π±ΡƒΠ΄Π΅Ρ‚ ΠΏΡ€ΠΈΠ½ΠΈΠΌΠ°Ρ‚ΡŒ наш интСрфСйс. Π’Π°ΠΊΠΆΠ΅ Π΄Π°Π½Π½Ρ‹ΠΉ класс Π΄ΠΎΠ»ΠΆΠ΅Π½ Ρ€Π΅Π°Π»ΠΈΠ·ΠΎΠ²Π°Ρ‚ΡŒ интСрфСйс OnClickListener. Π’ ΠΈΡ‚ΠΎΠ³Π΅ сигнатура объявлСния класса PersonAdaper выглядит ΡΠ»Π΅Π΄ΡƒΡŽΡ‰ΠΈΠΌ ΠΎΠ±Ρ€Π°Π·ΠΎΠΌ:

class PersonAdapter(private val personActionListener: PersonActionListener) :
    RecyclerView.Adapter<PersonAdapter.PersonViewHolder>(), View.OnClickListener {

Π’Π΅ΠΏΠ΅Ρ€ΡŒ Π² классС PersonAdapter Π² ΠΌΠ΅Ρ‚ΠΎΠ΄Π΅ onBindViewHolder ΠΊΠ»Π°Π΄Ρ‘ΠΌ Π² tag ΠΊΠ°ΠΆΠ΄ΠΎΠ³ΠΎ view, Π½Π° ΠΊΠΎΡ‚ΠΎΡ€ΡƒΡŽ Π±ΡƒΠ΄Π΅Ρ‚ ΠΏΡ€ΠΎΠΈΡΡ…ΠΎΠ΄ΠΈΡ‚ΡŒ Π½Π°ΠΆΠ°Ρ‚ΠΈΠ΅, Π½ΡƒΠΆΠ½ΠΎΠ³ΠΎ Ρ‡Π΅Π»ΠΎΠ²Π΅ΠΊΠ°:

holder.itemView.tag = person
holder.binding.likedImageView.tag = person
holder.binding.more.tag = person

Π’Π΅ΠΏΠ΅Ρ€ΡŒ Π² ΠΌΠ΅Ρ‚ΠΎΠ΄Π΅ onCreateViewHolder Π½Π΅ΠΎΠ±Ρ…ΠΎΠ΄ΠΈΠΌΠΎ ΠΏΡ€ΠΎΠΈΠ½ΠΈΡ†ΠΈΠ°Π»ΠΈΠ·ΠΈΡ€ΠΎΠ²Π°Ρ‚ΡŒ ΡΠ»ΡƒΡˆΠ°Ρ‚Π΅Π»Π΅ΠΉ ΠΏΡ€ΠΈ Π½Π°ΠΆΠ°Ρ‚ΠΈΠΈ. Π’ Π΄Π°Π½Π½ΠΎΠΌ ΠΏΡ€ΠΈΠΌΠ΅Ρ€Π΅ Π±ΡƒΠ΄Π΅Ρ‚ ΡΠ»ΡƒΡˆΠ°Ρ‚Π΅Π»ΡŒ Π½Π° Π½Π°ΠΆΠ°Ρ‚ΠΈΠ΅ Π½Π° элСмСнт списка, ΠΊΠ½ΠΎΠΏΠΊΡƒ more (Ρ‚Ρ€ΠΈ Ρ‚ΠΎΡ‡ΠΊΠΈ) ΠΈ likedImageView (сСрдцС):

binding.root.setOnClickListener(this)
binding.more.setOnClickListener(this)
binding.likedImageView.setOnClickListener(this)

Π’Π΅ΠΏΠ΅Ρ€ΡŒ создадим ΠΌΠ΅Ρ‚ΠΎΠ΄ showPopupMenu, ΠΊΠΎΡ‚ΠΎΡ€Ρ‹ΠΉ Π±ΡƒΠ΄Π΅Ρ‚ "Ρ€ΠΈΡΠΎΠ²Π°Ρ‚ΡŒ" Π²Ρ‹ΠΏΠ°Π΄Π°ΡŽΡ‰Π΅Π΅ мСню с доступными дСйствиями, Π° ΠΈΠΌΠ΅Π½Π½ΠΎ: ΡƒΠ΄Π°Π»ΠΈΡ‚ΡŒ ΠΏΠΎΠ»ΡŒΠ·ΠΎΠ²Π°Ρ‚Π΅Π»Ρ, ΠΏΠ΅Ρ€Π΅ΠΌΠ΅ΡΡ‚ΠΈΡ‚ΡŒ Π²Π²Π΅Ρ€Ρ…, ΠΏΠ΅Ρ€Π΅ΠΌΠ΅ΡΡ‚ΠΈΡ‚ΡŒ Π²Π½ΠΈΠ·:

    private fun showPopupMenu(view: View) {
        val popupMenu = PopupMenu(view.context, view)
        val person = view.tag as Person
        val position = data.indexOfFirst { it.id == person.id }

        popupMenu.menu.add(0, ID_MOVE_UP, Menu.NONE, "Up").apply {
            isEnabled = position > 0
        }
        popupMenu.menu.add(0, ID_MOVE_DOWN, Menu.NONE, "Down").apply {
            isEnabled = position < data.size - 1
        }
        popupMenu.menu.add(0, ID_REMOVE, Menu.NONE, "Remove")

        popupMenu.setOnMenuItemClickListener {
            when (it.itemId) {
                ID_MOVE_UP -> personActionListener.onPersonMove(person, -1)
                ID_MOVE_DOWN -> personActionListener.onPersonMove(person, 1)
                ID_REMOVE -> personActionListener.onPersonRemove(person)
            }
            return@setOnMenuItemClickListener true
        }

        popupMenu.show()
    }

    companion object {
        private const val ID_MOVE_UP = 1
        private const val ID_MOVE_DOWN = 2
        private const val ID_REMOVE = 3
    }

Π’ ΠΌΠ΅Ρ‚ΠΎΠ΄Π΅ onClick ΠΎΠ±Ρ€Π°Π±ΠΎΡ‚Π°Π΅ΠΌ наТатия Π½Π° элСмСнты списка:

    override fun onClick(view: View) {
        val person: Person = view.tag as Person // ΠŸΠΎΠ»ΡƒΡ‡Π°Π΅ΠΌ ΠΈΠ· тэга Ρ‡Π΅Π»ΠΎΠ²Π΅ΠΊΠ°

        when (view.id) {
            R.id.more -> showPopupMenu(view)
            R.id.likedImageView -> personActionListener.onPersonLike(person)
            else -> personActionListener.onPersonGetId(person)
        }
    }

На этом наш Π°Π΄Π°ΠΏΡ‚Π΅Ρ€ Π³ΠΎΡ‚ΠΎΠ². Π’Π΅ΠΏΠ΅Ρ€ΡŒ ΠΏΠ΅Ρ€Π΅ΠΉΠ΄Π΅ΠΌ Π² MainActivity ΠΈ, ΠΏΡ€ΠΈ ΠΈΠ½ΠΈΡ†ΠΈΠ°Π»ΠΈΠ·Π°Ρ†ΠΈΠΈ нашСго Π°Π΄Π°ΠΏΡ‚Π΅Ρ€Π°, ΠΏΠ΅Ρ€Π΅Π΄Π°Π΄ΠΈΠΌ Ρ€Π΅Π°Π»ΠΈΠ·Π°Ρ†ΠΈΡŽ интСрфСйса:

adapter = PersonAdapter(object : PersonActionListener { // Π‘ΠΎΠ·Π΄Π°Π½ΠΈΠ΅ ΠΎΠ±ΡŠΠ΅ΠΊΡ‚Π°
   override fun onPersonGetId(person: Person) =
      Toast.makeText(this@MainActivity, "Persons ID: ${person.id}", Toast.LENGTH_SHORT).show()

   override fun onPersonLike(person: Person) = personService.likePerson(person)

   override fun onPersonRemove(person: Person) = personService.removePerson(person)

   override fun onPersonMove(person: Person, moveBy: Int) = personService.movePerson(person, moveBy)

})

Π’Π°ΠΊΠΆΠ΅ Π΄ΠΎΠ±Π°Π²ΠΈΠΌ ΡΠ»ΡƒΡˆΠ°Ρ‚Π΅Π»Ρ Π² MainActivity, ΠΊΠΎΡ‚ΠΎΡ€Ρ‹ΠΉ Π±ΡƒΠ΄Π΅Ρ‚ ΠΏΡ€ΠΎΡΠ»ΡƒΡˆΠΈΠ²Π°Ρ‚ΡŒ измСнСния, происходящиС Π² PersonService:

private val listener: PersonListener = {adapter.data = it}

И Π² ΠΌΠ΅Ρ‚ΠΎΠ΄Π΅ onCreate Π΄ΠΎΠ±Π°Π²ΠΈΠΌ этого ΡΠ»ΡƒΡˆΠ°Ρ‚Π΅Π»Ρ:

personService.addListener(listener)

На этом наша Ρ€Π°Π±ΠΎΡ‚Π° Π²Ρ‹ΠΏΠΎΠ»Π½Π΅Π½Π°. ЗапускаСм ΠΏΡ€ΠΎΠ΅ΠΊΡ‚ ΠΈ смотрим Ρ€Π΅Π·ΡƒΠ»ΡŒΡ‚Π°Ρ‚:

GIF

ΠœΡ‹ рассмотрСли основы RecyclerView. ЕстСствСнно это Π΄Π°Π»Π΅ΠΊΠΎ Π½Π΅ всС, Ρ‡Ρ‚ΠΎ позволяСт ΡΠ΄Π΅Π»Π°Ρ‚ΡŒ этот ΠΌΠΎΡ‰Π½Ρ‹ΠΉ инструмСнт. ВсСгда ΠΌΠΎΠΆΠ½ΠΎ Π΄ΠΎΠ±Π°Π²ΠΈΡ‚ΡŒ DiffUtil, ΠΊΠΎΡ‚ΠΎΡ€Ρ‹ΠΉ ΠΏΠΎΠΌΠΎΠΆΠ΅Ρ‚ ΠΎΠΏΡ‚ΠΈΠΌΠΈΠ·ΠΈΡ€ΠΎΠ²Π°Ρ‚ΡŒ список, ItemDecorator, для Π΄Π΅ΠΊΠΎΡ€Π° Π½Π°ΡˆΠΈΡ… элСмСнтов ΠΈ Ρ‚.Π΄.
Бсылка Π½Π° ΡΡ‚Π°Ρ‚ΡŒΡŽ Π½Π° Habr.


πŸ† Π― надСюсь, Ρ‡Ρ‚ΠΎ данная Ρ€Π°Π±ΠΎΡ‚Π° ΠΏΠΎΠΌΠΎΠ³Π»Π° Π’Π°ΠΌ.
πŸ“§ ΠŸΡ€ΠΈ Π²ΠΎΠ·Π½ΠΈΠΊΠ½ΠΎΠ²Π΅Π½ΠΈΠΈ ΠΊΠ°ΠΊΠΈΡ…-Π»ΠΈΠ±ΠΎ вопросов ΠΈ ΠΏΡ€Π΅Π΄Π»ΠΎΠΆΠ΅Π½ΠΈΠΉ - ΡΠ²ΡΠΆΠΈΡ‚Π΅ΡΡŒ со ΠΌΠ½ΠΎΠΉ.
🀝 Бпасибо, Ρ‡Ρ‚ΠΎ Π·Π°ΠΈΠ½Ρ‚Π΅Ρ€Π΅ΡΠΎΠ²Π°Π»ΠΈΡΡŒ Π΄Π°Π½Π½ΠΎΠΉ Ρ€Π°Π±ΠΎΡ‚ΠΎΠΉ.

About

πŸ“– RecyclerView for beginner android developer. Article. // RecyclerView для Π½Π°Ρ‡ΠΈΠ½Π°ΡŽΡ‰Π΅Π³ΠΎ Android-Ρ€Π°Π·Ρ€Π°Π±ΠΎΡ‚Ρ‡ΠΈΠΊΠ°. Π‘Ρ‚Π°Ρ‚ΡŒΡ.

Resources

Stars

Watchers

Forks

Releases

No releases published

Packages

No packages published

Languages

  • Kotlin 100.0%