Android RecyclerView: що таке, дізнайтеся на простих прикладах

⚡ Розумний підсумок

RecyclerView — це вдосконалений Android Віджет, який ефективно відображає великі набори даних з можливістю прокручування, використовуючи обмежену кількість представлень. Він спирається на адаптер, менеджер розміщення та власник представлень для зв'язування колекцій даних, що змінюються під час виконання.

  • 📱 Що це: RecyclerView — це гнучкий наступник ListView та GridView для відображення великих колекцій даних.
  • 🧱 Представлення та групи представлень: Android малює інтерфейс за допомогою класів View та ViewGroup, де LayoutParams контролює розмір та положення.
  • 🔄 Адаптер: Шаблон адаптера перетворює масив даних на ViewHolders, які може прийняти RecyclerView.
  • 📐 Менеджер макета: Менеджер макета розташовує елементи лінійно або у вигляді сітки та сигналізує, коли подання можна використовувати повторно.
  • ♻️ Переробка: Види поза екраном використовуються повторно, а не створюються заново, що економить пам'ять і забезпечує плавну прокрутку.
  • 🛠️ Кроки збірки: Додати Gradle залежність, створіть макет елемента, ViewHolder, адаптер та LayoutManager.

Android RecyclerView

Що таке RecyclerView Android?

Команда RecyclerView — це гнучкіша та розширеніша версія GridView та ListView. Це контейнер для відображення великих наборів даних, які можна ефективно прокручувати, зберігаючи обмежену кількість подань. Ви можете використовувати віджет RecyclerView, коли у вас є колекції даних, елементи яких змінюються під час виконання залежно від мережевої події або дії користувача.

думки

Команда Android Платформа використовує класи View та ViewGroup для малювання елементів на екрані. Ці класи є ABS.tract та розширюються на різні реалізації відповідно до конкретного випадку використання. Наприклад, TextView має просту мету відображення текстового вмісту на екрані. EditText походить від того ж класу View та додає більше функціональності, щоб користувач міг вводити дані.

Можна створювати власні перегляди для досягнення більшої гнучкості під час розробкиping інтерфейси користувача. Клас View надає методи, які ми можемо перевизначити для малювання на екрані, а також засоби для передачі таких параметрів, як ширина, висота та власні атрибути, які ми хотіли б додати до нашого View, щоб він поводився належним чином.

ViewGroups

Клас ViewGroup є різновидом View, але, на відміну від простого класу View, відповідальним за просте відображення, ViewGroup надає нам можливість поміщати кілька представлень в одне представлення, на яке ми можемо посилатися як на єдине ціле. У цьому випадку View, створене на верхньому рівні, до якого ми додаємо інші прості представлення, називається «батьківським», а представлення, що додаються всередині, — «дітьми».

Ми можемо уявити собі View як масив, а ViewGroup як масив масивів. Враховуючи, що масив масивів сам по собі є масивом, ми можемо побачити, як ViewGroup можна розглядати як View.

var arr1 = [1,2,3] //imagine a simple View as an Array
//we can imagine this as a NumberTextView which doesn't really exist
//but we could imagine there's one that makes it easy to use numbers
var arr2 = ["a","b","c"] // We can imagine this as another simple view

var nestedArr = [arr1,arr2] //in our analogy, we can now group views
//together and the structure that would hold that would be what we call the ViewGroup

ViewGroup також дозволяє нам визначити, як організовані дочірні елементи всередині представлення, наприклад, чи розташовані вони вертикально чи горизонтально. Ми можемо мати різні правила взаємодії всередині представлення. Наприклад, TextView, що йдуть один за одним, повинні мати відстань 12dp, тоді як ImageView, за яким йде TextView, повинні мати відстань 5dp.

Так було б, якби ми розвивалисяping нашу власну ViewGroup з нуля. Щоб спростити ці налаштування, Android надає клас під назвою LayoutParams, який ми можемо використовувати для введення цих конфігурацій.

Команда Android документація надає деякі параметри за замовчуванням, які ми б реалізували під час налаштування нашої власної групи перегляду (ViewGroup). Деякі поширені параметри стосуються ширини, висоти та відступів. За замовчуванням ці конфігурації мають структуру android:layout_height для висоти та android:layout_width для ширини. У зв'язку з цим, під час створення ViewGroup ви можете додатково створити параметри LayoutParams, що відповідають бажаній поведінці вашої групи перегляду (ViewGroup).

Android постачається зі стандартними Views та ViewGroups, які ми можемо використовувати для виконання багатьох поширених завдань. Одним із прикладів, який ми згадали, є TextView. Це просте представлення з налаштовуваними аспектами, такими як висота, ширина та розмір тексту. У нас також є ImageView для відображення зображень та EditText, як ми вже згадували, серед багатьох інших. Android також має спеціальні ViewGroups, до яких ми можемо додати наші Views і отримати очікувану поведінку.

Лінійний макет

LinearLayout дозволяє нам додавати елементи View. LinearLayout має атрибут orientation, який визначає, як він буде розміщений на екрані. Він також має LinearLayout.LayoutParams, які визначають правила для представлень всередині. Наприклад, атрибут android:center_horizontal центруватиме представлення вздовж горизонтальної осі, тоді як android:center_vertical центруватиме вміст представлення вздовж вертикальної осі.

Ось кілька зображень, які допоможуть зрозуміти центрування. Ми розглядаємо це як простий TextView у просторі 200px на 200px; атрибути центрування зроблять його поведінку наступною.

android:center_horizontal

Вміст, орієнтований по горизонталі

Горизонтально відцентрований вміст

android:center_vertical

Вертикально центрований контент

Вертикально центрований контент

android:center

Центрований вміст

Центрований вміст

Основні компоненти RecyclerView

Основні компоненти RecyclerView

Основні компоненти RecyclerView

Нижче наведено важливі компоненти RecyclerView:

RecyclerView.Adapter

Основа №1 – шаблон адаптера

Адаптер — це пристрій, який перетворює атрибути однієї системи або пристрою на атрибути іншого, несумісного пристрою або системи. Деякі адаптери змінюють атрибути сигналу або живлення, тоді як інші просто адаптують фізичну форму одного роз'єму до іншого.

Простий приклад з реального життя, який пояснює використання адаптера, – це коли нам потрібно з’єднати пристрої між собою, але вони мають несумісні порти підключення. Це може статися, коли ви відвідуєте іншу країну, де використовуються різні типи розеток. Якщо ви берете із собою зарядний пристрій для телефону або ноутбука, підключити його до розеток буде неможливо. Однак, ви не здаваєтеся, а просто купуєте адаптер, який вставляється між розеткою та зарядним пристроєм і дозволяє заряджати пристрій.

Це той випадок у програмуванні, коли ми хочемо з'єднати дві структури даних разом для виконання завдання, але їхні порти за замовчуванням не мають способу зв'язку один з одним.

Ми розглянемо простий приклад із пристроєм та зарядним пристроєм. У нас буде два випадки зарядних пристроїв: американський та британський.

class AmericanCharger() {
    var chargingPower = 10
}
class BritishCharger(){
    var chargingPower = 5
}

Потім ми створимо два пристрої.

class AmericanDevice()
class BritishDevice()

Як приклад, ми можемо створити кілька екземплярів пристроїв для гри.

var myAmericanPhone = new AmericanDevice()
var myBritishPhone = new BritishDevice()

Потім ми введемо концепцію заряджання для обох пристроїв, додавши до пристроїв метод під назвою charge(). Метод приймає відповідний зарядний пристрій і виконує заряджання на його основі.

sealed trait Device
class AmericanDevice : Device{
    fun charge(charger:AmericanCharger){
        //Do some American charging
    }
}
class BritishDevice: Device{
    fun charge(charger:BritishCharger){
        //Do some British charging
    }
}

У цьому випадку, виходячи з нашої аналогії, нам з тієї чи іншої причини знадобиться використовувати BritishCharger під час використання AmericanDevice, або навпаки.

У світі програмування це зазвичай відбувається під час змішування бібліотек, які пропонують однакову функціональність (у нашому контексті наша спільна функціональність платна). Нам потрібно було б знайти спосіб увімкнути це.

Якщо дотримуватися аналогії, нам потрібно буде піти до магазину електроніки та купити адаптер, який дозволить нам заряджати пристрої AmericanDevices, враховуючи BritishChargers. З точки зору програмування, ми будемо виробником адаптера.

Ми створимо адаптер, який точно відповідає шаблону, який нам знадобиться для створення іншого. Ми реалізуємо його як клас наступним чином. Це не обов'язково має бути клас, це може бути функція, яка підкреслює, що зазвичай робить шаблон адаптера. Ми використовуємо клас, оскільки він відповідає більшості випадків використання на Android.

class AmericanToBritishChargerAdapter(theAmericanCharger:AmericanCharger){
    fun returnNewCharger(): BritishCharger{
            //convert the American charger to a BritishCharger
            //we would change the American charging functionality
            //to British charging functionality to make sure the
            //adapter doesn't destroy the device. The adapter could
            //, for example, control the power output by dividing by 2
            //our adapter could encompass this functionality in here

           var chargingPower:Int = theAmericanCharger.chargingPower / 2
           var newBritishCharger = new BritishCharger()
           newBritishCharger.chargingPower = theAmericanCharger.chargingPower/2

        return newBritishCharger
    }
}

У світі програмування різниця в розетках аналогічна різниці в методах заряджання. Зарядні пристрої з різними методами заряджання унеможливлюють їх використання.

var myBritishDevice = new BritishDevice()
var americanChargerIFound = new AmericanCharger()

Спроба викликати метод charge() на myBritishDevice з americanChargerIFound не спрацює, оскільки AmericanDevice приймає лише AmericanCharger. Тому це неможливо зробити:

var myBritishDevice = new BritishDevice()
var americanChargerIFound = new AmericanCharger()
myBritishDevice.charge(americanChargerIFound)

У цьому сценарії нам може стати в пригоді адаптер, AmericanToBritishChargerAdapter. Ми можемо використати метод returnNewCharger() для створення нового BritishCharger, який ми зможемо використовувати для заряджання. Все, що нам потрібно зробити, це створити екземпляр нашого адаптера та передати йому наявний AmericanCharger, і він створить BritishCharger, який ми зможемо використовувати.

var myBritishDevice = new BritishDevice()
var americanChargerIFound = new AmericanCharger()
//We create the adapter and feed it the americanCharger
var myAdapter =  AmericanToBritishChargerAdapter(americanChargerIFound)
//calling returnNewCharger from myAdapter would return a BritishCharger
var britishChargerFromAdapter = myAdapter.returnNewCharger()
//and once we have the britishCharger we can now use it
myBritishDevice.charge(britishChargerFromAdapter)

RecyclerView.LayoutManager

Коли ми маємо справу з ViewGroup, у нас є Views, розміщені всередині неї. LayoutManager має завдання описати, як Views розміщені всередині.

Для порівняння, під час роботи з LinearLayout ViewGroup, нам потрібен варіант використання, який дозволяє розміщувати елементи вертикально або горизонтально. Це легко реалізувати, додавши атрибут орієнтації, який вказує нам, як LinearLayout буде розміщено на екрані. Ми можемо зробити це, використовуючи android:orientation=VERTICAL|HORIZONTAL атрибут.

У нас також є ще одна група ViewGroup під назвою GridLayout. Її використання — коли ми хочемо розмістити Views у прямокутній сітці. Це може бути пов'язано з такими причинами, як спрощення використання даних, які ми надаємо користувачеві програми. За своєю суттю, GridLayout дозволяє налаштування, які допоможуть вам досягти цієї мети, визначаючи розміри сітки; наприклад, ми можемо мати сітку 4×4 або 3×2.

RecyclerView.ViewHolder

ViewHolder – це абсцесtract-клас, який ми також розширюємо з RecyclerView. ViewHolder надає нам загальні методи, які допомагають нам посилатися на View, розміщений на RecyclerView, навіть після того, як механізм переробки в RecyclerView змінив різні посилання, про які нам невідомо.

Великі списки

RecyclerViews використовуються, коли ми хочемо представити користувачеві справді великий набір Views, не вичерпуючи при цьому весь Оперативна пам'ять на нашому пристрої для кожного створеного екземпляра View.

Якщо взяти до прикладу список контактів, ми матимемо загальне уявлення про те, як один контакт виглядатиме в ньому. Потім нам потрібно створити шаблон макета, який насправді є виглядом (View), зі слотами, де будуть заповнені різні дані з нашого списку контактів. Нижче наведено псевдокод, який пояснює всю цю мету:

//OneContactView
<OneContact>
<TextView>{{PlaceHolderForName}}</TextView>
<TextView>{{PlaceHolderForAddress}}</TextView>
<ImageView>{{PlaceHolderForProfilePicture}}</ImageView>
<TextView>{{PlaceHolderForPhoneNumber}}</TextView>
</OneContact>

Тоді у нас буде список контактів такого типу:

<ContactList>
</ContactList>

Якби ми жорстко прописували вміст, у нас не було б програмного способу додавання нового вмісту до списку без переписування програми. На щастя для нас, додавання View до ViewGroup підтримується... addView(view:View) метод. Навіть у цьому випадку RecyclerView не отримує дочірні представлення до нього.

У нашому випадку використання у нас буде довгий список контактів. Для кожного контакту у списку нам потрібно буде створити OneContactView та заповнити дані всередині View відповідно до полів у нашому класі Contact. Потім, коли у нас буде представлення, нам потрібно буде додати його до RecyclerView, щоб відобразити список.

data  class Contact(var name:String, var address:String, var pic:String, var phoneNumber:Int)

var contact1 = Contact("Guru","Guru97", "SomePic1.jpg", 991)
var contact2 = Contact("Guru","Guru98", "SomePic2.jpg", 992)
var contact3 = Contact("Guru","Guru99", "SomePic3.jpg", 993)

var myContacts:ArrayList<Contact> = arrayListOf<Contact>(contact1,contact2,contact3)

У нас є масив контактів. OneContactView містить слоти для отримання вмісту з класу Contact та його відображення. У RecyclerView нам потрібно додати до нього Views, щоб він міг допомогти нам з його здатністю до переробки.

RecyclerView насправді не дозволяє нам додавати представлення, але дозволяє нам додавати ViewHolder. Отже, в цьому сценарії у нас є два елементи, які ми хочемо з'єднати, але вони не збігаються. Ось тут і з'являється наш адаптер. RecyclerView надає нам адаптер, подібний до нашого AmericanToBritishChargerAdapter() з попереднього, що дозволило нам перетворити наш AmericanCharger, який був непридатний для використання з нашим BritishDevice, на щось корисне, схоже на адаптер живлення в реальному житті.

У цьому сценарії адаптер візьме наш масив контактів та наш View, і звідти згенерує ViewHolders, які RecyclerView готовий прийняти.

RecyclerView надає інтерфейс, який ми можемо розширити для створення нашого адаптера за допомогою класу RecyclerView.Adapter. Усередині цього адаптера є спосіб створити клас ViewHolder, з яким RecyclerView хоче працювати. Отже, ми маємо ту саму ситуацію, що й раніше, але з однією додатковою річчю – адаптером.

У нас є масив контактів, представлення для відображення одного контакту (OneContactView) та RecyclerView, що є списком представлень, що надають послуги переробки, але готові приймати лише ViewHolders. У цьому сценарії у нас тепер є клас RecyclerView.Adapter, який має метод для створення ViewHolders всередині.

fun createViewHolder(@NonNull parent: ViewGroup, viewType: Int): ViewHolder

RecyclerView.ViewHolder – це ABStract-клас, який приймає наш View як аргумент і перетворює його на ViewHolder. Він використовує шаблон-обгортку, що використовується для розширення можливостей класів.

Основа №2 – Шаблон обгортки

Ми використаємо простий приклад, щоб показати, як ми можемо змусити тварин говорити.

sealed trait Animal{
    fun sound():String
}

data class Cat(name:String):Animal{
    fun sound(){
        "Meow"
        }
}
data class Dog(name:String):Animal{
    fun sound(){
        "Woof"
        }
}

var cat1 = Cat("Tubby")
var dog1 = Dog("Scooby")
cat1.sound() //meow
dog1.sound() //woof

У наведеному вище прикладі у нас є дві тварини. Якби ми випадково захотіли додати метод, який би змусив їх говорити, але автор бібліотеки був нецікавим, ми все одно могли б знайти спосіб. Нам знадобилася б обгортка для нашого класу Animal. Ми б зробили це, прийнявши Animal як конструктор для нашого класу.

class SpeechPoweredAnimalByWrapper(var myAnimal:Animal){

    fun sound(){
        myAnimal.sound()
    }

    fun speak(){
        println("Hello, my name is ${myAnimal.name}")
    }
}

Тепер ми можемо передати екземпляр тварини до SpeechPoweredAnimalByWrapper. Виклик методу sound() для неї викличе переданий метод animal sound(). У нас також є додатковий метод speak(), який вважається новою функціональністю, яку ми додаємо до переданих тварин. Ми можемо використовувати його так:

var cat1 =  Cat("Garfield")
cat1.sound()//"meow"
cat1.speak()// doesn't work as it isn't implemented
var talkingCat = new SpeechPoweredAnimalByWrapper(cat1)
talkingCat.sound() //"meow" the sound method calls the one defined for cat1
talkingCat.speak() //"Hello, my name is Garfield"

Використовуючи цей шаблон, ми можемо використовувати класи та додавати функціональність. Все, що нам потрібно, це передати екземпляр класу та нові методи, визначені нашим обгортанням.ping клас.

У наведеному вище випадку ми використовували клас бетону. Також можливо реалізувати його в класі ABS.tract клас. Нам потрібно змінити клас SpeechPoweredAnimalByWrapper на abstract, і ми закінчили. Ми змінимо назву класу на коротшу, щоб зробити її читабельнішою.

abstract class SpeechPowered(var myAnimal:Animal){

    fun sound(){
        myAnimal.sound()
    }

    fun speak(){
        println("Hello, my name is ${myAnimal.name}")
    }
}

Це те саме, що й раніше, але означатиме дещо інше. У звичайному класі ми можемо мати екземпляр класу так само, як ми створили cat1 та dog1. AbstracОднак класи t не призначені для створення екземплярів, а призначені для розширення інших класів. Тож як би ми використовували новий SpeechPowered(var myAnimal:Animal) abstract-клас? Ми можемо використовувати його, створюючи нові класи, які розширять його та, в свою чергу, отримають його функціональність.

У нашому прикладі ми створимо клас SpeechPoweredAnimal, який розширює цей клас.

class SpeechPoweredAnimal(var myAnimal:Animal):SpeechPowered(myAnimal)
var cat1 =  Cat("Tubby")
var speakingKitty = SpeechPoweredAnimal(cat1)
speakingKitty.speak() //"Hello, my name is Tubby"

Це той самий шаблон, що використовується у ViewHolder. Клас RecyclerView.ViewHolder — це ABS.tract-клас, який додає функціональність до View, подібно до того, як ми додали метод speak до тварин. Додаткова функціональність робить його працездатним під час роботи з RecyclerView.

Ось як ми створимо OneContactViewHolder з OneContactView:

//The View argument we pass is converted to a ViewHolder which uses the View to give it more abilities and in turn work with the RecyclerView
class OneContactViewHolder(ourContactView: View) : RecyclerView.ViewHolder(ourContactView)

RecyclerView має адаптер, який дозволяє нам підключати наш масив контактів до ContactsView за допомогою RecyclerView.

Додавання перегляду

Група переглядів (ViewGroup) не перемальовується автоматично, а дотримується певного розкладу. На вашому пристрої може бути так, що вона перемальовується кожні 10 мс або 100 мс; або, якщо ми виберемо абсурдне число, скажімо, 1 хвилину, коли ми додамо View до ViewGroup, ви побачите зміни через 1 хвилину, коли ViewGroup «оновиться».

RecyclerView.Recycler

Основа №3 – Кешування

Один із найкращих прикладів того, де ми регулярно оновлюємо сторінку, – це браузер. Уявімо, наприклад, що сайт, який ми відвідуємо, є статичним і не надсилає контент динамічно; нам потрібно буде постійно оновлювати сторінку, щоб побачити зміни.

Для цього прикладу уявімо, що це Twitter. У нас буде список статичних твітів, і єдиний спосіб побачити нові твіти – це натиснути кнопку оновлення для повторного отримання вмісту.

Перемальовування всього екрана, очевидно, є дороговартісною справою. Уявіть, що у нас обмежена пропускна здатність від нашого телефонного оператора, а наш список твітів містить багато зображень і відео; було б дорого завантажувати весь вміст сторінки при кожному оновленні.

Нам потрібен спосіб зберігати вже завантажені твіти та гарантувати, що наш наступний запит матиме можливість повідомляти, які твіти вже є. Таким чином, система не завантажує все повторно, а отримує лише нові твіти, а також перевіряє, чи немає вже якогось локально збереженого твіту, щоб видалити його локально. Те, що ми описуємо, називається кешуванням.

Інформація, яку ми надсилаємо на веб-сайт про наявний у нас контент, називається метаданими. Тож, по суті, ми не просто кажемо «ми хочемо завантажити ваш сайт», ми кажемо «ми хочемо завантажити ваш сайт, і ось деякий контент, який ми вже зберегли з моменту останнього завантаження; будь ласка, використовуйте його, щоб надсилати нам лише те, чого там немає, щоб ми не використовували багато пропускної здатності».

Макет викликів – список твітів, мабуть, божевільний

Прикладом виклику макета є scrollToPosition. Це поширений приклад, який можна знайти в таких додатках для спілкування в чаті. Якщо хтось із ланцюжка чату відповідає на попереднє повідомлення, деякі додатки для спілкування додають відповідь і посилання на повідомлення, після натискання на яке ви перейдете туди, де було ваше початкове повідомлення.

У випадку, якщо ми викликаємо цей метод до того, як до нашого RecyclerView буде додано LayoutManager, і до того, як у нас буде RecyclerView.Adapter, scrollToPosition(n:Int) просто ігнорується.

Зв'язок між компонентами RecyclerView

Основа №4 – Зворотні виклики

RecyclerView, виконуючи свою роботу, має багато рухомих частин. Він має взаємодіяти з LayoutManager, який вказує нам, як організувати Views, лінійно або у вигляді сітки. Він має взаємодіяти з адаптером, який виконує завдання перетворення наших елементів (contactList) у Views (OneContactView), а потім у ViewHolders (OneContactViewHolder), з якими RecyclerView готовий працювати, використовуючи надані ним методи.

Сировиною для RecyclerView є наші Views, наприклад OneContactView, та джерело даних.

contactList:Array<Contact>

Ми використали простий сценарій як відправну точку, щоб зрозуміти, чого намагається досягти RecyclerView. Базовий випадок, коли у нас є статичний масив із 1000 контактів, які ми хочемо показати користувачеві, легко зрозуміти. Механізм RecyclerView по-справжньому починає працювати, коли список перестає бути статичним. З динамічним списком нам потрібно подумати про те, що відбувається з View на екрані, коли ми додаємо елемент до списку або видаляємо елемент зі списку.

RecyclerView.LayoutManager

Окрім визначення способу розташування наших представлень, лінійно чи у вигляді сітки, LayoutManager виконує багато роботи «під капотом» у hel.ping Переробник знає, коли потрібно займатися переробкою.

Він відповідає за збереженняping track представлень, що наразі видно на екрані, та передача цієї інформації механізму переробки. Коли користувач прокручує вниз, LayoutManager відповідає за інформування системи переробки про представлення, які виходять з фокусу зверху, щоб їх можна було використовувати повторно, замість того, щоб залишатися там і споживати пам'ять або знищуватися лише для створення нових.

Це означає, що LayoutManager повинен зберігати track місця, де знаходиться користувач під час прокручування нашого списку. Це досягається за допомогою списку позицій, що базуються на індексі, тобто перший елемент починається з 0 і збільшується відповідно до кількості елементів у нашому списку.

Якщо ми можемо переглянути 10 елементів у нашому списку, скажімо, зі 100, то на початку LayoutManager знає, що у фокусі у нього View-0 аж до View-9. Під час прокручування LayoutManager може обчислити представлення, які вийшли з фокуса.

LayoutManager може передати ці представлення механізму переробки, щоб їх можна було використовувати повторно (нові дані можна прив'язати до них; наприклад, контактні дані представлення можна видалити, а нові контактні дані з наступного сегмента можуть замінити заповнювачі).

Це було б вдалим випадком, якби наш список був статичним, але одним із найпоширеніших випадків використання RecyclerView є динамічні списки, де дані можуть надходити з онлайн-кінцевої точки або навіть, можливо, з датчика. Дані не лише додаються, але й іноді видаляються або оновлюються.

Динамічний стан наших даних може дуже ускладнити розуміння LayoutManager. З цієї причини LayoutManager веде власний список елементів та позицій, окремий від списку, який використовує компонент переробки. Це гарантує правильне виконання завдання макета.

Водночас, LayoutManager RecyclerView не хоче спотворювати наявні дані. Для коректної роботи LayoutManager синхронізується з RecyclerView.Adapter через задані інтервали (60 мс), обмінюючись інформацією про елементи нашого списку, тобто елементи, додані, оновлені, видалені або переміщені з однієї позиції в іншу. LayoutManager, отримавши цю інформацію, реорганізує вміст на екрані відповідно до змін, коли це необхідно.

Багато основних операцій, пов'язаних з RecyclerView, обертаються навколо зв'язку між RecyclerView.LayoutManager та RecyclerView.Adapter, які зберігають наші списки іноді статичних, а іноді динамічних даних.

Крім того, RecyclerView надає нам методи, які ми можемо використовувати для прослуховування подій, такі як onBindViewHolder, коли наш RecyclerView.Adapter прив'язує контент з нашого списку (наприклад, контакт) до ViewHolder, щоб він використовувався для відображення інформації на екрані.

Інший — onCreateViewHolder, який повідомляє нам, коли RecyclerView.Adapter приймає звичайний View, такий як OneContactView, та перетворює його на елемент ViewHolder, з яким RecyclerView може працювати.

Окрім основних механізмів, що забезпечують переробку, RecyclerView надає способи налаштування поведінки без впливу на переробку. Повторне використання Views ускладнює виконання звичайних дій, до яких ми звикли зі статичними представленнями, таких як реагування на події onClick.

Як нам відомо, RecyclerView.LayoutManager який представляє Views користувачеві може на мить мати інший список елементів від RecyclerView.Adapter список якого зберігається в базі даних або потоково передається з джерела. Розміщення подій OnClick безпосередньо у Views може призвести до неочікуваної поведінки, такої як видалення неправильного контакту.

Gradle

Якщо ми хочемо використовувати RecyclerView, нам потрібно додати його як залежність у наш файл build.gradle. У наступному прикладі ми використали реалізацію “androidx.recyclerview:recyclerview:1.1.0”, яка є найновішою версією згідно з цією статтею.

Після додавання залежності до нашого Gradle файл, нам буде запропоновано Android Studio синхронізувати зміни. Ось як наші Gradle Файл виглядатиме після додавання RecyclerView у порожній проект лише з налаштуваннями за замовчуванням.

apply plugin: 'com.android.application'

apply plugin: 'kotlin-android'

apply plugin: 'kotlin-android-extensions'

android {
    compileSdkVersion 29
    buildToolsVersion "29.0.2"
    defaultConfig {
        applicationId "com.guru99.learnrecycler"
        minSdkVersion 17
        targetSdkVersion 29
        versionCode 1
        versionName "1.0"
        testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
    }
    buildTypes {
        release {
            minifyEnabled false
            proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'),
             'proguard-rules.pro'
        }
    }
}

dependencies {
    implementation fileTree(dir: 'libs', include: ['*.jar'])
    implementation"org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version"
    implementation 'androidx.appcompat:appcompat:1.1.0'
    implementation 'androidx.core:core-ktx:1.1.0'
    implementation 'androidx.constraintlayout:constraintlayout:1.1.3'
    testImplementation 'junit:junit:4.12'
    androidTestImplementation 'androidx.test.ext:junit:1.1.1'
    androidTestImplementation 'androidx.test.espresso:espresso-core:3.2.0'

    implementation "androidx.recyclerview:recyclerview:1.1.0"
}

Наразі у нас є лише один файл макета. Почнемо з простого прикладу, де ми використовуємо RecyclerView для відображення списку назв фруктів на екрані.

Перелік предметів

Ми перейдемо до нашого файлу MainActivity та створимо масив з назвами фруктів всередині, безпосередньо перед методом onCreate(), який був згенерований під час налаштування.

package com.guru99.learnrecycler

import androidx.appcompat.app.AppCompatActivity
import android.os.Bundle

class MainActivity : AppCompatActivity() {

    var fruitNames:Array<String> = arrayOf<String>("Banana", "Mango", "Passion fruit", "Orange", "Grape")

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)
    }
}

Нашою наступною метою буде відображення цього списку на екрані за допомогою RecyclerView. Для цього ми перейдемо до каталогу макетів, який містить наші макети, і створимо View, який буде відповідати за показ одного фрукта.

Макет, який буде використовуватися для кожного елемента в нашому списку

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

У TextView вище ми додали поле id, яке буде використовуватися для ідентифікації View. Воно не генерується за замовчуванням. Ми надали нашому TextView id fruitName, щоб він відповідав даним, які будуть до нього прив'язані.

Додавання RecyclerView до основного макета

У цій самій активності для нас за замовчуванням було згенеровано файл макета main_layout.xml. Якщо ми виберемо порожній проект, він згенерує XML міститиме ConstraintLayout, а всередині буде TextView з текстом «Hello». Ми видалимо весь вміст і зробимо так, щоб макет містив лише RecyclerView, як показано нижче:

<?xml version="1.0" encoding="utf-8"?>
<androidx.recyclerview.widget.RecyclerView 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:id="@+id/fruitRecyclerView"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context=".MainActivity" />

Ми також додали атрибут id для RecyclerView, який використовуватимемо для посилання на нього в нашому коді.

android:id="@+id/fruitRecyclerView"

Потім ми повернемося до нашого файлу MainActivity. Використовуючи створені нами ідентифікатори, ми зможемо посилатися на щойно створені представлення (Views). Ми почнемо з посилання на RecyclerView за допомогою методу findViewById(), що надається ` AndroidМи зробимо це в нашому методі onCreate(). Наш метод onCreate() виглядатиме наступним чином.

override fun onCreate(savedInstanceState: Bundle?) {
    super.onCreate(savedInstanceState)
    setContentView(R.layout.activity_main)

    var myFruitRecyclerView: RecyclerView = findViewById(R.id.fruitRecyclerView)
}

Створіть ViewHolder

Далі ми створимо RecyclerView.ViewHolder, який відповідає за перетворення нашого View на ViewHolder, який RecyclerView використовує для відображення наших елементів. Ми зробимо це одразу після нашого методу onCreate().

package com.guru99.learnrecycler

import androidx.appcompat.app.AppCompatActivity
import android.os.Bundle
import android.view.View
import androidx.recyclerview.widget.RecyclerView

class MainActivity : AppCompatActivity() {

    var fruitNames:Array<String> = arrayOf<String>("Banana", "Mango", "Passion fruit", "Orange", "Grape")

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)

        var myFruitRecyclerView: RecyclerView = findViewById(R.id.fruitRecyclerView)
    }

    class FruitViewHolder(fruitView: View): RecyclerView.ViewHolder(fruitView)

}

Створіть RecyclerViewAdapter

Далі ми створимо клас FruitArrayAdapter, який розширює клас RecyclerView.Adapter. Створений нами FruitArrayAdapter відповідатиме за наступне: він братиме назви фруктів з масиву фруктів, створюватиме ViewHolder, використовуючи наш вигляд one_fruit_view.xml, потім прив'язуватиме фрукт до ViewHolder та динамічно прив'язуватиме вміст до створеного нами вигляду.

package com.guru99.learnrecycler

import androidx.appcompat.app.AppCompatActivity
import android.os.Bundle
import android.view.View
import androidx.recyclerview.widget.RecyclerView

class MainActivity : AppCompatActivity() {

    var fruitNames:Array<String> = arrayOf<String>("Banana", "Mango", "Passion fruit", "Orange", "Grape")

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)

        var myFruitRecyclerView: RecyclerView = findViewById(R.id.fruitRecyclerView)
    }

    class FruitViewHolder(fruitView: View): RecyclerView.ViewHolder(fruitView)

    class FruitArrayAdapter(var fruitArray: Array<String>) : RecyclerView.Adapter<FruitViewHolder>()
}

Android Studio додасть червоні хвилясті лінії на наш FruitArrayAdapter, які повідомлять нам про те, що нам потрібно реалізувати методи, які RecyclerView може використовувати для підключення нашого масиву до ViewHolder.

class FruitListAdapter(var fruitArray: Array<String>) : RecyclerView.Adapter<FruitViewHolder>() {
     override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): FruitViewHolder {

     }

     override fun getItemCount(): Int {

     }

     override fun onBindViewHolder(holder: FruitViewHolder, position: Int) {

     }
 }

Почнемо з найпростішого фрагмента згенерованого коду – методу getItemCount(). Ми знаємо, як отримати кількість елементів у нашому масиві, викликавши властивість array size.

class FruitListAdapter(var fruitArray: Array<String>) : RecyclerView.Adapter<FruitViewHolder>() {
        override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): FruitViewHolder {

        }

        override fun getItemCount(): Int {
         return fruitArray.size
        }

        override fun onBindViewHolder(holder: FruitViewHolder, position: Int) {

        }
    }

Потім ми реалізуємо метод onCreateViewHolder. Саме тут RecyclerView просить нас допомогти йому створити FruitViewHolder. Якщо ми пам'ятаємо, наш клас FruitViewHolder виглядав ось так:

class FruitViewHolder(fruitView: View): RecyclerView.ViewHolder(fruitView)

Для цього потрібен наш fruitView, який ми створили як XML-файл one_fruit_view.xml. Ми можемо створити посилання на цей XML та перетворити його на View наступним чином.

class FruitListAdapter(var fruitArray: Array<String>) : RecyclerView.Adapter<FruitViewHolder>() {

        override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): FruitViewHolder {

            var fruitView = LayoutInflater.from(parent.context).inflate(R.layout.one_fruit_view, parent, false)

            var fruitViewHolder = FruitViewHolder(fruitView)

            return fruitViewHolder

        }

        override fun getItemCount(): Int {
            return fruitArray.size
        }

        override fun onBindViewHolder(holder: FruitViewHolder, position: Int) {

        }
    }

Решта біта — це перевизначення onBindViewHolder.

fun onBindViewHolder(holder: FruitViewHolder, position: Int)

RecyclerView.Adapter запитує ціле число позиції, яке ми використовуємо для вибору елемента зі списку. Він також надає нам об'єкт-холдер, щоб ми могли прив'язати елемент, отриманий з fruitArray, до View, який зберігається всередині холдера View. View, що зберігається всередині ViewHolder, доступний через поле ViewHolder.itemView. Після отримання представлення ми можемо використовувати ідентифікатор fruitName, який ми створили раніше, для встановлення вмісту.

override fun onBindViewHolder(holder: FruitViewHolder, position: Int) {

          var ourFruitTextView = holder.itemView.findViewById<TextView>(R.id.fruitName)

          var aFruitName = fruitArray.get(position)

          ourFruitTextView.setText(aFruitName)
      }

На цьому наш FruitArrayAdapter завершений і виглядає наступним чином.

class FruitListAdapter(var fruitArray: Array<String>) : RecyclerView.Adapter<FruitViewHolder>() {

        override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): FruitViewHolder {

            var fruitView = LayoutInflater.from(parent.context).inflate(R.layout.one_fruit_view, parent, false)

            var fruitViewHolder = FruitViewHolder(fruitView)

            return fruitViewHolder
        }

        override fun getItemCount(): Int {
            return fruitArray.size
        }

        override fun onBindViewHolder(holder: FruitViewHolder, position: Int) {

            var ourFruitTextView = holder.itemView.findViewById<TextView>(R.id.fruitName)

            var aFruitName = fruitArray.get(position)

            ourFruitTextView.setText(aFruitName)
        }
    }

Нарешті, ми готові підключити решту частин нашого RecyclerView, які створюють LayoutManager, що вкаже RecyclerView, як відображати вміст списку: лінійно за допомогою LinearLayoutManager чи у вигляді сітки за допомогою GridLayoutManager або StaggeredGridLayoutManager.

Створити менеджер макетів

Ми повернемося всередину нашої функції onCreate та додамо LayoutManager.

override fun onCreate(savedInstanceState: Bundle?) {
       super.onCreate(savedInstanceState)
       setContentView(R.layout.activity_main)

       var myFruitRecyclerView: RecyclerView = findViewById(R.id.fruitRecyclerView)

       var fruitLinearLayout = LinearLayoutManager(this)

       myFruitRecyclerView.layoutManager =fruitLinearLayout

   }

Підключіть наш адаптер до елементів і встановіть його на RecyclerView

override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)

        var myFruitRecyclerView: RecyclerView = findViewById(R.id.fruitRecyclerView)

        var fruitLinearLayout = LinearLayoutManager(this)

        myFruitRecyclerView.layoutManager =fruitLinearLayout

        var fruitListAdapter = FruitListAdapter(fruitNames)

        myFruitRecyclerView.adapter =fruitListAdapter
    }

Ми також створили екземпляр fruitListAdapter та передали йому масив назв фруктів. І, в принципі, все готово. Повний файл MainActivity.kt виглядає наступним чином.

package com.guru99.learnrecycler

import androidx.appcompat.app.AppCompatActivity
import android.os.Bundle
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.widget.TextView
import androidx.recyclerview.widget.LinearLayoutManager
import androidx.recyclerview.widget.RecyclerView

class MainActivity : AppCompatActivity() {

    var fruitNames:Array<String> = arrayOf<String>("Banana", "Mango", "Passion fruit", "Orange", "Grape")

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)

        var myFruitRecyclerView: RecyclerView = findViewById(R.id.fruitRecyclerView)

        var fruitLinearLayout = LinearLayoutManager(this)

        myFruitRecyclerView.layoutManager =fruitLinearLayout

        var fruitListAdapter = FruitListAdapter(fruitNames)

        myFruitRecyclerView.adapter =fruitListAdapter
    }

    class FruitViewHolder(fruitView: View): RecyclerView.ViewHolder(fruitView)

    class FruitListAdapter(var fruitArray: Array<String>) : RecyclerView.Adapter<FruitViewHolder>() {

        override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): FruitViewHolder {

            var fruitView = LayoutInflater.from(parent.context).inflate(R.layout.one_fruit_view, parent, false)

            var fruitViewHolder = FruitViewHolder(fruitView)

            return fruitViewHolder
        }

        override fun getItemCount(): Int {
            return fruitArray.size
        }

        override fun onBindViewHolder(holder: FruitViewHolder, position: Int) {

            var ourFruitTextView = holder.itemView.findViewById<TextView>(R.id.fruitName)

            var aFruitName = fruitArray.get(position)

            ourFruitTextView.setText(aFruitName)
        }
    }
}

Завантажте проект

Поширені запитання

RecyclerView — це гнучкіший та ефективніший наступник ListView. Він застосовує шаблон ViewHolder, підтримує лінійні, сітчасті та ступінчасті макети за допомогою менеджерів макетів, а також додає вбудовану анімацію елементів, тоді як ListView за замовчуванням не пропонує нічого з цього.

GridView відображає елементи лише у фіксованій сітці. RecyclerView може відтворювати сітку за допомогою GridLayoutManager, але він також підтримує лінійні та ступінчасті макети, переробку виглядів та анімацію, що робить його набагато універсальнішим, ніж GridView.

Цей приклад RecyclerView написаний на Kotlin, мові, офіційно рекомендованій Google та цінності Android розробка. Ті самі API RecyclerView, адаптера та менеджера макета також працюють у Java.

Приєднайте слухач кліків всередині ViewHolder або onBindViewHolder, а потім надайте його через зворотний виклик інтерфейсу активності або фрагменту. Використання позиції ViewHolder дозволяє уникнути прив'язки кліків до неправильного переробленого елемента.

DiffUtil — це утилітний клас, який обчислює різницю між двома списками та надсилає мінімальний набір оновлень до адаптера. Він робить динамічні зміни списків ефективними та автоматично анімує додавання, видалення та переміщення.

Jetpack Compose використовує LazyColumn та LazyRow замість RecyclerView для нових екранів. Однак RecyclerView залишається широко використовуваним в існуючих проектах на основі View та може бути вбудований у Compose через API сумісності.

Так. Помічники штучного інтелекту, такі як GitHub Copilot та інструменти на основі GPT, можуть створювати каркас адаптера RecyclerView, ViewHolder та шаблонного коду onBindViewHolder з короткого запиту. Розробникам все одно слід перевіряти згенерований код на правильність та продуктивність.

навчання за допомогою машини Android Програми використовують RecyclerView для відображення динамічних результатів, таких як рекомендації, рейтинги пошуку або класифікації зображень. Адаптер пов'язує результати моделі з елементами списку, щоб великі, часто змінювані набори даних прокручувалися плавно.

Підсумуйте цей пост за допомогою: