Android RecyclerView: що таке, дізнайтеся на простих прикладах
Що таке RecyclerView Android?
Команда RecyclerView це віджет, який є більш гнучкою та розширеною версією GridView та ListView. Це контейнер для відображення великих наборів даних, які можна ефективно прокручувати, зберігаючи обмежену кількість переглядів. Ви можете використовувати віджет RecyclerView, якщо у вас є колекції даних, елементи яких змінюються під час виконання залежно від події мережі або дії користувача.
думки
Команда Android Платформа використовує класи View і ViewGroup для малювання елементів на екрані. Ці класи є абстрактними та розширені до різних реалізацій відповідно до конкретного випадку використання. TextView, наприклад, має просте призначення відображення текстового вмісту на екрані. EditText походить від того самого класу View і додає більше функціональних можливостей, щоб користувач міг вводити дані.
Можна створювати власні власні перегляди, щоб досягти більшої гнучкості під час розробки інтерфейсів користувача. Клас View надає методи, які ми можемо замінити для малювання на екрані, і засоби для передачі таких параметрів, як ширина, висота та наші власні користувацькі атрибути, які ми хотіли б додати до нашого View, щоб він поводився так, як ми хочемо.
ViewGroups
Клас ViewGroup є різновидом View, але, на відміну від простого класу View, який відповідає за просто відображення, ViewGroup дає нам можливість помістити кілька представлень в одне представлення, на яке ми можемо посилатися як на одне ціле. У цьому випадку представлення, створене на верхньому рівні, до якого ми додаємо інші прості представлення (ми також можемо додати ViewGroups), називається «батьківським», а представлення, які додаються всередину, є «дочірніми».
Ми можемо відобразити 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 anology, we can now group views //together and the structure that would hold that would be what we call the ViewGroup
ViewGroup також дозволяє нам визначити, як діти організовані всередині перегляду, наприклад, розташовуються вертикально або горизонтально. Ми можемо мати різні правила взаємодії всередині View. Наприклад, TextViews, наступні один за одним, повинні мати відстань 12 dp, тоді як ImageViews, за якими слідує TextView, мають мати відстань 5 dp.
Так було б, якби ми розробляли власну ViewGroup з нуля. Щоб полегшити ці налаштування, Android надає клас під назвою LayoutParams, який ми можемо використовувати для введення цих конфігурацій.
Android документація надає деякі параметри за замовчуванням, які ми реалізуємо під час налаштування нашої власної ViewGroup. Деякі загальні параметри стосуються ширини, висоти та поля. За замовчуванням ці конфігурації мають структуру android:layout_height для висоти, наприклад, android:layout_width. У зв’язку з цим, коли ви створюєте свою ViewGroup, ви можете додатково створити LayoutParams відповідно до того, як ви бажаєте, щоб ваша ViewGroup працювала.
Android поставляється зі стандартними представленнями та групами переглядів, які ми можемо використовувати для виконання багатьох поширених завдань, які нам потрібні. Одним із прикладів, який ми згадали, є TextView. Це просте подання, яке включає такі параметри, як висота, ширина, розмір тексту тощо. У нас є ImageView для відображення зображень і EditText, як ми вже згадували, серед багатьох інших. Android також має спеціальні ViewGroups, до яких ми можемо додати наші Views і отримати очікувану поведінку.
Лінійний макет
LinearLayout дозволяє додавати в нього елементи View. LinearLayout — це атрибут орієнтації, який визначає, як він буде розміщений на екрані. Він також має LinearLayout.LayoutParams, які визначають правила для переглядів усередині, наприклад, атрибут android:center_horizontal центруватиме перегляди вздовж горизонтальної осі, тоді як `android:center_vertical центруватиме вміст перегляду вздовж вертикальної осі.
Ось кілька зображень, щоб мати розуміння центрування. Ми приймемо це як простий TextView всередині простору 200 на 200 пікселів, атрибути центрування змусять його поводитися наступним чином.
android:center_horizontal
android:center_vertical
android:center
Основні компоненти RecyclerView
Нижче наведено важливі компоненти RecyclerView:
RecyclerView.Adapter
Основа №1 – шаблон адаптера
Адаптер — це пристрій, який перетворює атрибути системи або пристрою на атрибути несумісного пристрою або системи. Деякі з них змінюють атрибути сигналу або живлення, а інші просто адаптують фізичну форму одного роз’єму до іншого.
Простий приклад, який ми знаходимо в реальному житті, щоб пояснити адаптер, коли нам потрібно з’єднати пристрої разом, але вони мають порти підключення, які не збігаються один з одним. Це може статися, коли ви відвідуєте іншу країну, де використовують різні види розеток. Якщо ви носите зарядний пристрій для телефону чи ноутбука, його неможливо буде підключити до розетки. Однак ви б не здавалися, а просто придбали б адаптер, який встановлюватиметься між розеткою та зарядним пристроєм і дозволить заряджати.
Це той випадок у програмуванні, коли ми хочемо з’єднати дві структури даних разом, щоб виконати завдання, але їхні порти за замовчуванням не мають способу зв’язуватися один з одним.
Ми будемо використовувати простий приклад пристрою та зарядного пристрою. У нас буде два екземпляри зарядних пристроїв. Американський і британський
class AmericanCharger() { var chargingPower = 10 } class BritishCharger(){ var charginPower = 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 charingPower:Int = charger.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(theAmericanCharger) //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 ми хочемо використовувати можливість розміщувати елементи вертикально або горизонтально. Це легко реалізувати, додавши атрибут орієнтації, який повідомляє нам, як лінійний макет буде розміщено на екрані. Ми можемо зробити це, використовуючи android:orientation=VERTICAL|HORIZONTAL
атрибут.
Ми також маємо іншу ViewGroup під назвою GridLayout, її використання, коли ми хочемо розмістити Views у прямокутній структурі Grid. Це може бути з таких причин, як полегшення використання даних, які ми надаємо користувачеві програми. За своєю конструкцією GridLayout дозволяє конфігурації, які допоможуть вам досягти цієї мети, маючи конфігурації, де ми можемо визначати розміри сітки, наприклад, ми можемо мати сітку 4×4, сітку 3 x 2.
RecyclerView.ViewHolder
ViewHolder — це абстрактний клас, який ми також розширюємо з RecyclerView. ViewHolder надає нам загальні методи, які допомагають нам посилатися на View, який ми розмістили в RecyclerView, навіть після того, як механізм Recycler у RecyclerView змінив різні посилання, про які ми не знаємо.
Великі списки
RecyclerViews використовуються, коли ми хочемо представити дійсно великий набір Views користувачеві, при цьому не виснажуючи наш Оперативна пам'ять на нашому пристрої для кожного створеного екземпляра View.
Якщо ми візьмемо список контактів, ми матимемо загальне уявлення про те, як один контакт буде виглядати в списку. Тоді ми створимо макет шаблону, який насправді є представленням, із слотами, які заповнюватимуть різні дані з нашого списку контактів. Нижче наведено псевдокод, який пояснює всю мету:
//OneContactView <OneContact> <TextView>{{PlaceHolderForName}}</TextView> <TextView>{{PlaceHolderForAddress}}</TextView> <ImageView>{{PlaceHolderForProfilePicture}}</ImageView> <TextView>{{PlaceHolderForPhoneNumber}}</TextView> </OneContact>
Тоді ми мали б ContactList такого характеру
<ContactList> </ContactList>
Якщо це так, ми жорстко кодували вміст, у нас не було б програмного способу додавання нового вмісту до списку без переписування програми. На щастя для нас. Додавання представлення до ViewGroup підтримується an 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, на щось придатне для використання, схоже на адаптер живлення в реальному житті.
У цьому сценарії адаптер візьме наш масив контактів і наше представлення, а звідти створить ViewHolders, які RecyclerView готовий прийняти.
RecyclerView надає інтерфейс, який ми можемо розширити для створення нашого адаптера через клас RecyclerView.Adapter. Всередині цього адаптера є спосіб створити клас ViewHolder, з яким хоче працювати RecyclerView. Отже, ми маємо ту саму ситуацію, що й раніше, але з однією додатковою річчю, тобто адаптером.
У нас є масив контактів, представлення для відображення одного контакту OneContactView. RecyclerView — це список Views, які надають послуги переробки, але готові приймати лише ViewHolders
Але в цьому сценарії ми тепер маємо клас RecyclerView.Adapter, який має метод для створення ViewHolders всередині.
fun createViewHolder(@NonNull parent: ViewGroup, viewType: Int): ViewHolder
RecyclerView.ViewHolder — це абстрактний клас, який приймає наш 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. Ми б зробили це, взявши Тварину як конструктор для нашого класу
class SpeechPoweredAnimalByWrapper(var myAnimal:Animal){ fun sound(){ myAnimal.sound() } 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"
Використовуючи цей шаблон, ми можемо проводити заняття та додавати функціональні можливості. Все, що нам потрібно, це передати екземпляр класу та нові методи, визначені нашим класом обгортки.
У нашому випадку вище ми використовували конкретний клас. Те ж саме можна реалізувати в абстрактному класі. Нам потрібно було б додати змінити клас SpeechPoweredAnimalByWrapper на абстрактний, і ми готові. Ми змінимо назву класу на коротшу, щоб її було легше читати.
abstract class SpeechPowered(var myAnimal:Animal){ fun sound(){ myAnimal.sound() } speak(){ println("Hello, my name is ${myAnimal.name}") } }
Це те саме, що й раніше, але це означатиме щось інше. У звичайному класі ми можемо мати екземпляр класу так само, як ми створили cat1 і dog1. Однак абстрактні класи не призначені для створення екземплярів, а призначені для розширення інших класів. Отже, як би ми використали новий абстрактний клас SpeechPowered(var myAnimal:Animal). Ми можемо використовувати його, створюючи нові класи, які розширять його та, у свою чергу, отримають його функціональність.
У нашому прикладі ми створимо клас 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 — це абстрактний клас, який додає функціональність до 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 має адаптер, який дозволяє підключити наш масив Contacts до ContactsView за допомогою RecyclerView
Додавання перегляду
ViewGroup не перемальовує ViewGroup автоматично, а дотримується певного розкладу. Можливо, на вашому пристрої він перемальовується кожні 10 мс або 100 мс, або якщо ми виберемо абсурдне число, скажімо, 1 хвилину, коли ми додаємо View до ViewGroup, ви побачите зміни через 1 хвилину, коли ViewGroup «оновиться».
RecyclerView.Recycler
Основа №3. Кешування
Одним із найкращих прикладів того, де ми регулярно оновлюємо дані, є веб-переглядач. Уявімо, наприклад, що сайт, який ми відвідуємо, є статичним і не надсилає вміст динамічно, нам потрібно буде постійно оновлювати його, щоб побачити зміни.
Для цього прикладу уявімо, що це сайт Twitter. У нас була б серія статичних твітів у списку, і єдиний спосіб побачити нові твіти – це натиснути кнопку оновлення, щоб повторно отримати вміст.
Перефарбовувати весь екран, очевидно, дорого. Якби ми мали уявити, що це було так, у нас була обмежена пропускна здатність у нашого телефонного провайдера. У нашому списку твітів було багато зображень і відео, було б дорого завантажувати весь вміст сторінки під час кожного оновлення.
Нам потрібен спосіб зберігати вже завантажені твіти та гарантувати, що наш наступний запит матиме можливість сказати твіти, які він уже має. Таким чином, він не завантажує все повторно, а отримує лише нові твіти, які має, а також перевіряє, чи немає локально збережених твітів, щоб можна було видалити їх локально. Те, що ми описуємо, називається кешуванням.
Інформація, яку ми надсилаємо на веб-сайт про наявний у нас вміст, називається метаданими. Отже, насправді ми не просто говоримо «ми хочемо завантажити ваш сайт», ми кажемо «ми хочемо завантажити ваш сайт, і ось деякий вміст, який ми вже зберегли під час останнього завантаження, використовуйте його, щоб надсилайте нам лише те, чого там немає, щоб ми не використовували велику пропускну здатність, оскільки у нас не так багато ресурсів».
Макет викликів – список твітів, мабуть, божевільний
Прикладом викликів макета є scrollToPosition
Це поширений приклад, який присутній у таких речах, як програми для чату. Якщо хтось у ланцюжку чату відповідає на спливаючу підказку чату, яка була раніше, деякі програми для чату включають відповідь і посилання на спливаючу підказку чату, після натискання якої ви переходите до місця, де було ваше вихідне повідомлення.
У цьому випадку ми викликаємо цей метод до того, як до нашого RecyclerView буде додано LayoutManager і до того, як у нас буде RecyclerView.Adapter, scrollToPosition(n:Int) просто ігнорується.
Зв'язок між компонентами RecyclerView
Основа №4. Зворотні дзвінки
RecyclerView, виконуючи свою роботу, має багато рухомих частин. Він має працювати з LayoutManager, який говорить нам, як організувати представлення, чи то лінійно, чи то в сітці. Він має працювати з адаптером, який виконує роботу з перетворення наших елементів contactList у Views OneContactView, а потім у ViewHolders OneContactViewHolder, з яким RecyclerView готовий працювати в межах методів, які він нам надає.
Сировиною для RecyclerView є наші Views, наприклад OneContactView та джерело даних.
contactList:Array<Contact>
Ми використали простий сценарій як відправну точку, щоб зрозуміти, чого намагається досягти RecyclerView.
Базовий випадок, коли у нас буде статичний масив із 1000 контактів, які ми хочемо показати користувачеві, легко зрозуміти.
Механізм RecyclerView дійсно починає оживати, коли список більше не є статичним.
З динамічним списком ми маємо думати про те, що відбувається з View на екрані, коли ми додаємо елемент до списку або видаляємо елемент зі списку.
RecyclerView.LayoutManager
Крім того, ми вирішуємо, як наші погляди мають бути викладені лінійно чи сіткою. LayoutManager виконує велику роботу під капотом, допомагаючи переробнику знати, коли робити переробку.
Він відповідає за відстеження переглядів, які зараз відображаються на екрані, і передачу цієї інформації механізму переробки. Коли користувач прокручує вниз, менеджер макета несе відповідальність за інформування системи Recycling про Views, які вийшли з фокусу вгорі, щоб їх можна було використовувати повторно замість того, щоб вони залишалися там і споживали пам’ять, або замість того, щоб знищувати їх і створювати новенькі.
Це означає, що LayoutManager повинен стежити за тим, де знаходиться користувач, коли він прокручує наш список. Це робиться за допомогою списку позицій, які є базою індексу, тобто перший елемент має починатися з 0 і збільшуватися відповідно до кількості елементів у нашому списку.
Якщо ми можемо переглянути 10 елементів у нашому списку, скажімо, 100, на початку, LayoutManager знає, що він має у фокусі view-0 аж до View-9. Коли ми прокручуємо, LayoutManager може обчислити види, які виходять фокусу.
LayoutManager може передавати ці подання механізму Recycling, щоб їх можна було повторно використовувати (до них можна прив’язати нові дані, наприклад, контактні дані View можна видалити, а нові контактні дані з наступного сегмента можуть замінити заповнювачі).
Це був би щасливий випадок, якщо б список, який ми маємо, був статичним, але один із найпоширеніших випадків використання RecyclerView — це динамічні списки, де дані можуть надходити з онлайн-кінцевої точки або навіть, можливо, із датчика. Дані не тільки додаються, але й іноді видаляються або оновлюються дані з нашого списку.
Динамічний стан наших даних може ускладнити міркування про LayoutManager. З цієї причини LayoutManager підтримує власний список елементів і позицій, який є окремим від списку, який використовує компонент Recycling. Це гарантує, що він правильно виконує свою роботу з компонування.
У той же час LayoutManager RecyclerView не хоче спотворювати дані, які він має. Щоб працювати правильно, LayoutManager синхронізується з RecyclerView.Adapter через задані проміжки часу (60 мс), обмінюючись інформацією про елементи нашого списку. тобто елементи додані, оновлені, видалені, переміщені з однієї позиції в іншу). LayoutManager, отримавши цю інформацію, реорганізовує вміст на екрані відповідно до змін, коли це необхідно.
Багато основних операцій, пов’язаних із RecylerView, обертаються навколо зв’язку між RecyclerView.LayoutManager і RecyclerView.Adapter, який зберігає наші списки іноді статичних або іноді динамічних даних.
Крім того, RecyclerView надає нам методи, які ми можемо використовувати для прослуховування подій, таких як onBindViewHolder, коли наш RecyclerView.Adapter прив’язує вміст із нашого списку, наприклад, контакт із ViewHolder, щоб тепер він використовувався для відображення інформації на екрані.
Інший — onCreateViewHolder, який повідомляє нам, коли RecyclerView. Адаптер приймає звичайне представлення, наприклад OneContactView, і перетворює його на елемент ViewHolder, з яким може працювати RecyclerView. З нашого ViewHolder для використання RecyclerView. onViewDetached
Окрім основних механізмів, які забезпечують переробку. RecyclerView надає способи налаштування поведінки, не впливаючи на переробку.
Повторне використання представлень ускладнює виконання типових речей, які ми звикли робити зі статичними представленнями, наприклад реагування на події onClick.
Оскільки ми знаємо, що RecyclerView.LayoutManager
який представляє Views користувачеві може на мить мати інший список елементів від RecyclerView.Adapter
який містить список, який ми зберегли в базі даних або потоково надсилаємо з джерела. Розміщення подій OnClick безпосередньо у Views може призвести до неочікуваної поведінки, як-от видалення неправильного контакту або зміни.
Gradle
Якщо ми хочемо використовувати RecyclerView, нам потрібно додати його як залежність у наш файл .gradle збірки.
У наступному прикладі ми використали реалізацію «androidx.recyclerview:recyclerview:1.1.0», яка є найновішою версією відповідно до цієї статті.
Після додавання залежності до нашого Gradle файл, нам буде запропоновано Android Студія до Syncхронізувати зміни,
Ось так наші 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.
Для цього ми перейдемо до каталогу макетів, який містить наші макети, і створимо представлення, яке відповідатиме за показ одного фрукта.
Макет, який буде використовуватися для кожного елемента в нашому списку
<?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 вище ми додали поле ідентифікатора, яке використовуватиметься для ідентифікації View.
Він не генерується за замовчуванням. Ми маємо наш TextView ідентифікатор 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. Використовуючи ідентифікатори, які ми створили, ми зможемо посилатися на представлення, які ми щойно створили.
Ми почнемо з посилання на 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 і динамічно прив’яже вміст до View, який ми створили one_fruit_view.xml.
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, який може використовувати RecyclerView.
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.length.
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) { } }
Потім ми реалізуємо метод override fun onCreateViewHolder.
Тут RecyclerView просить нас допомогти йому створити для нього FruitHolder.
Якщо ми згадаємо, як виглядав наш клас 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) { } }
Біт, що залишився, є перевизначенням
fun onBindViewHolder(holder: FruitViewHolder, position: Int)
RecyclerView.Adapter запитує ціле число позиції, яке ми будемо використовувати для отримання елемента з нашого списку. Він також надає нам утримувач, щоб ми могли прив’язати елемент, який отримуємо з fruitArray, до View, який зберігається всередині утримувача представлення.
Перегляд, який зберігається всередині ViewHoder, доступний через поле 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) } } }