Android RecyclerView: wat is, leer met eenvoudige voorbeelden

โšก Slimme samenvatting

RecyclerView is een geavanceerd Android Een widget die grote, scrollbare datasets efficiรซnt weergeeft door een beperkt aantal weergaven te hergebruiken. Het maakt gebruik van een adapter, een lay-outmanager en een weergavehouder om dataverzamelingen te koppelen die tijdens de uitvoering veranderen.

  • ๐Ÿ“ฑ Wat het is: RecyclerView is een flexibele opvolger van ListView en GridView voor het weergeven van grote datasets.
  • ๐Ÿงฑ Weergaven en weergavegroepen: Android De interface wordt getekend met behulp van de klassen View en ViewGroup, waarbij LayoutParams de grootte en positie bepalen.
  • ๐Ÿ”„ Adapter: Het adapterpatroon zet een data-array om in ViewHolders die de RecyclerView kan accepteren.
  • ๐Ÿ“ Lay-outmanager: De lay-outmanager rangschikt items lineair of in een raster en geeft aan wanneer weergaven hergebruikt kunnen worden.
  • โ™ป๏ธ recycling: Weergaven die niet op het scherm te zien zijn, worden hergebruikt in plaats van opnieuw te worden gegenereerd. Dit bespaart geheugen en zorgt voor een soepele scrollervaring.
  • ๏ธ Bouw stappen: Voeg de Gradle Maak een afhankelijkheid aan, een itemlayout, een ViewHolder, een adapter en een LayoutManager.

Android RecyclerBekijken

Waar zit RecyclerView in Android?

De RecyclerBekijken RecyclerView is een flexibelere en geavanceerdere versie van GridView en ListView. Het is een container voor het weergeven van grote datasets die efficiรซnt kunnen worden gescrolld door een beperkt aantal weergaven te behouden. Je kunt de RecyclerView-widget gebruiken wanneer je gegevensverzamelingen hebt waarvan de elementen tijdens de uitvoering veranderen, afhankelijk van een netwerkgebeurtenis of gebruikersactie.

Weergaves

De Android Het platform gebruikt de klassen View en ViewGroup om items op het scherm te tekenen. Deze klassen zijn abstract.tracDe View-klasse en EditText worden uitgebreid naar verschillende implementaties om aan een specifiek gebruiksscenario te voldoen. TextView heeft bijvoorbeeld als eenvoudig doel het weergeven van tekst op het scherm. EditText erft van dezelfde View-klasse en voegt meer functionaliteit toe waarmee de gebruiker gegevens kan invoeren.

Het is mogelijk om onze eigen aangepaste weergaven te creรซren voor meer flexibiliteit tijdens de ontwikkeling.ping Gebruikersinterfaces. De View-klasse biedt methoden die we kunnen overschrijven om op het scherm te tekenen en een manier om parameters door te geven zoals breedte, hoogte en onze eigen aangepaste attributen die we aan onze View willen toevoegen om deze te laten functioneren zoals wij dat willen.

Groepen bekijken

De ViewGroup-klasse is een soort View, maar in tegenstelling tot de eenvoudige View-klasse, die alleen verantwoordelijk is voor het weergeven van gegevens, biedt de ViewGroup ons de mogelijkheid om meerdere views in รฉรฉn view te plaatsen, waarnaar we als geheel kunnen verwijzen. In dit geval wordt de View die op het hoogste niveau wordt gemaakt en waaraan we andere eenvoudige views toevoegen, de "parent" genoemd, en de views die daarin worden toegevoegd, zijn de "children".

We kunnen een View zien als een array en een ViewGroup als een array van arrays. Aangezien een array van arrays zelf ook een array is, kunnen we zien hoe een ViewGroup op dezelfde manier als een View kan worden behandeld.

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

Met de ViewGroup kunnen we ook definiรซren hoe de onderliggende elementen binnen de weergave worden georganiseerd, bijvoorbeeld of ze verticaal of horizontaal worden geplaatst. We kunnen verschillende regels voor interactie binnen de weergave instellen. Zo moeten TextViews die elkaar opvolgen een afstand van 12dp hebben, terwijl een ImageView gevolgd door een TextView een afstand van 5dp moet hebben.

Dit zou het geval zijn als we ons zouden ontwikkelenping onze eigen ViewGroup helemaal vanaf nul. Om deze configuraties te vereenvoudigen, Android biedt een klasse met de naam LayoutParams, die we kunnen gebruiken om deze configuraties in te voeren.

De Android documentatie Dit biedt een aantal standaardparameters die we kunnen implementeren bij het configureren van onze eigen ViewGroup. Enkele veelvoorkomende parameters hebben betrekking op breedte, hoogte en marge. Standaard hebben deze configuraties de structuur `android:layout_height` voor de hoogte en `android:layout_width` voor de breedte. Wanneer u uw ViewGroup aanmaakt, kunt u vervolgens specifieke `LayoutParams` definiรซren die bepalen hoe u wilt dat uw ViewGroup zich gedraagt.

Android Het bevat standaard Views en ViewGroups die we kunnen gebruiken voor veelvoorkomende taken. Een voorbeeld hiervan is de TextView. Dit is een eenvoudige view met configureerbare eigenschappen zoals hoogte, breedte en tekstgrootte. We hebben ook een ImageView voor het weergeven van afbeeldingen en een EditText, zoals we al eerder noemden, naast vele andere. Android heeft ook aangepaste ViewGroups waaraan we onze weergaven kunnen toevoegen en het verwachte gedrag kunnen krijgen.

Lineaire indeling

Met LinearLayout kunnen we View-elementen toevoegen. LinearLayout heeft een `orientation`-attribuut dat bepaalt hoe het op het scherm wordt weergegeven. Het heeft ook `LinearLayout.LayoutParams` die de regels voor de weergaven binnenin bepalen. Het attribuut `android:center_horizontal` centreert de weergaven bijvoorbeeld langs de horizontale as, terwijl `android:center_vertical` de inhoud van de weergave langs de verticale as centreert.

Hieronder staan โ€‹โ€‹enkele afbeeldingen om het centreren te verduidelijken. We gaan ervan uit dat dit een eenvoudige TextView is binnen een ruimte van 200px bij 200px; de centreereigenschappen zouden ervoor zorgen dat deze zich als volgt gedraagt.

androรฏde:center_horizontaal

Horizontaal gecentreerde inhoud

Horizontaal gecentreerde inhoud

androรฏde:center_verticaal

Verticaal gecentreerde inhoud

Verticaal gecentreerde inhoud

androรฏde:centrum

Gecentreerde inhoud

Gecentreerde inhoud

Kerncomponenten van de RecyclerView

Kerncomponenten van de RecyclerView

Kerncomponenten van de RecyclerView

Hieronder staan โ€‹โ€‹de belangrijke componenten van RecyclerView:

RecyclerView.Adapter

Grondwerk #1 โ€“ Adapterpatroon

Een adapter is een apparaat dat de eigenschappen van een systeem of apparaat aanpast aan die van een anderszins incompatibel apparaat of systeem. Sommige adapters wijzigen signaal- of voedingseigenschappen, terwijl andere simpelweg de fysieke vorm van de ene connector aanpassen aan die van een andere.

Een eenvoudig voorbeeld uit de praktijk dat een adapter illustreert, is wanneer we apparaten met elkaar moeten verbinden, maar de aansluitingen niet op elkaar aansluiten. Dit kan bijvoorbeeld het geval zijn wanneer je een ander land bezoekt met andere stopcontacten. Als je je telefoon- of laptopoplader bij je hebt, is het onmogelijk om deze in de stopcontacten aan te sluiten. Je geeft het echter niet op, maar gebruikt gewoon een adapter die tussen het stopcontact en je oplader wordt geplaatst, zodat je apparaat toch kunt opladen.

Dit is een voorbeeld in de programmeerwereld wanneer we twee datastructuren met elkaar willen verbinden om een โ€‹โ€‹taak uit te voeren, maar hun standaardpoorten geen manier bieden om met elkaar te communiceren.

We gebruiken het eenvoudige voorbeeld van een apparaat en een oplader. We hebben twee opladers, een Amerikaanse en een Britse.

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

We gaan dan twee apparaten maken.

class AmericanDevice()
class BritishDevice()

We kunnen dan bijvoorbeeld enkele exemplaren van de apparaten maken om mee te spelen.

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

Vervolgens introduceren we het concept van opladen voor beide apparaten door een methode genaamd `charge()` aan de apparaten toe te voegen. Deze methode ontvangt de bijbehorende oplader en voert het opladen uit op basis daarvan.

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

In dit geval zouden we, op basis van onze analogie, om de een of andere reden een Britse oplader nodig hebben bij het gebruik van een Amerikaans apparaat, of omgekeerd.

In de programmeerwereld is dit meestal het geval bij het combineren van bibliotheken die dezelfde functionaliteit bieden (in onze context wordt onze gedeelde functionaliteit in rekening gebracht). We zouden een manier moeten vinden om dit mogelijk te maken.

Als we de analogie volgen, moeten we naar een elektronicawinkel gaan en een adapter kopen waarmee we Amerikaanse apparaten kunnen opladen met Britse opladers. Vanuit programmeerperspectief zijn wij dan de fabrikant van de adapter.

We maken een adapter die exact overeenkomt met het patroon dat we nodig hebben om de andere te creรซren. We implementeren deze als een klasse, zoals hieronder weergegeven. Het hoeft niet per se een klasse te zijn; het kan ook een functie zijn die illustreert wat het adapterpatroon in het algemeen doet. We gebruiken een klasse omdat dit het beste aansluit bij het gebruik ervan. 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
    }
}

In de programmeerwereld is het verschil tussen sockets vergelijkbaar met het verschil in methoden voor het opladen. Als de opladers verschillende methoden zouden gebruiken, zou het onmogelijk zijn om ze te gebruiken.

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

Het aanroepen van de charge()-methode op myBritishDevice met americanChargerIFound zou niet werken, omdat de AmericanDevice alleen een AmericanCharger accepteert. Het is dus onmogelijk om dit te doen:

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

In dit scenario komt de adapter die we hebben gemaakt, AmericanToBritishChargerAdapter, goed van pas. We kunnen de methode returnNewCharger() gebruiken om een โ€‹โ€‹nieuwe BritishCharger te maken, waarmee we kunnen opladen. Het enige wat we hoeven te doen, is een instantie van onze adapter maken en deze de AmericanCharger meegeven die we hebben, waarna de adapter een BritishCharger aanmaakt die we kunnen gebruiken.

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

Bij een ViewGroup worden Views binnenin geplaatst. De LayoutManager heeft de taak om te beschrijven hoe de Views binnen de ViewGroup worden gerangschikt.

Ter vergelijking: bij het werken met een LinearLayout ViewGroup willen we de items verticaal of horizontaal kunnen plaatsen. Dit is eenvoudig te implementeren door een oriรซntatie-attribuut toe te voegen, dat aangeeft hoe de LinearLayout op het scherm wordt geplaatst. We kunnen dit doen door gebruik te maken van de android:orientation=VERTICAL|HORIZONTAL attribuut.

We hebben ook nog een andere ViewGroup genaamd GridLayout. Deze is handig wanneer we Views in een rechthoekig raster willen plaatsen. Dit kan bijvoorbeeld handig zijn om de gegevens die we aan de app-gebruiker presenteren, makkelijker leesbaar te maken. De GridLayout biedt configuratiemogelijkheden waarmee je dit doel kunt bereiken door de afmetingen van het raster te definiรซren; zo kun je bijvoorbeeld een 4x4-raster of een 3x2-raster gebruiken.

RecyclerView.ViewHolder

De ViewHolder is een abstracDe ViewHolder is een klasse die we ook van RecyclerView overerven. De ViewHolder biedt ons algemene methoden waarmee we kunnen verwijzen naar een View die we in de RecyclerView hebben geplaatst, zelfs nadat de recyclingmechanismen in de RecyclerView verschillende verwijzingen hebben gewijzigd waar we geen weet van hebben.

Grote lijsten

RecyclerViews worden gebruikt wanneer we een zeer grote set weergaven aan de gebruiker willen presenteren, zonder de capaciteit van de viewbox uit te putten. RAM op ons apparaat voor elk exemplaar van de gemaakte weergave.

Als we bijvoorbeeld een contactenlijst nemen, hebben we een algemeen idee hoe een contactpersoon er in de lijst uit zou zien. Vervolgens maken we een sjabloonlay-out, in feite een weergave, met vakjes waarin verschillende gegevens uit onze contactenlijst worden geplaatst. De volgende pseudocode illustreert het hele principe:

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

We zouden dan een contactlijst van deze aard hebben:

<ContactList>
</ContactList>

Als we de inhoud hardcoded zouden vastleggen, zouden we geen programmatische manier hebben om nieuwe inhoud aan de lijst toe te voegen zonder de app te herschrijven. Gelukkig wordt het toevoegen van een View aan een ViewGroup ondersteund door een addView(view:View) methode. Maar zelfs dan worden de onderliggende weergaven niet op deze manier aan de RecyclerView toegevoegd.

In ons geval hebben we een lange lijst met contactpersonen. Voor elke contactpersoon in de lijst moeten we een OneContactView maken en de gegevens in de View vullen zodat ze overeenkomen met de velden in onze Contact-klasse. Zodra we de View hebben, moeten we deze toevoegen aan de RecyclerView om de lijst weer te geven.

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)

We hebben een array met contactpersonen. De OneContactView bevat slots om inhoud uit de Contact-klasse te halen en weer te geven. In de RecyclerView moeten we Views toevoegen zodat deze kan bijdragen aan de hergebruikfunctie.

De RecyclerView staat ons niet toe om een โ€‹โ€‹view toe te voegen, maar wel een ViewHolder. In dit scenario hebben we dus twee onderdelen die we willen verbinden, maar die niet overeenkomen. Dit is waar onze adapter van pas komt. RecyclerView biedt ons een adapter, net zoals onze AmericanToBritishChargerAdapter() Een eerdere stap stelde ons in staat om onze Amerikaanse oplader, die onbruikbaar was met ons Britse apparaat, om te bouwen tot iets bruikbaars, vergelijkbaar met een echte stroomadapter.

In dit scenario zou de adapter onze array met contactpersonen en onze View gebruiken om ViewHolders te genereren die de RecyclerView kan accepteren.

De RecyclerView biedt een interface die we kunnen uitbreiden om onze eigen adapter te maken via de klasse RecyclerView.Adapter. Binnen deze adapter is er een manier om de ViewHolder-klasse te creรซren waarmee de RecyclerView wil werken. We hebben dus dezelfde situatie als voorheen, maar met รฉรฉn extra element: de adapter.

We hebben een array met contactpersonen, een weergave om รฉรฉn contactpersoon weer te geven (OneContactView) en een RecyclerView, een lijst met weergaven die hergebruikservices biedt, maar alleen ViewHolders accepteert. In dit scenario hebben we nu een RecyclerView.Adapter-klasse met een methode om ViewHolders te creรซren.

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

De RecyclerView.ViewHolder is een abstracEen klasse die onze View als argument neemt en deze omzet naar een ViewHolder. Deze klasse maakt gebruik van het wrapper-patroon, dat wordt gebruikt om de mogelijkheden van klassen uit te breiden.

Grondwerk #2 โ€“ Wikkelpatroon

We zullen een eenvoudig voorbeeld gebruiken om te laten zien hoe we dieren kunnen laten spreken.

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

In het bovenstaande voorbeeld hebben we twee dieren. Stel dat we een methode wilden toevoegen om ze te laten spreken, maar de auteur van de bibliotheek was niet zo behulpzaam, dan zouden we toch een manier kunnen vinden. Wat we nodig zouden hebben, is een wrapper voor onze klasse Animal. Dat zouden we doen door Animal als constructor voor onze klasse te gebruiken.

class SpeechPoweredAnimalByWrapper(var myAnimal:Animal){

    fun sound(){
        myAnimal.sound()
    }

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

Nu kunnen we een dier-instantie doorgeven aan SpeechPoweredAnimalByWrapper. Door de sound()-methode erop aan te roepen, wordt de sound()-methode van het doorgegeven dier aangeroepen. We hebben ook een extra speak()-methode, die telt als nieuwe functionaliteit die we toevoegen aan de doorgegeven dieren. We kunnen deze als volgt gebruiken:

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"

Met dit patroon kunnen we functionaliteit toevoegen aan klassen. Het enige wat we hoeven te doen, is een klasse-instantie en nieuwe methoden, gedefinieerd door onze wrapper, door te geven.ping klasse.

In het bovenstaande voorbeeld hebben we een concrete klasse gebruikt. Het is ook mogelijk om hetzelfde te implementeren in een abstracte klasse.tract-klasse. We zouden de klasse SpeechPoweredAnimalByWrapper moeten veranderen naar abs.tracEn daarmee zijn we klaar. We veranderen de klassenaam in iets korter om hem leesbaarder te maken.

abstract class SpeechPowered(var myAnimal:Animal){

    fun sound(){
        myAnimal.sound()
    }

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

Het is hetzelfde als voorheen, maar het zou iets anders betekenen. In een normale klas kunnen we een instantie van een klasse hebben op dezelfde manier als we cat1 en dog1 hebben gemaakt. Abstract-klassen zijn echter niet bedoeld om te worden geรฏnstantieerd, maar om andere klassen uit te breiden. Dus hoe zouden we de nieuwe SpeechPowered(var myAnimal:Animal) abs gebruiken?tract klasse? We kunnen die gebruiken door nieuwe klassen te creรซren die ervan uitbreiden en zo de functionaliteit ervan overnemen.

In ons voorbeeld maken we een klasse SpeechPoweredAnimal die de klasse uitbreidt.

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

Dit is hetzelfde patroon dat in de ViewHolder wordt gebruikt. De klasse RecyclerView.ViewHolder is een abstracEen klasse die functionaliteit toevoegt aan de View, net zoals we de speak-methode aan de dieren hebben toegevoegd. De toegevoegde functionaliteit zorgt ervoor dat het werkt in combinatie met de RecyclerView.

Zo maken we een OneContactViewHolder van 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 heeft een adapter waarmee we onze contactenlijst kunnen koppelen aan de ContactsView via de RecyclerView.

Een weergave toevoegen

De ViewGroup wordt niet automatisch opnieuw getekend, maar volgt een specifiek schema. Op uw apparaat kan het bijvoorbeeld zo zijn dat de ViewGroup elke 10 ms of 100 ms opnieuw wordt getekend; of, als we een absurd getal kiezen, bijvoorbeeld 1 minuut, dan ziet u de wijzigingen 1 minuut later wanneer de ViewGroup wordt 'ververst' nadat u een View aan een ViewGroup hebt toegevoegd.

RecyclerView.Recycler

Basisoefening #3 โ€“ Geocaching

Een van de beste voorbeelden van waar we regelmatig de pagina vernieuwen, is in de browser. Stel je bijvoorbeeld voor dat de website die we bezoeken statisch is en geen dynamische content verzendt; dan zouden we de pagina steeds moeten vernieuwen om de wijzigingen te zien.

Laten we voor dit voorbeeld aannemen dat de betreffende site Twitter is. We zouden een reeks statische tweets zien, en de enige manier om nieuwe tweets te zien, zou zijn door op de vernieuwingsknop te klikken om de inhoud opnieuw op te halen.

Het volledig opnieuw tekenen van het scherm is uiteraard een kostbare aangelegenheid. Stel je voor dat we een beperkte bandbreedte hebben bij onze telefoonprovider en onze lijst met tweets veel afbeeldingen en video's bevat; het zou kostbaar zijn om alle inhoud van de pagina bij elke vernieuwing opnieuw te downloaden.

We hebben een manier nodig om de reeds geladen tweets op te slaan en ervoor te zorgen dat ons volgende verzoek kan aangeven welke tweets het al heeft. Op die manier hoeft het niet alles opnieuw te downloaden, maar worden alleen de nieuwe tweets opgehaald. Ook wordt gecontroleerd of een lokaal opgeslagen tweet niet meer aanwezig is, zodat deze indien nodig lokaal kan worden verwijderd. Wat we hiermee beschrijven, noemen we caching.

De informatie die we naar de website sturen over de inhoud die we hebben, noemen we metadata. In feite zeggen we dus niet alleen "we willen je site laden", maar "we willen je site laden, en hier is een deel van de inhoud die we al hebben opgeslagen van de vorige keer dat we je site hebben geladen; gebruik dit alstublieft om ons alleen de inhoud te sturen die er nog niet in staat, zodat we niet te veel bandbreedte gebruiken."

Layout-oproepen โ€“ De tweetlijst moet gek zijn

Een voorbeeld van een layout-aanroep is `scrollToPosition`. Dit is een veelvoorkomend voorbeeld in bijvoorbeeld chat-apps. Als iemand in een chatgesprek reageert op een eerder geplaatst bericht, tonen sommige chat-apps het antwoord en een link naar het bericht. Door op die link te klikken, ga je terug naar de pagina waar je het oorspronkelijke bericht plaatste.

Als we deze methode aanroepen voordat we een LayoutManager aan onze RecyclerView hebben toegevoegd en voordat we een RecyclerView.Adapter hebben, wordt scrollToPosition(n:Int) simpelweg genegeerd.

Communicatie tussen RecyclerView-componenten

Voorbereiding #4 โ€“ Terugbelverzoeken

De RecyclerView heeft, om zijn werk te kunnen doen, veel bewegende onderdelen. Hij moet samenwerken met de LayoutManager, die bepaalt hoe de Views georganiseerd moeten worden, lineair of in een raster. Hij moet ook samenwerken met een adapter die onze items (contactList) omzet in Views (OneContactView) en vervolgens in ViewHolders (OneContactViewHolder) waarmee de RecyclerView kan werken met behulp van de methoden die hij aanbiedt.

De grondstoffen voor de RecyclerView zijn onze Views, bijvoorbeeld OneContactView, en een gegevensbron.

contactList:Array<Contact>

We hebben een eenvoudig scenario als uitgangspunt gebruikt om een โ€‹โ€‹idee te krijgen van wat de RecyclerView probeert te bereiken. Een basisvoorbeeld, waarbij we een statische lijst met 1000 contactpersonen hebben die we aan de gebruiker willen tonen, is gemakkelijk te begrijpen. De RecyclerView komt pas echt tot leven wanneer de lijst niet langer statisch is. Bij een dynamische lijst moeten we nadenken over wat er met de weergave op het scherm gebeurt wanneer we een item aan de lijst toevoegen of een item uit de lijst verwijderen.

RecyclerView.LayoutManager

Naast het bepalen van de lay-out van onze weergaven, lineair of in een raster, verricht de LayoutManager veel werk achter de schermen.ping De recycler weet wanneer hij moet recyclen.

Het is verantwoordelijk voor het bewaren vanping tracDe LayoutManager registreert de Views die momenteel op het scherm zichtbaar zijn en communiceert deze informatie naar het recyclingmechanisme. Wanneer een gebruiker naar beneden scrollt, is de LayoutManager verantwoordelijk voor het informeren van het recyclingsysteem over de Views die bovenaan de focus verliezen, zodat ze hergebruikt kunnen worden in plaats van daar te blijven en geheugen te verbruiken of te worden verwijderd om plaats te maken voor nieuwe.

Dit betekent dat de LayoutManager het volgende moet bewaren: track geeft aan waar de gebruiker zich bevindt tijdens het scrollen door onze lijst. Dit doet het door een lijst met posities te gebruiken die op indexen gebaseerd zijn, d.w.z. het eerste item begint bij 0 en loopt door tot het aantal items in onze lijst.

Als we in het begin 10 items van onze lijst van bijvoorbeeld 100 items kunnen bekijken, weet de LayoutManager dat alle weergaven, van weergave 0 tot en met weergave 9, in focus zijn. Tijdens het scrollen kan de LayoutManager berekenen welke weergaven niet meer in focus zijn.

De LayoutManager kan deze weergaven vrijgeven aan het recyclingmechanisme, zodat ze opnieuw kunnen worden gebruikt (er kunnen nieuwe gegevens aan worden gekoppeld; bijvoorbeeld, de contactgegevens van een weergave kunnen worden verwijderd en nieuwe contactgegevens uit het volgende segment kunnen de plaatsaanduidingen vervangen).

Dit zou ideaal zijn als de lijst statisch was, maar een van de meest voorkomende toepassingen van een RecyclerView is met dynamische lijsten waarbij gegevens afkomstig kunnen zijn van een online bron of zelfs van een sensor. Er worden niet alleen gegevens toegevoegd, maar soms worden er ook gegevens uit onze lijst verwijderd of bijgewerkt.

De dynamische aard van onze data kan het erg lastig maken om de LayoutManager te begrijpen. Daarom houdt de LayoutManager een eigen lijst bij van de items en hun posities, los van de lijst die de recyclingcomponent gebruikt. Dit zorgt ervoor dat de LayoutManager zijn lay-outtaak correct uitvoert.

Tegelijkertijd wil de LayoutManager van de RecyclerView de gegevens die hij heeft niet verkeerd weergeven. Om correct te functioneren, synchroniseert de LayoutManager met de RecyclerView.Adapter met vaste intervallen (60 ms) en deelt informatie over de items in onze lijst, zoals items die zijn toegevoegd, bijgewerkt, verwijderd of verplaatst. Na ontvangst van deze informatie herschikt de LayoutManager de inhoud op het scherm indien nodig om de wijzigingen weer te geven.

Veel van de kernbewerkingen met RecyclerView draaien om de communicatie tussen de RecyclerView.LayoutManager en RecyclerView.Adapter, die onze lijsten met soms statische en soms dynamische gegevens opslaan.

Daarnaast biedt RecyclerView ons methoden waarmee we gebeurtenissen kunnen opvangen, zoals onBindViewHolder, wanneer onze RecyclerView.Adapter inhoud uit onze lijst (bijvoorbeeld een contactpersoon) aan een ViewHolder koppelt, zodat deze gebruikt kan worden om de informatie op het scherm weer te geven.

Een andere is onCreateViewHolder, die ons vertelt wanneer de RecyclerView.Adapter een gewone View, zoals OneContactView, neemt en deze omzet in een ViewHolder-item waarmee de RecyclerView kan werken.

Naast de kernmechanismen die hergebruik mogelijk maken, biedt de RecyclerView manieren om het gedrag aan te passen zonder het hergebruik te beรฏnvloeden. Hergebruik van Views maakt het lastig om gangbare handelingen uit te voeren die we gewend zijn met statische views, zoals reageren op onClick-events.

Zoals we weten, RecyclerView.LayoutManager die de weergaven aan de gebruiker presenteert, kan tijdelijk een andere lijst met items hebben dan de RecyclerView.Adapter waarbij de lijst is opgeslagen in een database of wordt gestreamd vanuit een bron. Het direct plaatsen van OnClick-events op Views kan leiden tot onverwacht gedrag, zoals het verwijderen van het verkeerde contact.

Gradle

Als we RecyclerView willen gebruiken, moeten we deze als afhankelijkheid toevoegen aan ons build.gradle-bestand. In het volgende voorbeeld hebben we de implementatie "androidx.recyclerview:recyclerview:1.1.0" gebruikt, wat volgens dit artikel de meest recente versie is.

Na het toevoegen van de afhankelijkheid aan onze Gradle bestand, we zullen worden gevraagd door Android Studio om de wijzigingen te synchroniseren. Zo doen wij dat. Gradle Dit bestand wordt gebruikt om een โ€‹โ€‹RecyclerView toe te voegen aan een leeg project met alleen de standaardinstellingen.

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

We hebben momenteel slechts รฉรฉn lay-outbestand. We beginnen met een eenvoudig voorbeeld waarin we een RecyclerView gebruiken om een โ€‹โ€‹lijst met fruitnamen op het scherm weer te geven.

Lijst van items

We gaan naar ons MainActivity-bestand en maken een array met fruitnamen aan, vlak voor de onCreate()-methode die tijdens de installatie is gegenereerd.

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

Ons volgende doel is om deze lijst op het scherm weer te geven met behulp van een RecyclerView. Hiervoor gaan we naar de lay-outmap met onze lay-outs en maken we een View aan die verantwoordelijk is voor het tonen van รฉรฉn fruitsoort.

Lay-out die voor elk item in onze lijst moet worden gebruikt

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

In de bovenstaande TextView hebben we een id-veld toegevoegd dat gebruikt zal worden om een โ€‹โ€‹View te identificeren. Dit veld wordt niet standaard gegenereerd. We hebben onze TextView de id 'fruitName' gegeven, zodat deze overeenkomt met de data die eraan gekoppeld zal worden.

De RecyclerView toevoegen aan de hoofdlay-out

Binnen dezelfde activiteit is er een lay-outbestand main_layout.xml dat standaard voor ons is gegenereerd. Als we een leeg project hadden gekozen, zou er een dergelijk bestand zijn gegenereerd. XML De layout bevat een ConstraintLayout met daarin een TextView met de tekst "Hallo". We verwijderen alle inhoud, zodat de layout alleen nog de RecyclerView bevat, zoals hieronder weergegeven:

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

We hebben ook een id-attribuut toegevoegd aan de RecyclerView, dat we zullen gebruiken om ernaar te verwijzen in onze code.

android:id="@+id/fruitRecyclerView"

We gaan nu terug naar ons MainActivity-bestand. Met behulp van de ID's die we hebben aangemaakt, kunnen we verwijzen naar de Views die we zojuist hebben gemaakt. We beginnen met het verwijzen naar de RecyclerView met behulp van de findViewById()-methode die beschikbaar is gesteld door AndroidWe doen dit in onze onCreate()-methode. Onze onCreate()-methode ziet er als volgt uit.

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

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

Maak een ViewHolder

Vervolgens maken we een RecyclerView.ViewHolder aan. Deze is verantwoordelijk voor het omzetten van onze View naar een ViewHolder, die de RecyclerView vervolgens gebruikt om onze items weer te geven. We doen dit direct na onze onCreate()-methode.

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)

}

Maak een RecyclerViewAdapter

Vervolgens maken we een FruitArrayAdapter-klasse aan die de RecyclerView.Adapter-klasse uitbreidt. De FruitArrayAdapter die we maken, is verantwoordelijk voor het volgende: hij haalt de fruitnamen uit de fruitarray, maakt een ViewHolder aan met behulp van onze view one_fruit_view.xml, koppelt het fruit aan de ViewHolder en koppelt de inhoud dynamisch aan de View die we hebben gemaakt.

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 Dit zal rode golvende lijnen toevoegen aan onze FruitArrayAdapter, wat aangeeft dat we methoden moeten implementeren die de RecyclerView kan gebruiken om onze array aan een ViewHolder te koppelen.

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

     }
 }

We beginnen met het eenvoudigste deel van de gegenereerde code, de getItemCount()-methode. We weten hoe we het aantal items in onze array kunnen krijgen door de array-eigenschap size aan te roepen.

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

        }
    }

Vervolgens implementeren we de methode onCreateViewHolder. Hier vraagt โ€‹โ€‹de RecyclerView ons om te helpen bij het aanmaken van een FruitViewHolder. Zoals we ons wellicht herinneren, zag onze FruitViewHolder-klasse er als volgt uit:

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

Hiervoor hebben we onze fruitView nodig, die we als XML-bestand hebben aangemaakt: one_fruit_view.xml. We kunnen een verwijzing naar deze XML maken en deze als volgt naar een View converteren.

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

        }
    }

Het resterende deel is de onBindViewHolder-override.

fun onBindViewHolder(holder: FruitViewHolder, position: Int)

De RecyclerView.Adapter vraagt โ€‹โ€‹om een โ€‹โ€‹positie-integer, die we gebruiken om een โ€‹โ€‹item uit onze lijst op te halen. Het biedt ons ook een houder, zodat we het item dat we uit de fruitArray krijgen, kunnen koppelen aan de View die zich in de houder bevindt. De View in een ViewHolder is toegankelijk via het veld ViewHolder.itemView. Zodra we de View hebben, kunnen we de eerder aangemaakte id fruitName gebruiken om de inhoud in te stellen.

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

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

          var aFruitName = fruitArray.get(position)

          ourFruitTextView.setText(aFruitName)
      }

Daarmee is onze FruitArrayAdapter voltooid en ziet er als volgt uit.

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

Tot slot kunnen we de resterende onderdelen van onze RecyclerView aansluiten. Dit houdt in dat we een LayoutManager maken die de RecyclerView vertelt hoe de inhoud van de lijst moet worden weergegeven: lineair met behulp van LinearLayoutManager, of in een raster met behulp van GridLayoutManager of StaggeredGridLayoutManager.

Lay-outmanager maken

We gaan terug naar onze onCreate-functie en voegen de LayoutManager toe.

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

   }

Sluit onze adapter aan op artikelen en stel hem in op 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
    }

We hebben ook een fruitListAdapter-instantie aangemaakt en deze voorzien van de array met fruitnamen. En daarmee zijn we in principe klaar. Het complete MainActivity.kt-bestand ziet er als volgt uit.

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

Download het project

Veelgestelde vragen

RecyclerView is de flexibelere en efficiรซntere opvolger van ListView. Het dwingt het ViewHolder-patroon af, ondersteunt lineaire, raster- en verspringende lay-outs via lay-outmanagers en voegt ingebouwde itemanimaties toe, terwijl ListView deze standaard niet biedt.

GridView geeft items alleen weer in een vast raster. RecyclerView kan een raster reproduceren met behulp van GridLayoutManager, maar ondersteunt ook lineaire en verspringende lay-outs, het hergebruiken van weergaven en animaties, waardoor het veelzijdiger is dan GridView.

Dit RecyclerView-voorbeeld is geschreven in Kotlin, de taal die officieel wordt aanbevolen door Google besteld, Android ontwikkeling. Dezelfde RecyclerView-, adapter- en layoutmanager-API's werken ook in Java.

Voeg een kliklistener toe aan de ViewHolder of onBindViewHolder en stel deze vervolgens beschikbaar via een interface-callback aan de activiteit of het fragment. Door de positie van de ViewHolder te gebruiken, wordt voorkomen dat klikken aan het verkeerde hergebruikte item worden gekoppeld.

DiffUtil is een hulpprogrammaklasse die het verschil tussen twee lijsten berekent en de minimale set updates naar de adapter verzendt. Het maakt dynamische lijstwijzigingen efficiรซnt en animeert toevoegingen, verwijderingen en verplaatsingen automatisch.

Jetpack Compose gebruikt LazyColumn en LazyRow in plaats van RecyclerView voor nieuwe schermen. RecyclerView wordt echter nog steeds veel gebruikt in bestaande projecten die op Views gebaseerd zijn en kan via interoperabiliteits-API's in Compose worden geรฏntegreerd.

Ja. AI-assistenten zoals GitHub Copilot en GPT-gebaseerde tools kunnen een RecyclerView-adapter, ViewHolder en onBindViewHolder-boilerplate genereren op basis van een korte prompt. Ontwikkelaars moeten de gegenereerde code echter nog steeds controleren op correctheid en prestaties.

machine learning Android Apps gebruiken RecyclerView om dynamische resultaten weer te geven, zoals aanbevelingen, zoekresultaten of beeldclassificaties. De adapter koppelt modelresultaten aan lijstitems, zodat grote, vaak veranderende datasets soepel scrollen.

Vat dit bericht samen met: