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

⚡ Rezumat inteligent

RecyclerView este o soluție avansată Android widget care afișează eficient seturi de date mari, derulabile, prin reciclarea unui număr limitat de vizualizări. Se bazează pe un adaptor, un manager de aspect și un deținător de vizualizări pentru a lega colecțiile de date care se modifică în timpul execuției.

  • ???? Ce este: RecyclerView este un succesor flexibil al ListView și GridView pentru afișarea colecțiilor mari de date.
  • 🧱 Vizualizări și grupuri de vizualizări: Android desenează interfața folosind clasele View și ViewGroup, cu LayoutParams controlând dimensiunea și poziția.
  • 🔄 Adaptor: Modelul adaptor convertește o matrice de date în ViewHolders pe care RecyclerView le poate accepta.
  • 📐 LayoutManager: Managerul de aspect aranjează elementele liniar sau într-o grilă și semnalează când vizualizările pot fi reciclate.
  • ♻️ Reciclare: Vizualizările din afara ecranului sunt reutilizate în loc să fie recreate, ceea ce economisește memorie și menține derularea fluidă.
  • 🛠️ Pași de construcție: Adauga Gradle dependență, creați un layout de element, un ViewHolder, un adaptor și un LayoutManager.

Android RecyclerView

În ce este RecyclerView Android?

RecyclerView este o versiune mai flexibilă și mai avansată a GridView și ListView. Este un container pentru afișarea seturilor 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 execuției, în funcție de un eveniment de rețea sau de o acțiune a utilizatorului.

Vizualizări

Android platforma folosește clasele View și ViewGroup pentru a desena elemente pe ecran. Aceste clase sunt abstrac„t” și sunt extinse la diferite implementări pentru a se potrivi unui caz de utilizare. „TextView”, de exemplu, are scopul 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ă creăm propriile noastre vizualizări personalizate pentru a obține mai multă flexibilitate la dezvoltare.ping interfețe utilizator. Clasa View oferă metode pe care le putem suprascrie pentru a desena pe ecran și un mijloc de a transmite parametri precum lățimea, înălțimea și propriile noastre atribute personalizate pe care am dori să le adăugăm la View-ul nostru pentru a-l face să se comporte așa cum dorim.

ViewGroups

Clasa ViewGroup este un tip de View, dar, spre deosebire de clasa View simplă, a cărei responsabilitate este simpla afișare, ViewGroup ne oferă posibilitatea de a pune mai multe view-uri într-o singură view, la care putem face referire ca un întreg. În acest caz, View-ul creat la nivelul superior la care adăugăm alte view-uri simple se numește „parent”, iar view-urile adăugate în interior sunt „children”.

Ne putem imagina o View ca un Array și un ViewGroup ca un Array de Array-uri. Având în vedere că un Array de Array-uri este în sine un Array, 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 analogy, 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 sunt organizați copiii în cadrul view-ului, de exemplu, dacă sunt așezați vertical sau orizontal. Putem avea reguli diferite pentru interacțiunea în cadrul view-ului. De exemplu, TextView-urile care se succed ar trebui să aibă o distanță de 12 dp, în timp ce un ImageView urmat de un TextView ar trebui să aibă o distanță de 5 dp.

Acesta ar fi cazul dacă am fi dezvoltatping propriul nostru ViewGroup de la zero. Pentru a simplifica aceste configurații, Android oferă o clasă numită LayoutParams, pe care o putem folosi pentru a introduce aceste configurații.

Android documentaţie oferă câțiva parametri impliciți pe care i-am implementa atunci când configurăm propriul ViewGroup. Unii parametri comuni se referă la lățime, înălțime și margine. În mod implicit, aceste configurații au structura android:layout_height pentru înălțime și android:layout_width pentru lățime. În acest sens, atunci când creați ViewGroup-ul, puteți crea în continuare parametri LayoutParam specifici modului în care doriți să se comporte ViewGroup-ul.

Android vine cu Views (vizualizări) și ViewGroups (grupuri de vizualizări) implicite pe care le putem folosi pentru a îndeplini multe dintre sarcinile comune de care avem nevoie. Un exemplu menționat este TextView. Aceasta este o vizualizare simplă care vine cu aspecte configurabile, cum ar fi înălțimea, lățimea și textSize. De asemenea, avem 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 de tip View (vizualizare). LinearLayout are un atribut de orientare care dictează modul în care va fi așezat pe ecran. De asemenea, are LinearLayout.LayoutParams care dictează regulile pentru vizualizările 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 care să ajute la înțelegerea centrării. Considerăm că este vorba de un simplu TextView într-un spațiu 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 pe verticală

Conținut centrat pe 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 unui sistem sau dispozitiv în cele ale unui dispozitiv sau sistem altfel incompatibil. Unele adaptoare modifică atributele semnalului sau ale alimentării, în timp ce altele pur și simplu adaptează forma fizică a unui conector la altul.

Un exemplu simplu din viața reală care explică un adaptor este atunci când trebuie să conectăm dispozitive între ele, dar acestea au porturi de conectare care nu se potrivesc între ele. Acesta ar putea fi cazul atunci când vizitați o altă țară care folosește diferite tipuri de prize. Dacă aveți încărcătorul telefonului sau al laptopului la dumneavoastră, ar fi imposibil să îl conectați la prize. Cu toate acestea, nu ar trebui să renunțați, ci pur și simplu să cumpărați un adaptor care se introduce între priză și încărcător și permite încărcarea.

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

Vom folosi exemplul simplu al unui dispozitiv și al unui încărcător. Vom avea două instanțe de încărcătoare, una americană și una britanică.

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

Vom crea apoi 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 dispozitiv numite charge(). Metoda preia încărcătorul respectiv și î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, dintr-un motiv sau altul am avea nevoie să folosim un încărcător britanic 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 dispozitive AmericanDevices cu ajutorul încărcătoarelor BritishChargers. Din perspectiva programării, vom fi producătorul adaptorului.

Vom crea un adaptor care să corespundă exact modelului de care am avea nevoie pentru a-l crea pe 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. Folosim o clasă deoarece se potrivește cu majoritatea utilizărilor pe Android.

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

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

        return newBritishCharger
    }
}

În lumea programării, diferența dintre prize este analogă cu diferența dintre metodele folosite 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() pe myBritishDevice cu americanChargerIFound nu ar funcționa, deoarece AmericanDevice acceptă doar un AmericanCharger. Prin urmare, este imposibil să faci acest lucru:

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

În acest scenariu, adaptorul pe care l-am creat, AmericanToBritishChargerAdapter, ne poate fi acum util. Putem folosi metoda returnNewCharger() pentru a crea un nou BritishCharger, pe care îl putem folosi pentru încărcare. Tot ce trebuie să facem este să creăm o instanță a adaptorului nostru și să o introducem în 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(americanChargerIFound)
//calling returnNewCharger from myAdapter would return a BritishCharger
var britishChargerFromAdapter = myAdapter.returnNewCharger()
//and once we have the britishCharger we can now use it
myBritishDevice.charge(britishChargerFromAdapter)

RecyclerView.LayoutManager

Când lucrăm cu un ViewGroup, avem View-uri plasate în interiorul acestuia. LayoutManager are sarcina de a descrie modul în care sunt aranjate View-urile în interior.

Spre comparație, atunci când lucrăm cu un LinearLayout ViewGroup, cazul de utilizare dorit este posibilitatea de a plasa elementele fie vertical, fie orizontal. Acest lucru este ușor de implementat prin adăugarea unui atribut de orientare, care ne spune cum va fi plasat LinearLayout pe ecran. Putem face acest lucru folosind android:orientation=VERTICAL|HORIZONTAL atribut.

De asemenea, avem un alt ViewGroup numit GridLayout. Cazul său de utilizare este atunci când dorim să plasăm View-uri într-o structură de grilă dreptunghiulară. Acest lucru ar putea fi din motive precum facilitarea accesului la datele pe care le prezentăm utilizatorului aplicației. Prin design, GridLayout permite configurații care să vă ajute să atingeți acest obiectiv prin definirea dimensiunilor grilei; de exemplu, putem avea o grilă de 4×4 sau o grilă de 3×2.

RecyclerView.ViewHolder

ViewHolder este un abdomentracclasa t pe care o extindem și din RecyclerView. ViewHolder ne oferă metode comune care ne ajută să facem referire la o View plasată în RecyclerView, chiar și după ce mecanismul de reciclare din RecyclerView a modificat diverse referințe despre care nu știm.

Liste mari

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

Dacă 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 apoi este să creăm un șablon, care este de fapt o Vizualizare, cu sloturi în care vor fi completate diverse date din lista noastră de contacte. Următorul este un pseudocod care explică întregul scop:

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

Am avea atunci o listă de contacte de acest gen:

<ContactList>
</ContactList>

Dacă am fi codificat conținutul în mod hardcoded, nu am avea o modalitate programatică de a adăuga conținut nou în listă fără a rescrie aplicația. Din fericire pentru noi, adăugarea unei View la un ViewGroup este suportată de un addView(view:View) metodă. Chiar și așa, nu este modul în care RecyclerView adaugă vizualizări pentru copii.

În cazul nostru de utilizare, am avea o listă lungă de contacte. Pentru fiecare contact din listă, ar trebui să creăm un OneContactView și să populăm datele din View pentru a se potrivi cu câmpurile din clasa noastră Contact. Apoi, odată ce avem view-ul, ar trebui să îl 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 matrice de contacte. OneContactView conține sloturi pentru a prelua conținut din clasa Contact și a-l afișa. În RecyclerView, trebuie să adăugăm View-uri pentru a ne ajuta cu capacitatea sa de reciclare.

RecyclerView nu ne permite de fapt să adăugăm o vizualizare, dar ne permite să adăugăm un ViewHolder. Așadar, în acest scenariu, avem două componente pe care vrem să le conectăm, dar care nu se potrivesc. Aici intervine adaptorul nostru. RecyclerView ne oferă un adaptor, la fel ca... AmericanToBritishChargerAdapter() de mai devreme, asta ne-a permis să transformăm încărcătorul nostru AmericanCharger, care era inutilizabil cu dispozitivul nostru BritishDevice, în ceva utilizabil, similar unui adaptor de alimentare în viața reală.

În acest scenariu, adaptorul ar lua matricea noastră de contacte și View-ul nostru și de acolo ar 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 intermediul clasei RecyclerView.Adapter. În interiorul acestui adaptor există o modalitate de a crea clasa ViewHolder cu care RecyclerView dorește să lucreze. Așadar, avem aceeași situație ca înainte, dar cu un element suplimentar, adaptorul.

Avem o matrice de contacte, o vizualizare pentru afișarea unui contact (OneContactView) și o clasă RecyclerView, care este o listă de vizualizări ce oferă servicii de reciclare, dar este dispusă să accepte doar ViewHolders. În acest scenariu, avem acum o clasă RecyclerView.Adapter, care are o metodă de creare a ViewHolders în interior.

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

RecyclerView.ViewHolder este un element abstracClasa t care primește View-ul nostru ca argument și îl convertește într-un ViewHolder. Folosește modelul wrapper folosit pentru a extinde capacităț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ă, din întâmplare, am vrea să adăugăm o metodă pentru a le face să vorbească, dar autorul bibliotecii nu ar fi amuzant, am putea totuși găsi o modalitate. Ceea ce am avea nevoie este un wrapper pentru clasa noastră Animal. Am face acest lucru luând Animal ca și constructor pentru clasa noastră.

class SpeechPoweredAnimalByWrapper(var myAnimal:Animal){

    fun sound(){
        myAnimal.sound()
    }

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

Acum putem transmite o instanță de animal către SpeechPoweredAnimalByWrapper. Apelarea metodei sound() pe aceasta ar apela metoda animal sound() transmisă. De asemenea, avem o metodă suplimentară speak(), care contează ca o funcționalitate nouă 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 prelua clase și adăuga funcționalități. Tot ce avem nevoie este să transmitem o instanță de clasă și metode noi definite de wrap-ul nostru.ping clasă.

În cazul de mai sus, am folosit o clasă concretă. De asemenea, este posibil să implementăm aceeași clasă într-o clasă abs.tracclasa t. Ar trebui să schimbăm clasa SpeechPoweredAnimalByWrapper în abstract și am terminat. Vom schimba numele clasei cu ceva mai scurt pentru a-l face mai ușor de citit.

abstract class SpeechPowered(var myAnimal:Animal){

    fun sound(){
        myAnimal.sound()
    }

    fun 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. AbstracClasele t, însă, nu sunt destinate instanțierii, ci extinderii altor clase. Așadar, cum am folosi noua abs SpeechPowered(var myAnimal:Animal)tracClasa t? O putem folosi creând noi clase care o vor extinde și, la rândul lor, îi vor dobândi 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 un element abstracClasa t care adaugă funcționalitate la View, la fel cum am adăugat metoda speak la animals. Funcționalitatea adăugată este cea care o face să funcționeze atunci când se lucrează cu RecyclerView.

Iată cum 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ă conectăm matricea noastră de contacte la ContactsView cu ajutorul RecyclerView.

Adăugarea unei vizualizări

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

RecyclerView.Recycler

Baza #3 – Caching

Unul dintre cele mai bune exemple de locuri în care facem actualizări regulate este în browser. Să ne imaginăm, de exemplu, că site-ul pe care îl vizităm este static și nu trimite conținut dinamic; ar trebui să actualizăm constant pagina pentru a vedea modificările.

Pentru acest exemplu, să ne imaginăm că site-ul în cauză este Twitter. Am avea o serie de tweet-uri statice listate, iar singura modalitate prin care am putea vedea tweet-urile noi ar fi prin apăsarea butonului de actualizare pentru a reîncărca conținutul.

Revopsirea întregului ecran este evident un lucru costisitor. Imaginați-vă că am avea o lățime de bandă limitată la furnizorul nostru de telefonie și lista noastră de tweet-uri ar avea o mulțime de imagini și videoclipuri; ar fi costisitor să reîncărcăm tot conținutul paginii la fiecare reîmprospătare.

Am avea nevoie de o modalitate de a stoca tweet-urile deja încărcate și de a ne asigura că următoarea noastră solicitare are capacitatea de a spune ce tweet-uri are deja. Prin urmare, nu descarcă din nou totul și primește doar tweet-urile noi și, de asemenea, verifică dacă un tweet care a fost salvat local nu mai există, astfel încât să îl poată șterge local. Ceea ce descriem se numește caching.

Informațiile pe care le trimitem site-ului web despre conținutul pe care îl avem se numesc metadate. Așadar, în sens real, nu spunem doar „vrem să încărcăm site-ul dvs.”, ci spunem „vrem să încărcăm site-ul dvs. și iată o parte din conținutul pe care l-am salvat deja de ultima dată când l-am încărcat; vă rugăm să îl folosiți pentru a ne trimite doar ceea ce nu se află acolo, astfel încât să nu folosim multă lățime de bandă”.

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

Un exemplu de apel layout este scrollToPosition. Acesta este un exemplu comun prezent în lucruri precum aplicațiile de chat. Dacă cineva dintr-un fir de chat răspunde la o bulă de chat anterioară, unele aplicații de chat includ răspunsul și un link către bula de chat, care, la clic, te direcționează către locul unde se afla mesajul tău inițial.

În cazul în care apelăm 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

Baza nr. 4 – Apeluri inverse

În desfășurarea activității sale, RecyclerView are multe componente mobile. Trebuie să interacționeze cu LayoutManager, care ne spune cum să organizăm View-urile, fie liniar, fie într-o grilă. Trebuie să interacționeze cu un adaptor care face treaba de a converti elementele noastre (contactList) în View-uri (OneContactView) și apoi în ViewHolder-uri (OneContactViewHolder) cu care RecyclerView este dispus să lucreze folosind metodele pe care le oferă.

Materia primă pentru RecyclerView o reprezintă View-urile noastre, de exemplu OneContactView, și o sursă de date.

contactList:Array<Contact>

Am folosit un scenariu simplu ca punct de plecare pentru a ne face o idee despre ceea ce încearcă să realizeze RecyclerView. Un caz de bază, atunci când avem o matrice statică de 1000 de contacte pe care dorim să i le arătăm utilizatorului, este ușor de înțeles. Mecanismul RecyclerView începe cu adevărat să prindă 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 element în listă sau eliminăm un element din listă.

RecyclerView.LayoutManager

Pe lângă faptul că decide cum sunt dispuse vizualizările noastre, fie liniar, fie într-o grilă, LayoutManager face multă treabă în hel.ping Reciclatorul știe când să facă reciclarea.

Este responsabil pentru keeping track dintre vizualizările vizibile în prezent pe ecran și comunicarea acestor informații mecanismului de reciclare. Pe măsură ce un utilizator derulează în jos, LayoutManager este responsabil pentru informarea sistemului de reciclare cu privire la vizualizările care ies din focalizare în partea de sus, astfel încât acestea să poată fi reutilizate, în loc să rămână acolo și să consume memorie sau să fie distruse doar pentru a crea altele noi.

Aceasta înseamnă că LayoutManager trebuie să păstreze track din locul în care se află utilizatorul în timp ce derulează lista noastră. Face acest lucru printr-o listă de poziții bazată pe index, adică primul element începe de la 0 și crește pentru a se potrivi cu numărul de elemente din lista noastră.

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

LayoutManager poate elibera aceste vizualizări mecanismului de reciclare, astfel încât să poată fi reutilizate (date noi pot fi legate de ele; de ​​exemplu, datele de contact ale unei vizualizări pot fi eliminate, iar date noi de contact din segmentul următor 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, unde datele pot proveni de la un endpoint online sau chiar poate de la un senzor. Nu numai că sunt adăugate date, dar uneori sunt eliminate sau actualizate datele din lista noastră.

Starea dinamică a datelor noastre poate face foarte dificilă raționamentul cu privire la LayoutManager. Din acest motiv, LayoutManager își menține propria listă cu elementele și pozițiile, separată de lista utilizată de componenta de reciclare. Acest lucru asigură că își îndeplinește corect sarcina de aspect.

În același timp, LayoutManager-ul RecyclerView nu dorește să denatureze datele pe care le deține. Pentru a funcționa corect, LayoutManager se sincronizează cu RecyclerView.Adapter la intervale date (60 ms), partajând informații despre elementele din lista noastră, adică elemente adăugate, actualizate, eliminate sau mutate dintr-o poziție în alta. La primirea acestor informații, LayoutManager 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 au legătură cu RecyclerView se învârt în jurul comunicării dintre RecyclerView.LayoutManager și RecyclerView.Adapter, care stochează listele noastre de date uneori statice sau alteori dinamice.

În plus, RecyclerView ne oferă metode pe care le putem folosi pentru a asculta evenimente, cum ar fi onBindViewHolder atunci când RecyclerView.Adapter leagă conținut din lista noastră (de exemplu, un contact) de un ViewHolder, astfel încât acesta să fie obișnuit să afișeze informațiile pe ecran.

O alta este onCreateViewHolder, care ne spune când RecyclerView.Adapter preia o View obișnuită, cum ar fi OneContactView, și o convertește într-un element ViewHolder cu care RecyclerView poate lucra.

Pe lângă 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 cu care suntem obișnuiți cu vizualizările statice, cum ar fi reacția la evenimentele onClick.

După cum știm, 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 stocată într-o bază de date sau este transmisă în flux continuu dintr-o sursă. Plasarea evenimentelor OnClick direct pe Views poate duce la comportamente neașteptate, cum ar fi ștergerea contactului greșit.

Gradle

Dacă vrem să folosim RecyclerView, trebuie să îl adăugăm ca dependență în fișierul nostru build.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 pentru a sincroniza modificările. Așa facem Gradle fișierul va avea grijă 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"
}

În acest moment avem un singur fișier de layout. Vom începe cu un exemplu simplu în care folosim un RecyclerView pentru a afișa pe ecran o listă de nume de fructe.

Listă de obiecte

Vom naviga la fișierul nostru MainActivity și vom crea un array cu nume de fructe în interior, chiar înainte de metoda onCreate() 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 layout-uri care conține layout-urile noastre și vom crea un View 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-ul de mai sus, am adăugat un câmp id care va fi folosit pentru identificarea unei View. Acesta nu este generat în mod implicit. I-am dat TextView-ului nostru 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ă un fișier de layout main_layout.xml care a fost generat pentru noi în mod implicit. Dacă am ales un proiect gol, acesta va genera un XML conține un ConstraintLayout și în interior va fi un TextView cu textul „Hello”. Vom șterge tot conținutul și vom face ca layout-ul 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 referenția î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 View-urile pe care tocmai le-am creat. Vom începe prin a face referire la RecyclerView folosind metoda findViewById() furnizată de AndroidVom face acest lucru în metoda noastră onCreate(). Metoda noastră onCreate() va arăta astfel.

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

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

Creați un ViewHolder

Apoi, vom crea un RecyclerView.ViewHolder, care este responsabil pentru preluarea View-ului nostru și convertirea acestuia într-un ViewHolder, pe care RecyclerView îl folosește pentru a afișa elementele noastre. Vom face acest lucru imediat după metoda 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-ul pe care îl creăm va fi responsabil pentru următoarele: va prelua numele fructelor 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.

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 niște zgârieturi roșii pe FruitArrayAdapter-ul nostru, spunându-ne că trebuie să implementăm metode pe care RecyclerView le poate utiliza pentru a conecta matricea noastră la un ViewHolder.

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

     }

     override fun getItemCount(): Int {

     }

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

     }
 }

Vom începe cu cea mai simplă parte din codul generat, metoda getItemCount(). Știm cum să obținem numărul de elemente din matricea noastră apelând proprietatea array size.

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

        }

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

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

        }
    }

Apoi vom implementa metoda onCreateViewHolder. Aici RecyclerView ne cere să o ajutăm să construiască un FruitViewHolder. 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 crea o referință la acest XML și o putem converti într-o View 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 suprascrierea onBindViewHolder.

fun onBindViewHolder(holder: FruitViewHolder, position: Int)

RecyclerView.Adapter solicită un număr întreg de poziție, pe care îl folosim pentru a prelua un element din lista noastră. De asemenea, ne oferă un holder, astfel încât să putem lega elementul obținut de la fruitArray la View-ul care se află în interiorul holderului de vizualizare. View-ul conținut în ViewHolder este accesibil prin câmpul ViewHolder.itemView. Odată ce obținem vizualizarea, putem folosi id-ul fruitName creat anterior 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)
      }

Cu aceasta, FruitArrayAdapter-ul nostru este complet și arată astfel.

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 componentele rămase ale RecyclerView-ului nostru, care creează un LayoutManager ce va spune RecyclerView-ului cum să afișeze conținutul listei, fie într-o manieră liniară folosind LinearLayoutManager, fie într-o grilă folosind GridLayoutManager sau StaggeredGridLayoutManager.

Creați Manager de aspect

Vom reveni în funcția noastră 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 furnizat matricea de nume de fructe. Și practic, am terminat. Fișierul MainActivity.kt complet arată astfel.

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

Întrebări frecvente

RecyclerView este succesorul mai flexibil și mai eficient al ListView. Acesta impune modelul ViewHolder, acceptă machete liniare, de tip grilă și eșalonate prin intermediul managerilor de machete și adaugă animații încorporate pentru elemente, în timp ce ListView nu oferă niciuna dintre acestea în mod implicit.

GridView afișează elementele doar într-o grilă fixă. RecyclerView poate reproduce o grilă folosind GridLayoutManager, dar acceptă și machete liniare și decalate, reciclarea vizualizărilor și animații, ceea ce îl face mult mai versatil decât GridView.

Acest exemplu RecyclerView este scris în Kotlin, limbajul recomandat oficial de Google pentru Android dezvoltare. Aceleași API-uri RecyclerView, adaptor și manager de layout funcționează și în Java.

Atașați un ascultător de clicuri în interiorul ViewHolder sau onBindViewHolder, apoi expuneți-l printr-un apel invers al interfeței către activitate sau fragment. Utilizarea poziției ViewHolder evită legarea clicurilor la elementul reciclat greșit.

DiffUtil este o clasă utilitară care calculează diferența dintre două liste și trimite setul minim de actualizări către adaptor. Aceasta eficientizează modificările dinamice ale listelor și animă automat adăugările, eliminările și mutările.

Jetpack Compose folosește LazyColumn și LazyRow în loc de RecyclerView pentru ecranele noi. Cu toate acestea, RecyclerView rămâne utilizat pe scară largă în proiectele existente bazate pe View și poate fi integrat în Compose prin API-uri de interoperabilitate.

Da. Asistenții AI, cum ar fi GitHub Copilot și instrumentele bazate pe GPT, pot crea o schemă pentru un adaptor RecyclerView, ViewHolder și un boilerplate onBindViewHolder dintr-o scurtă solicitare. Dezvoltatorii ar trebui să verifice în continuare codul generat pentru corectitudine și performanță.

Invatare mecanica Android Aplicațiile folosesc RecyclerView pentru a reda rezultate dinamice, cum ar fi recomandări, clasamente de căutare sau clasificări de imagini. Adaptorul leagă rezultatele modelului de elemente de listă, astfel încât seturile de date mari, care se schimbă frecvent, se derulează lin.

Rezumați această postare cu: