Android RecyclerView: wat is, leer met eenvoudige voorbeelden

Wat is RecyclerView in Android?

Het RecyclerBekijken is een widget die een flexibelere en geavanceerdere versie is 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. U kunt de RecyclerView-widget gebruiken als u gegevensverzamelingen heeft waarvan de elementen tijdens runtime veranderen, afhankelijk van netwerkgebeurtenissen of gebruikersacties.

In deze zelfstudie leert u:

keer bekeken

Het Android-platform gebruikt de klassen View en ViewGroup om items op het scherm te tekenen. Deze klassen zijn abstract en worden uitgebreid naar verschillende implementaties om aan een use-case te voldoen. TextView heeft bijvoorbeeld een eenvoudig doel: tekstinhoud op het scherm weergeven. EditText breidt zich uit van dezelfde View-klasse en voegt meer functionaliteit toe zodat de gebruiker gegevens kan invoeren.

Het is mogelijk om onze eigen aangepaste weergaven te maken om meer flexibiliteit te bereiken bij het ontwikkelen van gebruikersinterfaces. De View-klasse biedt methoden die we kunnen overschrijven om op het scherm te tekenen en een manier om parameters zoals breedte, hoogte en onze eigen aangepaste attributen door te geven die we aan onze View willen toevoegen om deze zich te laten gedragen zoals we zouden willen.

Groepen bekijken

De ViewGroup-klasse is een soort View, maar in tegenstelling tot de eenvoudige View-klasse, wiens verantwoordelijkheid simpelweg het weergeven is, geeft de ViewGroup ons de mogelijkheid om meerdere weergaven in één weergave te plaatsen, waarnaar we als geheel kunnen verwijzen. In dit geval wordt de weergave die op het hoogste niveau is gemaakt en waaraan we andere eenvoudige weergaven toevoegen (we kunnen ook viewGroups toevoegen) de 'bovenliggende' genoemd, en de weergaven die daarin worden toegevoegd zijn 'kinderen'.

We kunnen een weergave weergeven als een array en een ViewGroup als een array van arrays. Gegeven dat een Array van Arrays zelf een Array is, kunnen we zien hoe een ViewGroup 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 anology, 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 kinderen in het zicht zijn georganiseerd, bijvoorbeeld verticaal of horizontaal. We kunnen verschillende regels hebben voor interactie binnen de weergave. TextViews volgt bijvoorbeeldwing elkaar moeten een afstand van 12 dp hebben, terwijl ImageViews gevolgd door TextView een afstand van 5 dp moeten hebben.

Dit zou het geval zijn als we onze eigen ViewGroup helemaal opnieuw zouden ontwikkelen. Om deze configuraties eenvoudiger te maken, biedt Android een klasse genaamd LayoutParams, die we kunnen gebruiken om deze configuraties in te voeren.

Android-documentatie biedt enkele standaardparameters die we zouden implementeren bij het configureren van onze eigen ViewGroup. Enkele veel voorkomende parameters zijn de parameters die betrekking hebben op de breedte, hoogte en marge. Standaard hebben deze configuraties de structuur android:layout_height voor hoogte, bijvoorbeeld android:layout_width. In dit opzicht kunt u, wanneer u uw ViewGroup maakt, verder LayoutParams maken die specifiek zijn voor de manier waarop u wilt dat uw ViewGroup zich gedraagt.

Android wordt geleverd met standaardweergaven en weergavegroepen die we kunnen gebruiken om veel van de algemene taken uit te voeren die we nodig hebben. Een voorbeeld dat we hebben genoemd is een TextView. Dat is een eenvoudige weergave met configureerbare aspecten zoals hoogte, breedte, tekstgrootte en dergelijke. We hebben een ImageView voor het weergeven van afbeeldingen en EditText, zoals we al zeiden, en vele andere. Android heeft ook aangepaste ViewGroups waaraan we onze weergaven kunnen toevoegen en het verwachte gedrag kunnen verkrijgen.

Lineaire indeling

Met LinearLayout kunnen we View-items eraan toevoegen. LinearLayout is een oriëntatieattribuut dat bepaalt hoe het op het scherm wordt weergegeven. Het heeft ook LinearLayout.LayoutParams die de regels voor de weergaven binnenin dicteren, bijvoorbeeld het attribuut android:center_horizontal zou de weergaven langs de horizontale as centreren, terwijl `android:center_vertical de inhoud van de weergave langs de verticale as zou centreren.

Hier zijn enkele afbeeldingen om inzicht te krijgen in centreren. We zouden dit beschouwen als een eenvoudige TextView binnen een ruimte van 200 px bij 200 px, door attributen te centreren zou het zich als volgt gedragen.

android: center_horizontaal

Lineaire indeling
Horizontaal gecentreerde inhoud

android: center_vertical

Lineaire indeling
verticaal gecentreerde inhoud

androïde: midden

Lineaire indeling
Gecentreerde inhoud

Kerncomponenten van de RecyclerView

Lineaire indeling
Kerncomponenten van de RecyclerView

Following zijn de belangrijke componenten van RecyclerView:

RecyclerView.Adapter

Grondwerk #1 – Adapterpatroon

Een adapter is een apparaat dat kenmerken van een systeem of apparaat omzet naar die van een anderwise incompatibel apparaat of systeem. Sommigen van hen wijzigen signaal- of vermogenskenmerken, terwijl anderen eenvoudigweg de fysieke vorm van de ene connector aan de andere aanpassen.

Een eenvoudig voorbeeld dat we in het echte leven tegenkomen om een ​​adapter uit te leggen, is wanneer we apparaten met elkaar moeten verbinden, maar ze hebben verbindingspoorten die niet met elkaar overeenkomen. Dit kan het geval zijn als je naar een ander land gaat waar ze andere soorten stopcontacten gebruiken. Als u de oplader van uw telefoon of laptop bij u draagt, is het onmogelijk om deze op het stopcontact aan te sluiten. U zou echter niet opgeven, maar gewoon een adapter kopen die tussen het stopcontact en uw oplader zou worden geplaatst en het opladen mogelijk zou maken.

Dit is het geval bij het programmeren wanneer we twee datastructuren met elkaar willen verbinden om de taak te vervullen, maar hun standaardpoorten geen manier hebben om met elkaar te communiceren.

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

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

We maken dan twee apparaten

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 toe te voegen aan de apparaten genaamd charge() .

De methode neemt als invoer de respectieve lader op en laadt 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, gebaseerd op onze analogie, om de een of andere reden behoefte hebben aan het gebruik van een British Charger bij 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, zullen we naar een elektronicawinkel moeten gaan en een adapter kopen waarmee we Amerikaanse apparaten kunnen opladen met Britse opladers. Vanuit programmeerperspectief zijn wij degenen die de fabrikant van de adapter zullen zijn.

Voor de ene maken we een adapter die exact overeenkomt met het patroon dat we nodig hebben om de andere te maken. We zullen het als volgt als klasse implementeren. Het hoeft niet noodzakelijkerwijs een klasse te zijn en kan een functie zijn die benadrukt wat het adapterpatroon in het algemeen doet. We gebruiken een klasse omdat deze overeenkomt met het meeste gebruik op 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
    }
}

In de programmeerwereld is het verschil in stopcontacten analoog aan het verschil in de methoden die binnenin worden gebruikt voor het opladen. De opladers met verschillende methoden zouden het onmogelijk maken om de opladers te gebruiken.

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

Proberen de methode charge() in myBritishDevice aan te roepen met americanChargerIFound zou niet werken omdat het 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 de adapter die we hebben gemaakt

AmericanToBritishChargerAdapter kan nu van pas komen. We kunnen de methode returnNewCharger() gebruiken om een ​​nieuwe BritishCharger te maken, die we kunnen gebruiken om op te laden. Het enige wat we nodig hebben is een exemplaar van onze adapter te maken en deze te voeden met de AmericanCharger die we hebben, en er zal een BritishCharger worden gemaakt die we kunnen gebruiken

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

Als we met een ViewGroup te maken hebben, zouden we Views erin plaatsen. Het is LayoutManager die de taak zou hebben om te beschrijven hoe de weergaven binnenin zijn ingedeeld.

Ter vergelijking: bij het werken met Linearlayout ViewGroup is het gebruiksscenario dat we willen de mogelijkheid om de items verticaal of horizontaal te plaatsen. Dit is eenvoudig te implementeren door een oriëntatie-attribuut toe te voegen, dat ons vertelt hoe de lineaire lay-out op het scherm zal worden geplaatst. Dit kunnen we doen door gebruik te maken van android:orientation=VERTICAL|HORIZONTAL attribuut.

We hebben ook een andere ViewGroup genaamd GridLayout. Het gebruik ervan is wanneer we weergaven in een rechthoekige rasterstructuur willen plaatsen. Dit kan bijvoorbeeld een reden zijn om ervoor te zorgen dat de gegevens die we aan de app-gebruiker presenteren, gemakkelijk te gebruiken zijn. Door het ontwerp maakt de GridLayout configuraties mogelijk om u te helpen dit doel te bereiken door configuraties te hebben waarbij we de afmetingen van het raster kunnen definiëren. We kunnen bijvoorbeeld een 4×4-raster, 3x2-raster hebben.

RecyclerView.ViewHolder

De ViewHolder is een abstracte klasse die we ook uitbreiden vanuit RecyclerView. De ViewHolder biedt ons algemene methoden om ons te helpen verwijzen naar een weergave die we op de RecyclerView hebben geplaatst, zelfs nadat de recyclingmachines in de RecyclerView verschillende referenties hebben gewijzigd waarvan we niets weten.

Grote lijsten

RecyclerViews worden gebruikt wanneer we een heel groot aantal weergaven aan de gebruiker willen presenteren, terwijl we onze mogelijkheden niet uitputten. RAM op ons apparaat voor elk exemplaar van de gemaakte weergave.

Als we het geval van een contactlijst zouden nemen, zouden we een algemeen idee hebben van hoe een contactpersoon er in de lijst uit zou zien. Wat we dan zouden doen is een sjabloonlay-out maken – wat eigenlijk een weergave is – met slots waar verschillende gegevens uit onze contactenlijst zullen worden gevuld. Volgwing is een pseudocode die het hele doel uitlegt:

//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 dat het geval is en we de inhoud hardcoderen, zouden we geen programmatische manier hebben om nieuwe inhoud aan de lijst toe te voegen zonder de app te herschrijven. Gelukkig voor ons. Het toevoegen van een weergave aan een ViewGroup wordt ondersteund door een addView(view:View) methode.

Zelfs als dat het geval is, is het niet de manier waarop RecyclerView er weergaven van kinderen aan toevoegt.

In ons gebruiksscenario zouden we een lange lijst met contacten hebben. Voor elk contact in de lijst moeten we OneContactView maken en de gegevens in de weergave invullen zodat deze overeenkomen met de velden in onze Contact-klasse. Zodra we de weergave hebben, moeten we deze toevoegen aan 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 reeks contacten genaamd OneContactView. Het bevat slots om inhoud uit de klasse Contact te halen en deze weer te geven. In RecyclerView moeten we er weergaven aan toevoegen, zodat het ons kan helpen met zijn recyclingmogelijkheden.

Met de RecyclerView kunnen we niet echt weergave toevoegen, maar kunnen we wel een ViewHolder toevoegen. In dit scenario hebben we dus twee onderdelen die we met elkaar willen verbinden, maar die niet bij elkaar passen. Dit is waar onze adapter van pas komt. RecyclerView biedt ons een adapter die veel op de onze lijkt AmericanToBritishChargerAdapter() van vroeger, waardoor we onze AmericanCharger, die onbruikbaar was met ons British Device, konden omzetten in iets bruikbaars, vergelijkbaar met een stroomadapter in het echte leven.

In dit scenario zou de adapter onze reeks contacten en onze weergave gebruiken en van daaruit ViewHolders genereren die de RecyclerView bereid is te accepteren.

De RecyclerView biedt een interface die we kunnen uitbreiden om onze Adapter te creëren via de klasse RecyclerView.Adapter. In deze adapter bevindt zich een manier om de ViewHolder-klasse te maken waarmee de RecyclerView wil werken. Wat we dus hebben is dezelfde situatie als voorheen, maar met één extra ding: de adapter.

We hebben een reeks contacten, een weergave om één contactpersoon OneContactView weer te geven. Een RecyclerView is een lijst met Views die recyclingdiensten leveren, maar alleen ViewHolders willen aannemen

Maar in dit scenario hebben we nu de klasse RecyclerView.Adapter, die een methode heeft om ViewHolders binnenin te maken.

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

De RecyclerView.ViewHolder is een abstracte klasse die onze View als argument neemt en deze omzet naar een ViewHolder.

Het maakt gebruik van het wrapperpatroon dat wordt gebruikt om de mogelijkheden van klassen uit te breiden.

Grondwerk #2 – Wikkelpatroon

We zullen een eenvoudig voorbeeld gebruiken om te demonstreren 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. Als we per ongeluk een methode wilden toevoegen om de spraak te maken, maar de bibliotheekauteur was niet leuk, konden we nog steeds een manier vinden. Wat we nodig hebben is een verpakking voor onze Dierenklas. We zouden dit doen door het Dier als constructor voor onze klas te gebruiken

 class SpeechPoweredAnimalByWrapper(var myAnimal:Animal){

    fun sound(){
        myAnimal.sound()
    }

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

Nu kunnen we een dierinstantie doorgeven aan de SpeechPoweredAnimalByWrapper. Als u de methode sound() erop aanroept, wordt de doorgegeven methode Animal Sound() aangeroepen. We hebben ook een extra speak()-methode, die geldt 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 behulp van dit patroon kunnen we lessen volgen en functionaliteit toevoegen. Het enige dat we nodig hebben, is een klasse-instantie en nieuwe methoden die zijn gedefinieerd door onze inpakklasse.

In ons geval hierboven hebben we een concrete klasse gebruikt. Het is ook mogelijk om hetzelfde in een Abstract-klasse te implementeren. We zouden dit moeten doen door de klasse SpeechPoweredAnimalByWrapper te wijzigen in abstract en we zijn klaar. We zullen de klassenaam veranderen in iets korters om het leesbaarder te maken.

abstract class SpeechPowered(var myAnimal:Animal){

    fun sound(){
        myAnimal.sound()
    }

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

Het is hetzelfde als voorheen, maar het zou iets anders betekenen. In een normale klasse kunnen we een instantie van een klasse hebben op dezelfde manier waarop we cat1 en dog1 hebben gemaakt. Abstracte klassen zijn echter niet bedoeld om te worden geïnstantieerd, maar zijn bedoeld om andere klassen uit te breiden. Dus hoe zouden we de nieuwe abstracte klasse SpeechPowered(var myAnimal:Animal) gebruiken. We kunnen het gebruiken door nieuwe klassen te maken die het uitbreiden en op zijn beurt de functionaliteit ervan verkrijgen.

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 wordt gebruikt in de ViewHolder. De klasse RecyclerView.ViewHolder is een abstracte klasse die functionaliteit aan de weergave toevoegt, net zoals we de spreekmethode aan de dieren hebben toegevoegd. De toegevoegde functionaliteit zorgt ervoor dat het werkt als het om RecyclerView gaat.

Dit is hoe we een OneContactViewHolder zouden maken 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 Contacts-array kunnen verbinden met de ContactsView met de RecyclerView

Een weergave toevoegen

De ViewGroup tekent de ViewGroup niet automatisch opnieuw, maar volgt een bepaald schema. Het kan op uw apparaat het geval zijn dat het elke 10 ms of 100 ms opnieuw wordt getekend, of als we een absurd getal kiezen, bijvoorbeeld 1 minuut, wanneer we een weergave aan een ViewGroup toevoegen, ziet u de wijzigingen na 1 minuut later wanneer de ViewGroup “vernieuwt.”

RecyclerView.Recycler

Grondwerk #3. Caching

Een van de beste voorbeelden van waar we regelmatig vernieuwingen uitvoeren, is in de browser. Laten we ons bijvoorbeeld voorstellen dat de site die we bezoeken statisch is en de inhoud niet dynamisch verzendt. We zouden dan moeten blijven vernieuwen om de veranderingen te zien.

Laten we ons voor dit voorbeeld voorstellen dat de site in kwestie Twitter is. We zouden een reeks statische tweets hebben en de enige manier waarop we nieuwe Tweets te zien zouden kunnen krijgen, is door op de knop Vernieuwen te klikken om de inhoud opnieuw op te halen.

Het hele scherm opnieuw schilderen is uiteraard een kostbare zaak. Als we ons zouden voorstellen dat dit het geval zou zijn, hadden we een beperkte bandbreedte bij onze telefoonprovider. En onze tweetlijst bevatte veel afbeeldingen en video's, het zou kostbaar zijn om de hele 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 de mogelijkheid heeft om de Tweets te zeggen die er al zijn. Daarom downloadt het niet alles opnieuw en haalt het alleen de nieuwe Tweets op die het heeft en controleert het ook of een Tweet die lokaal is opgeslagen er niet meer is, zodat het deze lokaal kan verwijderen. Wat we beschrijven heet Caching.

De informatie die we naar de website sturen over de inhoud die we hebben, worden metagegevens genoemd. Dus in werkelijkheid zeggen we niet alleen “we willen uw site laden”, we zeggen ook “we willen uw site laden, en hier is een deel van de inhoud die we al hadden opgeslagen van de laatste keer dat we deze laadden. Gebruik deze alstublieft om Stuur ons alleen wat er niet in zit, dus we gebruiken niet veel bandbreedte omdat we niet veel bronnen hebben.”

Layout-oproepen – De tweetlijst moet gek zijn

Een voorbeeld van een lay-outaanroep is scrollToPosition

Dit is een veelvoorkomend voorbeeld dat aanwezig is in zaken als chat-apps. Als iemand in een chatthread op een chatballon van eerder reageert, bevatten sommige chat-apps het antwoord en een link naar de chatballon, die u na het klikken naar de plaats van uw oorspronkelijke bericht brengt.

In dit geval roepen we deze methode aan voordat we een LayoutManager hebben toegevoegd aan onze RecyclerView en voordat we een RecyclerView.Adapter hebben, wordt de scrollToPosition(n:Int) eenvoudigweg genegeerd.

Communicatie tussen RecyclerView-componenten

Grondwerk #4. Terugbelgesprekken

De RecyclerView heeft tijdens zijn werk veel bewegende delen. Het heeft te maken met de LayoutManager die ons vertelt hoe we de weergaven moeten organiseren, lineair of in een raster. Het heeft te maken met een adapter die ervoor zorgt dat onze items contactList worden omgezet in Views OneContactView en vervolgens in ViewHolders OneContactViewHolder, zodat de RecyclerView bereid is te werken binnen de methoden die het ons biedt.

De grondstof voor de RecyclerView zijn onze Views, bijvoorbeeld OneContactView en 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 basisscenario waarin we een statische reeks van 1000 contacten zouden hebben die we aan de gebruiker willen laten zien, is gemakkelijk te begrijpen.

De RecyclerView-machines gaan pas echt leven als de lijst niet langer statisch is.

Bij een dynamische lijst moeten we bedenken wat er met de weergave op het scherm gebeurt als we een item aan de lijst toevoegen of uit de lijst verwijderen.

RecyclerView.LayoutManager

Afgezien van het beslissen hoe onze standpunten lineair of in een raster moeten worden opgemaakt. De LayoutManager doet veel werk onder de motorkap om de Recycler te helpen weten wanneer hij moet recyclen.

Het is verantwoordelijk voor het bijhouden van de weergaven die momenteel zichtbaar zijn op het scherm en voor het communiceren van deze informatie naar het recyclingmechanisme. Terwijl een gebruiker naar beneden scrolt, is de Layout Manager verantwoordelijk voor het informeren van het Recycling-systeem over de weergaven die bovenaan onscherp zijn, zodat ze opnieuw kunnen worden gebruikt in plaats van dat ze daar blijven en geheugen verbruiken, of in plaats van ze te vernietigen en opnieuw te moeten creëren. nieuwe.

Wat dit betekent is dat de LayoutManager moet bijhouden waar de gebruiker zich bevindt terwijl hij door onze lijst bladert. Dit wordt gedaan door een lijst met posities te hebben die op de indexbasis staan, dat wil zeggen dat het eerste item moet beginnen vanaf 0 en moet worden verhoogd om overeen te komen met het aantal items in onze lijst.

Als we in het begin 10 items op onze lijst van bijvoorbeeld 100 kunnen bekijken, is de LayoutManager zich ervan bewust dat deze in focus weergave-0 helemaal tot aan Weergave-9 heeft. Terwijl we scrollen, kan de LayoutManager de weergaven berekenen die eruit komen. van focus.

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

Dit zou een gelukkig geval zijn als de lijst die we hebben statisch is, maar een van de meest voorkomende gebruiksscenario's van het gebruik van een RecyclerView is met dynamische lijsten waar gegevens afkomstig kunnen zijn van een online eindpunt of misschien zelfs van een sensor. Er worden niet alleen gegevens toegevoegd, maar gegevens op onze lijst worden soms ook verwijderd of bijgewerkt.

De dynamische status van onze gegevens kan het erg moeilijk maken om over de LayoutManager te redeneren. Om deze reden houdt de LayoutManager een eigen lijst bij over de artikelen en posities, die los staat van de Lijst die de component Recycling gebruikt. Dit zorgt ervoor dat het zijn lay-outtaak correct uitvoert.

Tegelijkertijd wil de LayoutManager van RecyclerView de gegevens die hij heeft niet verkeerd voorstellen. Om correct te kunnen werken, moet de LayoutManager syncsynchroniseert met de RecyclerView.Adapter met bepaalde tussenpozen (60 ms) en deelt informatie over onze lijstitems. dwz items toegevoegd, bijgewerkt, verwijderd, verplaatst van de ene positie naar de andere). Wanneer de LayoutManager deze informatie ontvangt, reorganiseert hij de inhoud op het scherm zodat deze overeenkomt met de wijzigingen wanneer dat nodig is.

Veel van de kernactiviteiten die met RecylerView te maken hebben, draaien rond de communicatie tussen de RecyclerView.LayoutManager en RecyclerView.Adapter, waarin onze lijsten met soms statische of soms dynamische gegevens worden opgeslagen.

Meer nog, RecyclerView biedt ons methoden die we kunnen gebruiken om te luisteren naar gebeurtenissen zoals onBindViewHolder wanneer onze RecyclerView.Adapter inhoud uit onze lijst bindt, bijvoorbeeld een contactpersoon aan een ViewHolder, zodat deze nu gewend raakt aan het weergeven van de informatie op het scherm.

Een andere is onCreateViewHolder, die ons vertelt wanneer de RecyclerView. De adapter een gewone weergave zoals OneContactView gebruikt en deze omzet in een ViewHolder-item waarmee de RecyclerView kan werken. Vanuit onze ViewHolder voor gebruik door de RecyclerView. onViewVrijstaand

Afgezien van de kernmechanismen die recycling mogelijk maken. De RecyclerView biedt manieren om gedrag aan te passen zonder dat dit gevolgen heeft voor Recycling.

Hergebruik van weergaven maakt het moeilijk om veelvoorkomende dingen te doen die we gewend zijn te doen met statische weergaven, zoals reageren op onClick-gebeurtenissen.

Omdat we ons ervan bewust zijn dat de RecyclerView.LayoutManager die de weergaven aan de gebruiker presenteert, kan tijdelijk een andere lijst met items hebben dan de RecyclerView.Adapter met de lijst die we in een database hebben opgeslagen of vanuit een bron hebben gestreamd. Het rechtstreeks op Views plaatsen van OnClick-gebeurtenissen kan tot onverwacht gedrag leiden, zoals het verwijderen van de verkeerde contactpersoon of het wijzigen ervan.

Gradle

Als we RecyclerView willen gebruiken, moeten we het als afhankelijkheid toevoegen aan ons build .gradle-bestand.

In het vervolgwing we hebben bijvoorbeeld de implementatie “androidx.recyclerview:recyclerview:1.1.0” gebruikt, wat de meest recente versie is volgens dit artikel.

Nadat we de afhankelijkheid aan ons Gradle-bestand hebben toegevoegd, worden we door Android Studio gevraagd om dit te doen Syncde veranderingen honiseren,

Dit is hoe ons Gradle-bestand eruit zal zien na het toevoegen van een RecyclerView in 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"
}

Momenteel beschikken wij slechts over één layoutbestand. We beginnen met een eenvoudig voorbeeld waarbij we RecyclerView gebruiken om een ​​lijst met fruitnamen op het scherm weer te geven.

Lijst van items

We navigeren naar ons MainActivity-bestand en maken een array met fruitnamen erin, vlak vóór 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 zal zijn om deze lijst op het scherm te presenteren met behulp van een RecyclerView.

Om dit te doen, navigeren we naar de lay-outmap die onze lay-outs bevat en creëren we een weergave die verantwoordelijk is voor de weergavewing één vrucht.

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 TextView hierboven hebben we een id-veld toegevoegd dat zal worden gebruikt voor het identificeren van een weergave.

Het wordt niet standaard gegenereerd. We hebben onze TextView de id fruitName die overeenkomt met de gegevens, die eraan gebonden zijn.

De RecyclerView toevoegen aan de hoofdlay-out

In dezelfde activiteit is er het lay-outbestand main_layout.xml dat standaard voor ons is gegenereerd.

Als we voor een leeg project kiezen. Het zal een XML met een ConstraintLayout en binnenin zal een TextView zijn met "Hallo" -tekst.

We verwijderen alle inhoud en laten de lay-out alleen de RecyclerView bevatten, zoals hieronder:

<?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 voor de RecyclerView, dat we zullen gebruiken om ernaar te verwijzen in onze code.

 android:id="@+id/fruitRecyclerView"

We zullen dan terug navigeren naar ons MainActivity-bestand. Met behulp van de ID's die we hebben gemaakt, kunnen we verwijzen naar de weergaven die we zojuist hebben gemaakt.

We beginnen met het verwijzen naar de RecyclerView met behulp van de findViewById()-methode van Android. We zullen dit doen 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 zullen we een RecyclerView.ViewHolder creëren die verantwoordelijk is voor het omzetten van onze View naar een ViewHolder, die de RecyclerView gebruikt om onze items weer te geven.

We zullen dit direct na onze leuke onCreate() -methode doen.

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 gaan we een FruitArrayAdapter-klasse maken die de klasse RecyclerView.Adapter uitbreidt.

De FruitArrayAdapter die we maken, is verantwoordelijk voor het volgendewing.

Er worden fruitnamen uit de fruitarray overgenomen. Het zal een ViewHolder creëren met behulp van onze view one_fruit_view.xml. Vervolgens zal het fruit aan een ViewHolder worden gebonden, en de inhoud dynamisch worden gebonden aan de View die we one_fruit_view.xml 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 zal rode kronkels toevoegen aan onze FruitArrayAdapter, wat ons vertelt dat we een methode moeten implementeren die de RecyclerView kan gebruiken om onze array te verbinden met een ViewHolder die de RecyclerView kan gebruiken.

   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 gemakkelijkste deel van de gegenereerde code: de getItemCount() -methode. We weten hoe we het aantal items in onze array kunnen krijgen door de methode array.length 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-override fun onCreateViewHolder.

Dit is waar de RecyclerView ons vraagt ​​om te helpen bij het bouwen van een FruitHolder ervoor.

Als we ons herinneren dat dit is hoe onze FruitViewHolder-klasse eruit zag:

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

Het vereist onze fruitView die we hebben gemaakt als een xml-bestand one_fruit_view.xml

We kunnen als volgt een verwijzing naar deze XML maken en deze naar een weergave 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 bit is de overschrijving

fun onBindViewHolder(holder: FruitViewHolder, position: Int)

De RecyclerView.Adapter vraagt ​​met een positie-geheel getal, dat we zullen 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 halen, kunnen binden aan de weergave die in de weergavehouder wordt vastgehouden.

De weergave die zich in een ViewHoder bevindt, is toegankelijk via het veld ViewHolder.itemView. Zodra we de weergave hebben, kunnen we de id fruitName gebruiken die we eerder hebben gemaakt 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 compleet 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)
        }
    }

Eindelijk zijn we klaar om de resterende delen van onze RecyclerView aan te sluiten. Die een LayoutManager maken, die de RecyclerView vertelt hoe de inhoud van de lijst moet worden weergegeven. Of het nu lineair moet worden weergegeven met LinearLayoutManager of in een raster met 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 instantie fruitListAdapter gemaakt en deze de reeks fruitnamen gegeven.

En eigenlijk zijn we allemaal klaar.

Het volledige 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