Android RecyclerView: Ce este, învață cu exemple simple

În ce este RecyclerView Android?

RecyclerView este un widget care este o versiune mai flexibilă și mai avansată a GridView și ListView. Este un container pentru afișarea unor seturi mari de date care pot fi derulate eficient prin menținerea unui număr limitat de vizualizări. Puteți utiliza widgetul RecyclerView atunci când aveți colecții de date ale căror elemente se modifică în timpul rulării depind de evenimentul din rețea sau de acțiunea utilizatorului.

Vizualizări

Android platforma folosește clasele View și ViewGroup pentru a desena elemente pe ecran. Aceste clase sunt abstracte și sunt extinse la diferite implementări pentru a completa un caz de utilizare. TextView, de exemplu, are un scop simplu de a afișa conținut text pe ecran. EditText se extinde din aceeași clasă View și adaugă mai multe funcționalități pentru a permite utilizatorului să introducă date.

Este posibil să ne creăm propriile vizualizări personalizate pentru a putea obține mai multă flexibilitate atunci când dezvoltăm interfețe cu utilizatorul. Clasa View oferă metode pe care le putem suprascrie pentru a desena pe ecran și un mijloc de a trece parametri precum lățimea, înălțimea și propriile atribute personalizate pe care am dori să le adăugăm la vizualizarea noastră pentru a o face să se comporte așa cum ne-am dori.

ViewGroups

Clasa ViewGroup este un fel de View, dar, spre deosebire de clasa View simplă a cărei responsabilitate este pur și simplu afișare, ViewGroup ne oferă posibilitatea de a pune mai multe vederi într-o singură vizualizare, la care putem face referire ca un întreg. În acest caz, vizualizarea care este creată la nivelul superior la care adăugăm alte vizualizări simple (putem adăuga, de asemenea, viewGroups) se numește „părinte”, iar vizualizările care sunt adăugate în interior sunt „copii”.

Putem imagina o vizualizare ca o matrice și un ViewGroup ca o matrice de matrice. Având în vedere că un Array of Arrays este un Array în sine, putem vedea cum un ViewGroup poate fi tratat ca o 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 ne permite, de asemenea, să definim modul în care copiii sunt organizați în interiorul vederii, de exemplu, sunt așezați pe verticală sau orizontală. Putem avea reguli diferite de interacțiune în interiorul View. De exemplu, TextViews care se urmăresc unul pe celălalt ar trebui să aibă o distanță de 12 dp, în timp ce ImageViews urmat de TextView ar trebui să aibă o distanță de 5 dp.

Acesta ar fi cazul dacă ne-am dezvolta propriul ViewGroup de la zero. Pentru a ușura aceste configurații, Android oferă o clasă numită LayoutParams, pe care o putem folosi pentru a introduce aceste configurații.

Android documentaţie furnizează câțiva parametri impliciti pe care i-am implementa atunci când ne configuram propriul ViewGroup. Unii parametri comuni sunt cei care se referă la lățimea, înălțimea și marginea. În mod implicit, aceste configurații au structura android:layout_height pentru înălțime, de exemplu, android:layout_width În acest sens, atunci când vă creați ViewGroup, puteți crea în continuare LayoutParams specifici modului în care doriți să se comporte ViewGroup.

Android vine cu vizualizări implicite și ViewGroups pe care le putem folosi pentru a face o mulțime de sarcini comune de care avem nevoie. Un exemplu pe care l-am menționat este un TextView. Aceasta este o vizualizare simplă care vine cu aspecte configurabile, cum ar fi înălțimea, lățimea, textSize și altele asemenea. Avem un ImageView pentru afișarea imaginilor și EditText așa cum am menționat, printre multe altele. Android are, de asemenea, ViewGroups personalizate la care putem adăuga vizualizările noastre și obține comportamentul așteptat.

LinearLayout

LinearLayout ne permite să adăugăm elemente View în el. LinearLayout este un atribut de orientare care dictează modul în care va fi aranjat pe ecran. De asemenea, are LinearLayout.LayoutParams care dictează regulile pentru vederile din interior, de exemplu, atributul android:center_horizontal ar centra vizualizările de-a lungul axei orizontale, în timp ce, `android:center_vertical ar centra conținutul vizualizării de-a lungul axei verticale.

Iată câteva imagini pentru a înțelege centrarea. Am considera că acesta este un simplu TextView în interiorul spațiului de 200px pe 200px, atributele de centrare l-ar face să se comporte după cum urmează.

android: center_horizontal

Conținut centrat pe orizontală
Conținut centrat pe orizontală

android:center_vertical

Conținut centrat vertical
conținut centrat vertical

android:centru

Conținut centrat
Conținut centrat

Componentele de bază ale RecyclerView

Componentele de bază ale RecyclerView
Componentele de bază ale RecyclerView

Următoarele sunt componentele importante ale RecyclerView:

RecyclerView.Adapter

Groundwork #1 – Model de adaptor

Un adaptor este un dispozitiv care transformă atributele sistemului sau dispozitivului în cele ale unui dispozitiv sau sistem altfel incompatibil. Unele dintre ele modifică atributele de semnal sau de putere, în timp ce altele adaptează pur și simplu forma fizică a unui conector la altul.

Un exemplu simplu pe care îl găsim în viața reală pentru a explica un adaptor este atunci când trebuie să conectăm dispozitive împreună, dar au porturi de conectare care nu se potrivesc între ele. Acesta ar putea fi cazul când vizitați o altă țară în care folosesc diferite tipuri de prize. Dacă purtați telefonul sau încărcătorul pentru laptop, ar fi imposibil să îl conectați la prize. Cu toate acestea, nu veți renunța, ci pur și simplu veți obține un adaptor care ar intra între priza de alimentare și încărcătorul și va permite încărcarea.

Acesta este cazul în programare când dorim să conectăm două structuri de date împreună pentru a îndeplini sarcina, dar porturile lor implicite nu au o modalitate de a comunica între ele.

Vom folosi exemplul simplu de dispozitiv și încărcător. Vom avea două cazuri de încărcătoare. Una americană și una britanică

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

Apoi vom crea două dispozitive

class AmericanDevice() 
class BritishDevice()

Ca exemplu, putem crea apoi câteva instanțe ale dispozitivelor pentru a juca împreună.

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

Vom introduce apoi conceptul de încărcare pentru ambele dispozitive prin adăugarea unei metode în dispozitive numită charge() .

Metoda preia ca intrare încărcătorul respectiv și se încarcă pe baza acestuia.

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
    }
}

În acest caz, pe baza analogiei noastre, am avea nevoie dintr-un motiv sau altul să folosim un BritishCharger atunci când folosim un dispozitiv american sau invers.

În lumea programării, acest lucru se întâmplă de obicei atunci când amestecăm biblioteci care oferă aceeași funcționalitate (în contextul nostru, funcționalitatea noastră comună se încarcă). Ar trebui să găsim o modalitate de a activa acest lucru.

Dacă urmăm analogia, va trebui să mergem la un magazin de electronice și să cumpărăm un adaptor care să ne permită să încărcăm dispozitivele American Devices din BritishChargers. Din perspectiva programării, noi vom fi cei care vom fi producătorul adaptorului.

Vom face un adaptor pentru unul care se potrivește exact cu modelul de care am avea nevoie pentru a crea celălalt. Îl vom implementa ca o clasă, după cum urmează. Nu trebuie neapărat să fie o clasă și ar putea fi o funcție care evidențiază ceea ce face în general modelul adaptorului. Vom folosi o clasă, deoarece se potrivește cu cea mai mare parte a utilizării 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
    }
}

În lumea programării, diferența dintre prize este analogă cu diferența dintre metodele din interior utilizate pentru încărcare. Încărcătoarele care au metode diferite ar face imposibilă utilizarea încărcătoarelor.

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

Încercarea de a apela metoda charge() în myBritishDevice cu americanChargerIFound nu ar funcționa, deoarece AmericanDevice acceptă doar un AmericanCharger

Deci este imposibil să faci asta

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

În acest scenariu adaptorul pe care l-am creat

AmericanToBritishChargerAdapter poate fi acum util. Putem folosi metoda returnNewCharger() pentru a crea un nou BritishCharger, pe care îl putem folosi pentru a încărca. Tot ce avem nevoie este să creăm o instanță a adaptorului nostru și să o alimentam cu AmericanCharger-ul pe care îl avem, iar acesta va crea un BritishCharger pe care îl putem folosi

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

Când avem de-a face cu un ViewGroup, am avea Views plasate în interiorul acestuia. LayoutManager ar avea sarcina de a descrie modul în care vizualizările sunt așezate în interior.

În scopuri de comparație, atunci când lucrăm cu Linearlayout ViewGroup, cazul de utilizare pe care îl dorim este capacitatea de a plasa elementele fie pe verticală, fie pe orizontală. Acest lucru este ușor de implementat prin adăugarea unui atribut de orientare, care ne spune cum va fi plasat aspectul liniar pe ecran. Putem face acest lucru folosind android:orientation=VERTICAL|HORIZONTAL atribut.

Avem, de asemenea, un alt ViewGroup numit GridLayout, cazul în care am dori să plasăm vizualizările într-o structură Grid dreptunghiulară. Acest lucru ar putea fi din motive precum faptul că datele pe care le prezentăm utilizatorului aplicației sunt ușor de consumat. Prin design, GridLayout permite configurații pentru a vă ajuta să atingeți acest obiectiv, având configurații în care putem defini dimensiunile grilei, de exemplu, putem avea o grilă 4×4, grilă 3 x 2.

RecyclerView.ViewHolder

ViewHolder este o clasă abstractă pe care o extindem și de la RecyclerView. ViewHolder ne oferă metode comune pentru a ne ajuta să facem referință la o vizualizare pe care am plasat-o în RecyclerView chiar și după ce mașina de reciclare din RecyclerView a schimbat diverse referințe despre care nu știm.

Liste mari

RecyclerViews sunt folosite atunci când dorim să prezentăm utilizatorului un set foarte mare de vizualizări, fără a ne epuiza totodată. RAM pe dispozitivul nostru pentru fiecare instanță a View creată.

Dacă ar fi să luăm cazul unei liste de contacte, am avea o idee generală despre cum ar arăta un contact în listă. Ceea ce am face atunci este să creăm un aspect șablon – care este de fapt o vizualizare – cu sloturi în care se vor umple diverse date din lista noastră de contacte. Mai jos este un pseudocod care explică întregul scop:

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

Atunci am avea o listă de contact de această natură

 <ContactList>
</ContactList>

Dacă este cazul, am codifica conținutul, nu am avea o modalitate programatică de a adăuga conținut nou la listă fără a rescrie aplicația. Din fericire pentru noi. Adăugarea unei vizualizări la un ViewGroup este acceptată de un addView(view:View) metodă.

Chiar dacă acesta este cazul, nu este modul în care RecyclerView adaugă vizualizările copiilor.

În cazul nostru de utilizare, am avea o listă lungă de contacte. Pentru fiecare contact din listă, ar trebui să creăm OneContactView și să populam datele din interiorul View pentru a se potrivi cu câmpurile din clasa noastră Contact. Apoi, odată ce avem vizualizarea, ar trebui să o adăugăm la RecyclerView pentru a afișa lista.

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)

Avem o serie de contacte numite OneContactView. Conține sloturi pentru a prelua conținut din clasa Contact și a le afișa. În RecyclerView trebuie să adăugăm vizualizări în el, astfel încât să ne poată ajuta cu capacitatea sa de reciclare.

RecyclerView nu ne permite cu adevărat să adăugăm vizualizare, dar ne permite să adăugăm un ViewHolder. Deci, în acest scenariu, avem două lucruri pe care vrem să le conectăm, dar care nu se potrivesc. Aici intervine adaptorul nostru. RecyclerView ne oferă un adaptor asemănător nostru AmericanToBritishChargerAdapter() de mai devreme, asta ne-a permis să transformăm AmericanCharger-ul nostru, care era inutilizabil cu BritishDevice-ul nostru, în ceva utilizabil, asemănător cu adaptorul de alimentare din viața reală.

În acest scenariu, adaptorul ar lua matricea noastră de contacte și vizualizarea noastră și de acolo va genera ViewHolders pe care RecyclerView este dispus să le accepte.

RecyclerView oferă o interfață pe care o putem extinde pentru a crea adaptorul nostru prin clasa RecyclerView.Adapter. În interiorul acestui adaptor este o modalitate de a crea clasa ViewHolder cu care RecyclerView dorește să lucreze. Deci, ceea ce avem este aceeași situație ca înainte, dar cu un lucru în plus, adică adaptorul.

Avem o serie de Contacte, o vizualizare pentru a afișa un contact OneContactView. Un RecyclerView este o listă de vizualizări care oferă servicii de reciclare, dar care sunt dispuse doar să accepte ViewHolders

Dar, în acest scenariu, avem acum clasa RecyclerView.Adapter, care are o metodă de a crea ViewHolders în interior.

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

RecyclerView.ViewHolder este o clasă abstractă care ia View-ul nostru ca argument și o convertește într-un ViewHolder.

Utilizează modelul de înveliș care este folosit pentru a extinde abilitățile claselor.

Groundwork #2 – Model de înveliș

Vom folosi un exemplu simplu pentru a demonstra cum putem face animalele să vorbească.

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

În exemplul de mai sus, avem două animale. Dacă, întâmplător, am vrut să adăugăm o metodă de a face vorbirea, dar autorul bibliotecii nu a fost distractiv, am putea găsi o cale. Ceea ce ne trebuie ar fi un ambalaj pentru clasa noastră de animale. Am face acest lucru luând Animalul ca constructor pentru clasa noastră

 class SpeechPoweredAnimalByWrapper(var myAnimal:Animal){

    fun sound(){
        myAnimal.sound()
    }

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

Acum putem trece într-o instanță de animal la SpeechPoweredAnimalByWrapper. Apelarea metodei sound() pe aceasta ar apela metoda transmisă în animal sound(). Avem, de asemenea, o metodă suplimentară speak(), care contează ca o nouă funcționalitate pe care o adăugăm animalelor transmise. O putem folosi după cum urmează:

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"

Folosind acest model, putem lua cursuri și putem adăuga funcționalități. Tot ce ne trebuie este să trecem este o instanță de clasă și noi metode definite de clasa noastră de împachetare.

În cazul nostru de mai sus, am folosit o clasă concretă. Este, de asemenea, posibil să implementați același lucru într-o clasă Abstract. Ar trebui să facem este să adăugăm să schimbăm clasa SpeechPoweredAnimalByWrapper în abstract și am terminat. Vom schimba numele clasei cu ceva mai scurt pentru a o face mai lizibilă.

abstract class SpeechPowered(var myAnimal:Animal){

    fun sound(){
        myAnimal.sound()
    }

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

Este la fel ca înainte, dar ar însemna altceva. Într-o clasă normală, putem avea o instanță a unei clase în același mod în care am creat cat1 și dog1. Clasele abstracte, cu toate acestea, nu sunt menite să fie instanțiate, ci sunt menite să extindă alte clase. Deci, cum am folosi noua clasă abstractă SpeechPowered(var myAnimal:Animal). Îl putem folosi creând noi clase care îl vor extinde și, la rândul lor, îi vor câștiga funcționalitatea.

În exemplul nostru, vom crea o clasă SpeechPoweredAnimal care extinde clasa

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

Acesta este același model folosit în ViewHolder. Clasa RecyclerView.ViewHolder este o clasă abstractă care adaugă funcționalitate View, așa cum am adăugat metoda speak la animale. Funcționalitatea adăugată este ceea ce o face să funcționeze atunci când se ocupă cu RecyclerView.

Acesta este modul în care am crea un OneContactViewHolder din 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 are un adaptor care ne permite să ne conectăm matricea Contacts la ContactsView cu RecyclerView

Adăugarea unei vizualizări

ViewGroup nu redesenează ViewGroup automat, ci urmează un anumit program. Este posibil ca pe dispozitivul dvs. să se redeseneze la fiecare 10ms sau 100ms, sau dacă alegem un număr absurd, să spunem 1 minut când adăugăm o vizualizare la un ViewGroup, veți vedea modificările 1 minut mai târziu când ViewGroup se „împrospătează”.

RecyclerView.Recycler

Lucrări de temelie #3. Memorarea în cache

Unul dintre cele mai bune exemple în care facem în mod regulat reîmprospătări este în Browser. Să ne imaginăm, de exemplu, site-ul pe care îl vizităm este static și nu trimite conținut dinamic, ar trebui să continuăm să reîmprospătăm pentru a vedea modificările.

Pentru acest exemplu, să ne imaginăm că site-ul în cauză este twitter. Am avea listate o serie de tweet-uri statice și singura modalitate prin care am putea vedea noi tweet-uri ar fi făcând clic pe butonul de reîmprospătare pentru a recăpăta conținutul.

Revopsirea întregului ecran este, evident, un lucru costisitor. Dacă ar fi să ne imaginăm dacă ar fi cazul, am avut o lățime de bandă limitată cu furnizorul nostru de telefonie. Și lista noastră de tweet-uri avea o mulțime de imagini și videoclipuri, ar fi costisitor să redescărcați tot conținutul paginii la fiecare reîmprospătare.

Am avea nevoie de o modalitate de a stoca Tweeturile deja încărcate și de a ne asigura că următoarea noastră cerere are capacitatea de a spune Tweeturile pe care le are deja. Prin urmare, nu redescarcă totul și primește doar noile Tweeturi pe care le are și, de asemenea, verifică dacă un Tweet care a fost salvat local nu mai este acolo, astfel încât să îl poată șterge local. Ceea ce descriem se numește Caching.

Informațiile pe care le trimitem către site-ul web despre conținutul pe care le avem se numesc metadate. Deci, în sens real, nu spunem doar „dorim să vă încărcăm site-ul”, spunem „dorim să vă încărcăm site-ul și iată câteva dintre conținuturile pe care le-am salvat deja de la ultima încărcare, vă rugăm să-l folosiți pentru a Trimite-ne doar ceea ce nu este acolo, astfel încât să nu folosim multă lățime de bandă, deoarece nu avem multe resurse.”

Apeluri de aspect – Lista de tweeturi trebuie să fie nebună

Un exemplu de apeluri de aspect este scrollToPosition

Acesta este un exemplu comun care este prezent în lucruri precum aplicațiile de chat. Dacă cineva dintr-un fir de chat răspunde la un balon de chat de mai devreme, unele aplicații de chat includ răspunsul și un link către balonul de chat, care, la clic, vă navighează la locul în care a fost mesajul original.

În acest caz, numim această metodă înainte de a adăuga un LayoutManager la RecyclerView și înainte de a avea un RecyclerView.Adapter, scrollToPosition(n:Int) este pur și simplu ignorat.

Comunicarea între componentele RecyclerView

Lucrări de temelie #4. Reapeluri

RecyclerView, în îndeplinirea sarcinii sale, are o mulțime de părți mobile. Trebuie să se ocupe de LayoutManager care ne spune cum să organizăm vizualizările, fie liniar, fie într-o grilă. Trebuie să se ocupe de un adaptor care face treaba de a converti articolele noastre contactList în Views OneContactView și apoi în ViewHolders OneContactViewHolder pe care RecyclerView este dispus să lucreze în cadrul metodelor pe care ni le oferă.

Materia primă pentru RecyclerView sunt vizualizările noastre, de exemplu, OneContactView și sursa de date.

contactList:Array<Contact>

Am folosit un scenariu simplu ca punct de plecare pentru a avea o idee despre ceea ce încearcă să realizeze RecyclerView.

Un caz de bază al când am avea o matrice statică de 1000 de contacte pe care dorim să-i arătăm utilizatorului este ușor de înțeles.

Mașinile RecyclerView chiar începe să ia viață atunci când lista nu mai este statică.

Cu o listă dinamică, trebuie să ne gândim la ce se întâmplă cu View-ul de pe ecran atunci când adăugăm un articol în listă sau eliminăm un element din listă.

RecyclerView.LayoutManager

În afară de a decide modul în care vederile noastre trebuie să fie dispuse fie liniar, fie într-o grilă. LayoutManager lucrează mult sub capotă pentru a-l ajuta pe reciclator să știe când să facă reciclarea.

Este responsabil pentru urmărirea vizualizărilor vizibile în prezent pe ecran și comunicarea acestor informații către mecanismul de reciclare. Pe măsură ce utilizatorul derulează în jos, managerul de aspect este responsabil să informeze sistemul de reciclare cu privire la vizualizările care nu se focalizează în partea de sus, astfel încât acestea să poată fi reutilizate în loc să rămână acolo și să consume memorie sau în loc să le distrugă și să fie nevoite să creeze noi.

Acest lucru înseamnă că LayoutManager trebuie să țină evidența unde se află utilizatorul în timp ce derulează lista noastră. Face acest lucru având o listă de poziții care sunt baza de index, adică primul articol trebuie să înceapă de la 0 și să crească pentru a se potrivi cu numărul de articole din lista noastră.

Dacă putem vizualiza 10 articole din lista noastră, să zicem 100, la început, LayoutManager este conștient că are în focus view-0 până la View-9 Pe măsură ce derulăm, LayoutManager este capabil să calculeze vizualizările care ies. de focalizare.

LayoutManager este capabil să elibereze aceste vizualizări în mecanismul de reciclare, astfel încât să poată fi reutilizate (pot fi legate date noi, de exemplu, datele de contact ale unei vizualizări pot fi eliminate și noile date de contact din următorul segment pot înlocui substituenții).

Acesta ar fi un caz fericit dacă Lista pe care o avem este statică, dar unul dintre cele mai frecvente cazuri de utilizare a unui RecyclerView este cu liste dinamice în care datele pot veni de la un punct final online sau chiar de la un Senzor. Nu numai că sunt adăugate date, dar și datele din Lista noastră sunt uneori eliminate sau actualizate.

Starea dinamică a datelor noastre poate face foarte dificil să raționezi despre LayoutManager. Din acest motiv, LayoutManager menține o listă proprie despre articole și poziții, care este separată de Lista pe care o folosește componenta de reciclare. Acest lucru asigură că își face treaba de aspect corect.

În același timp, LayoutManager de la RecyclerView nu dorește să denaturaze datele pe care le are. Pentru a funcționa corect, LayoutManager se sincronizează cu RecyclerView.Adapter la intervale date (60 ms) partajând informații despre articolele din Listă. adică Articole adăugate, actualizate, eliminate, mutate dintr-o poziție în alta). LayoutManager, la primirea acestor informații, reorganizează conținutul de pe ecran pentru a se potrivi cu modificările atunci când este necesar.

Multe dintre operațiunile de bază care se ocupă de RecylerView se rotesc în jurul comunicării dintre RecyclerView.LayoutManager și RecyclerView.Adapter care stochează listele noastre de date uneori statice sau uneori dinamice.

Mai mult decât atât, RecyclerView ne prezintă metode pe care le putem folosi pentru a asculta evenimente precum onBindViewHolder atunci când RecyclerView.Adapter leagă conținutul din Lista noastră, de exemplu, un Contact la un ViewHolder, astfel încât să se obișnuiască acum să afișeze informațiile pe ecran.

Un altul este onCreateViewHolder, care ne spune când RecyclerView. Adaptorul ia o vizualizare obișnuită precum OneContactView și o convertește într-un element ViewHolder cu care poate lucra RecyclerView. Din ViewHolder-ul nostru pentru utilizare de către RecyclerView. onViewDetached

În afară de mecanismele de bază care permit reciclarea. RecyclerView oferă modalități de personalizare a comportamentului fără a afecta reciclarea.

Reutilizarea vizualizărilor face dificilă realizarea unor lucruri comune pe care suntem obișnuiți să le facem cu vizualizările statice, cum ar fi reacția la evenimentele onClick.

După cum știm că RecyclerView.LayoutManager care prezintă Vizualizările utilizatorului ar putea avea, pentru un moment, o listă diferită de articole de la RecyclerView.Adapter care are lista pe care am stocat-o într-o bază de date sau streaming dintr-o sursă. Punerea evenimentelor OnClick direct în Views poate duce la un comportament neașteptat, cum ar fi ștergerea contactului greșit sau schimbarea.

Gradle

Dacă vrem să folosim RecyclerView, trebuie să-l adăugăm ca dependență în fișierul nostru de compilare .gradle.

În exemplul următor am folosit implementarea „androidx.recyclerview:recyclerview:1.1.0”, care este cea mai recentă versiune conform acestui articol.

După adăugarea dependenței la nostru Gradle fișier, ni se va solicita Android Studio la Synccronizează schimbările,

Acesta este modul nostru Gradle fișierul va arăta ca după adăugarea unui RecyclerView într-un proiect gol cu ​​doar valorile implicite.

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"
}

Avem un singur fișier de aspect momentan. Vom începe cu un exemplu simplu în care vom folosi un RecyclerView pentru a afișa o listă de nume de fructe pe ecran.

Listă de obiecte

Vom naviga la fișierul nostru MainActivity și vom crea o matrice cu nume de fructe în interior chiar înainte de metoda onCreate() care a fost generată în timpul configurării.

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)
    }
}

Următorul nostru obiectiv va fi să prezentăm această listă pe ecran folosind un RecyclerView.

Pentru a face acest lucru, vom naviga la directorul de aspect care deține aspectele noastre și vom crea o vizualizare care va fi responsabilă pentru afișarea unui fruct.

Aspect care va fi folosit pentru fiecare articol din lista noastră

<?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>

În TextView de mai sus, am adăugat un câmp de id care va fi folosit pentru identificarea unei vizualizări.

Nu este generat implicit. Avem TextView id-ul fruitName pentru a se potrivi cu datele, care vor fi legate de acesta.

Adăugarea RecyclerView la aspectul principal

În aceeași activitate, există fișierul de aspect main_layout.xml care a fost generat pentru noi în mod implicit.

Dacă alegem un proiect gol. Va fi generat un XML care conține un ConstraintLayout și în interior va fi un TextView cu text „Hello”.

Vom șterge tot conținutul și vom avea ca aspectul să conțină doar RecyclerView, după cum urmează:

<?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" />

De asemenea, am adăugat un atribut id pentru RecyclerView pe care îl vom folosi pentru a-l face referire în codul nostru.

 android:id="@+id/fruitRecyclerView"

Apoi vom naviga înapoi la fișierul nostru MainActivity. Folosind id-urile pe care le-am creat, vom putea face referire la vizualizarile pe care tocmai le-am creat.

Vom începe prin a face referire la RecyclerView folosind metoda findViewById() oferită de Android. Vom face acest lucru în metoda noastră onCreate().

Metoda noastră onCreate() va arăta după cum urmează.

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)
        
        var myFruitRecyclerView: RecyclerView = findViewById(R.id.fruitRecyclerView)
    }

Creați un ViewHolder

În continuare, vom crea un RecyclerView.ViewHolder care este responsabil pentru preluarea vizualizării noastre și convertirea acestuia într-un ViewHolder, pe care RecyclerView îl folosește pentru a ne afișa articolele.

Vom face acest lucru imediat după metoda noastră distractivă 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)
    
}

Creați un RecyclerViewAdapter

În continuare, vom crea o clasă FruitArrayAdapter care extinde clasa RecyclerView.Adapter.

FruitArrayAdapter pe care îl creăm va fi responsabil pentru următoarele.

Va lua nume de fructe din matricea de fructe. Va crea un ViewHolder folosind vizualizarea noastră one_fruit_view.xml Apoi va lega fructul la un ViewHolder și va lega dinamic conținutul la vizualizarea pe care am creat-o 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 va adăuga squiggle roșii pe FruitArrayAdapter, spunându-ne că trebuie să implementăm o metodă pe care RecyclerView o poate folosi pentru a conecta matricea noastră la un ViewHolder pe care RecyclerView îl poate folosi.

   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) {
          
        }
    }

Vom începe cu cel mai ușor bit din codul generat este metoda getItemCount(). Știm cum să obținem numărul de elemente din matricea noastră apelând metoda 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) {
          
        }
    }

Apoi vom implementa metoda override fun onCreateViewHolder.

Aici RecyclerView ne cere să-l ajutăm să construiască un FruitHolder pentru el.

Dacă ne amintim, așa arăta clasa noastră FruitViewHolder:

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

Necesită fruitView-ul nostru pe care l-am creat ca fișier xml one_fruit_view.xml

Putem fi capabili să creăm o referință la acest xml și să îl convertim într-o vizualizare după cum urmează.

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) {

        }
    }

Bitul rămas este anularea

fun onBindViewHolder(holder: FruitViewHolder, position: Int)

RecyclerView.Adapter întreabă cu un număr întreg de poziție, pe care îl vom folosi pentru a prelua un element din lista noastră. De asemenea, ne oferă un suport, astfel încât să putem lega articolul pe care îl primim din fruitArray la View-ul care este ținut în interiorul suportului pentru vizualizare.

Vederea care este deținută în interiorul unui ViewHoder este accesibilă prin câmpul ViewHolder.itemView. Odată ce obținem vizualizarea, putem folosi id-ul fruitName pe care l-am creat mai devreme pentru a seta conținutul.

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

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

            var aFruitName = fruitArray.get(position)

            ourFruitTextView.setText(aFruitName)
        }

Prin urmare, FruitArrayAdapter este complet și arată după cum urmează.

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)
        }
    }

În cele din urmă, suntem gata să conectăm piesele rămase din RecyclerView. Care creează un LayoutManager, care va spune lui RecyclerView cum să afișeze conținutul listei. Dacă se afișează într-o manieră liniară folosind LinearLayoutManager sau într-o grilă folosind GridLayoutManager sau StaggeredGridLayoutManager.

Creați Manager de aspect

Ne vom întoarce în funcția onCreate și vom adăuga 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
        
    }

Conectați adaptorul nostru la articole și setați-l pe 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
    }

De asemenea, am creat o instanță fruitListAdapter și i-am alimentat matricea de nume de fructe.

Și, în principiu, am terminat cu toții.

Fișierul complet MainActivity.kt arată după cum urmează.

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)
        }
    }
}

Descărcați Proiectul