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, следващи един след друг, трябва да имат 12dp разстояние, докато ImageViews, последвани от TextView, трябва да имат разстояние 5dp.

Това би било така, ако разработвахме собствена ViewGroup от нулата. За да улесните тези конфигурации, Android предоставя клас, наречен LayoutParams, който можем да използваме за въвеждане на тези конфигурации.

Android документация предоставя някои параметри по подразбиране, които бихме приложили, когато конфигурираме нашата собствена ViewGroup. Някои общи параметри са тези, които се отнасят до ширина, височина и поле. По подразбиране тези конфигурации имат структурата android:layout_height за височина, например android:layout_width В тази връзка, когато създавате своя ViewGroup, можете допълнително да създадете LayoutParams, специфични за начина, по който бихте искали да се държи вашата ViewGroup.

Android идва с изгледи по подразбиране и ViewGroups, които можем да използваме, за да изпълняваме много от обичайните задачи, от които се нуждаем. Един пример, който споменахме, е TextView. Това е прост изглед, който идва с конфигурируеми аспекти като височина, ширина, размер на текста и други подобни. Имаме ImageView за показване на изображения и EditText, както споменахме, наред с много други. Android също има персонализирани ViewGroups, към които можем да добавим нашите Views и да получим очакваното поведение.

LinearLayout

LinearLayout ни позволява да добавяме View елементи в него. LinearLayout е атрибут за ориентация, който диктува как ще бъде разположен на екрана. Освен това има LinearLayout.LayoutParams, които диктуват правилата за изгледите вътре, например атрибутът android:center_horizontal ще центрира изгледите по хоризонталната ос, докато `android:center_vertical ще центрира съдържанието на изгледа по вертикалната ос.

Ето няколко изображения, за да имате представа за центрирането. Ще приемем, че това е обикновен TextView в пространство 200px на 200px, атрибутите за центриране ще го накарат да се държи по следния начин.

android:center_horizontal

Хоризонтално центрирано съдържание
Хоризонтално центрирано съдържание

android:center_vertical

вертикално центрирано съдържание
вертикално центрирано съдържание

android: център

Центрирано съдържание
Центрирано съдържание

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

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

Следват важните компоненти на RecyclerView:

RecyclerView.Адаптер

Основа #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 би имал задачата да опише как изгледите са разположени вътре.

За целите на сравнението, когато работим с Linearlayout ViewGroup, случаят на използване, който искаме, е възможността да поставяме елементите или вертикално, или хоризонтално. Това се изпълнява лесно чрез добавяне на атрибут за ориентация, който ни казва как линейното оформление ще бъде поставено на екрана. Можем да направим това, като използваме android:orientation=VERTICAL|HORIZONTAL атрибут.

Имаме и друга ViewGroup, наречена GridLayout, случаят на използване е, когато искаме да поставим Views в правоъгълна Grid структура. Това може да се дължи на причини като да направим данните, които представяме на потребителя на приложението, лесни за използване. По дизайн GridLayout позволява конфигурации, които да ви помогнат да постигнете тази цел, като разполагате с конфигурации, в които можем да дефинираме размерите на мрежата, например можем да имаме мрежа 4 × 4, мрежа 3 x 2.

RecyclerView.ViewHolder

ViewHolder е абстрактен клас, който също разширяваме от RecyclerView. ViewHolder ни предоставя общи методи, които да ни помогнат да реферираме View, който сме поставили в RecyclerView, дори след като машината за рециклиране в RecyclerView е променила различни препратки, за които не знаем.

Големи списъци

RecyclerView се използват, когато искаме да представим наистина голям набор от изгледи на потребителя, като през цялото време не изчерпваме нашите RAM на нашето устройство за всяко създадено копие на View.

Ако вземем случая със списък с контакти, ще имаме обща представа как би изглеждал един контакт в списъка. Това, което бихме направили след това, е да създадем оформление на шаблон – което всъщност е изглед – със слотове, където различни данни от нашия списък с контакти ще се запълнят. Следва псевдокод, който обяснява цялата цел:

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

Тогава ще имаме списък с контакти от този вид

 <ContactList>
</ContactList>

Ако случаят е такъв, ние твърдо кодирахме съдържанието, нямаше да имаме програмен начин за добавяне на ново съдържание към списъка, без да пренаписваме приложението. За наше щастие. Добавянето на изглед към ViewGroup се поддържа от an addView(view:View) метод.

Дори и това да е така, това не е начинът, по който RecyclerView добавя детски изгледи към него.

В нашия случай на използване ще имаме дълъг списък от контакти. За всеки контакт в списъка ще трябва да създадем OneContactView и да попълним данните в изгледа, за да съответстват на полетата в нашия клас 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 трябва да добавим изгледи в него, за да може да ни помогне със способността си за рециклиране.

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 има адаптер, който ни позволява да свържем нашия масив с контакти към ContactsView с RecyclerView

Добавяне на изглед

ViewGroup не преначертава ViewGroup автоматично, а следва определен график. Възможно е вашето устройство да се преначертава на всеки 10ms или 100ms, или ако изберем абсурдно число, да речем 1 минута, когато добавим View към ViewGroup, ще видите промените 1 минута по-късно, когато ViewGroup се „освежи“.

RecyclerView.Recycler

Основа #3. Кеширане

Един от най-добрите примери за това къде редовно правим опреснявания е в браузъра. Нека си представим например, че сайтът, който посещаваме, е статичен и не изпраща съдържание динамично, ще трябва да продължим да опресняваме, за да видим промените.

За този пример нека си представим, че въпросният сайт е twitter. Щяхме да имаме изброени поредица от статични туитове и единственият начин да видим нови туитове е чрез щракване върху бутона за опресняване, за да изтеглим отново съдържанието.

Пребоядисването на целия екран очевидно е скъпо нещо. Ако трябваше да си представим, че случаят беше такъв, имахме ограничена честотна лента с нашия телефонен доставчик. И нашият списък с туитове имаше много изображения и видеоклипове, би било скъпо да изтегляме отново цялото съдържание на страницата при всяко опресняване.

Ще ни трябва начин да съхраняваме вече заредените туитове и да гарантираме, че следващата ни заявка има възможност да каже туитовете, които вече има. Следователно той не изтегля отново всичко и получава само новите туитове, които има, и също така проверява дали някой туит, който е бил записан локално, вече не е там, за да може да го изтрие локално. Това, което описваме, се нарича кеширане.

Информацията, която изпращаме на уебсайта относно съдържанието, което имаме, се нарича метаданни. Така че в реалния смисъл ние не просто казваме „искаме да заредим вашия сайт“, ние казваме „ние искаме да заредим вашия сайт и ето част от съдържанието, което вече бяхме запазили от последното зареждане, моля, използвайте го, за изпращайте ни само това, което не е там, така че да не използваме много честотна лента, тъй като нямаме много ресурси.“

Обаждания за оформление – Списъкът с туитове трябва да е луд

Пример за извикване на оформление е scrollToPosition

Това е често срещан пример, който присъства в неща като приложения за чат. Ако някой в ​​нишка за чат отговори на балонче за чат от по-рано, някои приложения за чат включват отговора и връзка към балончето за чат, което при щракване ви навигира до мястото, където е било първоначалното ви съобщение.

В случая извикваме този метод, преди да имаме добавен LayoutManager към нашия RecyclerView и преди да имаме RecyclerView.Adapter, scrollToPosition(n:Int) просто се игнорира.

Комуникация между компонентите на RecyclerView

Основа #4. Обратни повиквания

RecyclerView, докато върши работата си, има много движещи се части. Трябва да се справи с LayoutManager, който ни казва как да организираме изгледите, линейно или в мрежа. Трябва да се справи с адаптер, който върши работата по преобразуването на нашите елементи contactList в Views OneContactView и след това във ViewHolders OneContactViewHolder, който RecyclerView е готов да работи в рамките на методите, които ни предоставя.

Суровият материал за RecyclerView са нашите изгледи, напр. OneContactView и източник на данни.

contactList:Array<Contact>

Използвахме прост сценарий като отправна точка, за да усетим какво се опитва да постигне RecyclerView.

Основен случай, когато имаме статичен масив от 1000 контакта, който искаме да покажем на потребителя, е лесен за разбиране.

Машината RecyclerView наистина започва да отнема живот, когато списъкът вече не е статичен.

С динамичен списък трябва да мислим какво се случва с изгледа на екрана, когато добавим елемент към списъка или премахнем елемент от списъка.

RecyclerView.LayoutManager

Освен да решим как нашите изгледи да бъдат разположени линейно или в мрежа. LayoutManager върши много работа под капака, като помага на Recycler да знае кога да извърши рециклирането.

Той е отговорен за проследяването на изгледите, видими в момента на екрана, и за предаването на тази информация на механизма за рециклиране. Докато потребителят превърта надолу, мениджърът на оформлението е отговорен за информирането на системата за рециклиране за изгледите, които излизат извън фокуса в горната част, така че да могат да бъдат използвани повторно, вместо да останат там и да консумират памет, или вместо да ги унищожат и да трябва да създават нови.

Това означава, че LayoutManager трябва да следи къде се намира потребителят, докато превърта нашия списък. Той прави това, като има списък с позиции, които са база на индекса, т.е. първият елемент трябва да започне от 0 и да се увеличава, за да съответства на броя на елементите в нашия списък.

Ако можем да видим 10 елемента в нашия списък от да речем 100, в началото, LayoutManager е наясно, че има на фокус view-0 чак до View-9 Докато превъртаме, LayoutManager може да изчисли изгледите, които излизат на фокус.

LayoutManager може да освободи тези изгледи към механизма за рециклиране, така че да могат да бъдат използвани повторно (нови данни могат да бъдат обвързани с тях, напр. данните за контакт на изглед могат да бъдат премахнати и нови данни за контакт от следващия сегмент могат да заменят контейнерите).

Това би бил щастлив случай, ако списъкът, който имаме, е статичен, но един от най-честите случаи на използване на RecyclerView е с динамични списъци, където данните могат да идват от онлайн крайна точка или дори може би от сензор. Не само се добавят данни, но данните от нашия списък понякога се премахват или актуализират.

Динамичното състояние на нашите данни може да направи много трудно разсъждението относно LayoutManager. Поради тази причина LayoutManager поддържа собствен списък за елементите и позициите, който е отделен от списъка, използван от компонента за рециклиране. Това гарантира, че изпълнява правилно своята работа по оформлението.

В същото време LayoutManager на RecyclerView не иска да представи погрешно данните, които има. За да работи правилно, LayoutManager се синхронизира с RecyclerView.Adapter на определени интервали (60ms), споделяйки информация за нашите елементи от списъка. т.е. елементи, добавени, актуализирани, премахнати, преместени от една позиция в друга). LayoutManager, при получаване на тази информация, реорганизира съдържанието на екрана, за да съответства на промените, когато е необходимо.

Много от основните операции, които се занимават с RecylerView, се въртят около комуникацията между RecyclerView.LayoutManager и RecyclerView.Adapter, който съхранява нашите списъци с понякога статични или понякога динамични данни.

Нещо повече, RecyclerView ни представя методи, които можем да използваме, за да слушаме събития като onBindViewHolder, когато нашият RecyclerView.Adapter обвързва съдържание от нашия списък, например контакт към ViewHolder, така че сега да свикне да показва информацията на екрана.

Друг е onCreateViewHolder, който ни казва кога RecyclerView. Адаптерът взема обикновен изглед като OneContactView и го преобразува в елемент ViewHolder, с който RecyclerView може да работи. От нашия ViewHolder за използване от RecyclerView. onViewDetached

Освен основните механизми, които позволяват рециклирането. RecyclerView предоставя начини за персонализиране на поведението, без да се засяга рециклирането.

Повторното използване на изгледи затруднява извършването на обичайни неща, които сме свикнали да правим със статични изгледи, като реагиране на събития onClick.

Тъй като сме наясно, че RecyclerView.LayoutManager който представя изгледите на потребителя може за момент да има различен списък от елементи от 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 по-горе добавихме поле за идентификатор, което ще се използва за идентифициране на изглед.

Не се генерира по подразбиране. Имаме нашия 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. Използвайки идентификаторите, които създадохме, ще можем да препращаме към изгледите, които току-що създадохме.

Ще започнем с препратка към 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, който отговаря за вземането на нашия изглед и преобразуването му във 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 и динамично ще обвърже съдържанието към изгледа, който създадохме 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 Студиото ще добави червени завъртания към нашия 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) {
          
        }
    }

След това ще внедрим метода за замяна на забавлението onCreateViewHolder.

Това е мястото, където RecyclerView ни моли да му помогнем да конструира FruitHolder за него.

Ако си спомняме, така изглеждаше нашият клас FruitViewHolder:

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

Изисква нашия fruitView, който създадохме като xml файл one_fruit_view.xml

Можем да създадем препратка към този xml и да го конвертираме в изглед, както следва.

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, към изгледа, който се държи вътре в държача на изгледа.

Изгледът, който се съхранява във 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)
        }
    }
}

Изтеглете проекта