Android RecyclerView: Hva er, lær med enkle eksempler

Hva er RecyclerView i Android?

Ocuco RecyclerView er en widget som er mer fleksibel og avansert versjon av GridView og ListView. Det er en beholder for å vise store datasett som kan rulles effektivt ved å opprettholde begrenset antall visninger. Du kan bruke RecyclerView-widgeten når du har datasamlinger hvis elementer endres ved kjøring, avhenger av nettverkshendelse eller brukerhandling.

Visninger

Ocuco Android plattformen bruker klassene View og ViewGroup til å tegne elementer på skjermen. Disse klassene er abstrakte og utvides til forskjellige implementeringer for å passe til en brukssak. TextView har for eksempel et enkelt formål å vise tekstinnhold på skjermen. EditText strekker seg fra den samme View-klassen og legger til mer funksjonalitet for å gjøre det mulig for brukeren å legge inn data.

Det er mulig å lage våre egne tilpassede visninger for å kunne oppnå mer fleksibilitet ved utvikling av brukergrensesnitt. View-klassen gir metoder vi kan overstyre for å tegne på skjermen og et middel til å sende inn parametere som bredde, høyde og våre egne tilpassede attributter vi ønsker å legge til i View for å få den til å oppføre seg slik vi ønsker.

Vis grupper

ViewGroup-klassen er en slags View, men i motsetning til den enkle View-klassen hvis ansvar bare er å vise, gir ViewGroup oss muligheten til å sette flere visninger i en visning, som vi kan referere til som helhet. I dette tilfellet kalles visningen som er opprettet på toppnivået som vi legger til andre enkle visninger (vi kan også legge til visningsgrupper) for "forelderen", og visningene som legges til er "barn".

Vi kan avbilde en View som en Array og en ViewGroup som en Array of Array. Gitt at en Array of Arrays er en Array i seg selv, kan vi se hvordan en ViewGroup kan behandles som en View.

var arr1 = [1,2,3] //imagine a simple View as an Array
//we can imagine this as a NumberTextView which doesn't really exist
//but we could imagine there's one that makes it easy to use numbers
var arr2 = ["a","b","c"] // We can imagine this as another simple view

var nestedArr = [arr1,arr2] //in our anology, we can now group views 
//together and the structure that would hold that would be what we call the ViewGroup

ViewGroup gjør oss også i stand til å definere hvordan barna er organisert inne i visningen, for eksempel legges våre vertikalt eller horisontalt. Vi kan ha forskjellige regler for interaksjon inne i visningen. For eksempel bør TextViews som følger hverandre ha en avstand på 12 dp, mens ImageViews etterfulgt av TextView skal ha en avstand på 5 dp.

Dette ville vært tilfelle hvis vi utviklet vår egen ViewGroup fra bunnen av. For å gjøre disse konfigurasjonene enklere, Android gir en klasse kalt LayoutParams, som vi kan bruke til å legge inn disse konfigurasjonene.

Android dokumentasjon gir noen standardparametere vi vil implementere når vi konfigurerer vår egen ViewGroup. Noen vanlige parametere er de som gjelder bredde, høyde og margin. Som standard har disse konfigurasjonene strukturen android:layout_height for høyde, for eksempel android:layout_width. Når du oppretter ViewGroup, kan du i denne forbindelse opprette LayoutParams som er spesifikke for måten du ønsker at ViewGroup skal oppføre seg på.

Android leveres med standard visninger og visningsgrupper vi kan bruke til å gjøre mange av de vanlige oppgavene vi trenger. Et eksempel vi har nevnt er en TextView. Det er en enkel visning som kommer med konfigurerbare aspekter som høyde, bredde, tekststørrelse og lignende. Vi har en ImageView for å vise bilder og EditText som vi hadde nevnt, blant mange andre. Android har også tilpassede visningsgrupper som vi kan legge til visningene våre i og få forventet oppførsel.

Lineær oppsett

LinearLayout lar oss legge til Vis-elementer i den. LinearLayout er en orienteringsattributt som dikterer hvordan den skal legges ut på skjermen. Den har også LinearLayout.LayoutParams som dikterer reglene for visningene inne, for eksempel vil attributtet android:center_horizontal sentrere visningene langs den horisontale aksen, mens `android:center_vertical vil sentrere innholdet i visningen langs den vertikale aksen.

Her er noen bilder for å forstå sentrering. Vi vil ta dette for å være en enkel TextView innenfor 200px x 200px plass, sentreringsattributter vil få det til å oppføre seg som følger.

android:center_horizontal

Horisontalt sentrert innhold
Horisontalt sentrert innhold

android:center_vertical

vertikalt sentrert innhold
vertikalt sentrert innhold

android:senter

Sentrert innhold
Sentrert innhold

Kjernekomponenter i RecyclerView

Kjernekomponenter i RecyclerView
Kjernekomponenter i RecyclerView

Følgende er de viktige komponentene i RecyclerView:

RecyclerView.Adapter

Grunnarbeid #1 – Adaptermønster

En adapter er en enhet som transformerer attributter til systemet eller enheten til de til en ellers inkompatibel enhet eller system. Noen av dem endrer signal- eller kraftattributter, mens andre ganske enkelt tilpasser den fysiske formen til en kontakt til en annen.

Et enkelt eksempel vi finner i det virkelige liv for å forklare en adapter er når vi trenger å koble sammen enheter, men de har tilkoblingsporter som ikke matcher hverandre. Dette kan være tilfelle når du besøker et annet land der de bruker forskjellige typer stikkontakter. Hvis du har med deg telefonen eller bærbar lader, ville det være umulig å koble den til strømuttakene. Du vil imidlertid ikke gi opp, men ganske enkelt få en adapter som kommer inn mellom strømuttaket og laderen og gjør det mulig å lade opp.

Dette er tilfellet i programmering når vi ønsker å koble to datastrukturer sammen for å utføre oppgaven, men standardportene deres ikke har en måte å kommunisere med hverandre på.

Vi bruker det enkle eksemplet på en enhet og en lader. Vi har to tilfeller av ladere. En amerikansk og en britisk

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

Deretter oppretter vi to enheter

class AmericanDevice() 
class BritishDevice()

Som et eksempel kan vi deretter lage noen forekomster av enhetene for å spille sammen.

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

Vi vil deretter introdusere konseptet med lading for begge enhetene ved å legge til en metode i enhetene kalt charge() .

Metoden tar inn den respektive laderen som inngang og lader basert på den.

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

I dette tilfellet, basert på vår analogi, ville vi av en eller annen grunn ha behov for å bruke en BritishCharger når vi bruker en AmericanDevice eller omvendt.

I programmeringsverdenen er dette vanligvis når man blander sammen biblioteker som tilbyr samme funksjonalitet (i vår sammenheng er vår delte funksjonalitet lading). Vi må finne en måte å aktivere dette på.

Hvis vi følger analogien, må vi gå til en elektronikkbutikk og kjøpe en adapter som vi vil gjøre det mulig for oss å lade AmericanDevices gitt BritishChargers. Fra et programmeringsperspektiv vil vi være de som vil være produsenten av adapteren.

Vi lager en adapter for den ene som samsvarer med det nøyaktige mønsteret vi trenger for å lage den andre. Vi implementerer det som en klasse som følger. Det trenger ikke nødvendigvis å være en klasse og kan være en funksjon som fremhever hva adaptermønsteret generelt gjør. Vi bruker en klasse ettersom den samsvarer med mest bruk på 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
    }
}

I programmeringsverdenen er forskjellen i stikkontaktene analog med forskjellen i metodene inne som brukes for lading. Laderne med forskjellige metoder vil gjøre det umulig å bruke laderne.

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

Å prøve å kalle charge()-metoden i myBritishDevice med americanChargerIFound ville ikke fungere ettersom AmericanDevice bare godtar en AmericanCharger

Så det er umulig å gjøre dette

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

I dette scenariet adapteren vi laget

AmericanToBritishChargerAdapter kan nå komme godt med. Vi kan bruke returnNewCharger()-metoden for å lage en ny BritishCharger, som vi kan bruke til å lade. Alt vi trenger er å lage en forekomst av adapteren vår og mate den med AmericanChargeren vi har, og den vil lage en BritishCharger vi kan bruke

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

Når vi har å gjøre med en ViewGroup ville vi ha Views plassert inne i den. Det er LayoutManager som skulle ha som oppgave å beskrive hvordan visningene er lagt ut på innsiden.

For sammenligningsformål, når vi jobber med Linearlayout ViewGroup, er brukssaken vi ønsker muligheten til å plassere elementene enten vertikalt eller horisontalt. Dette implementeres enkelt ved å legge til et orienteringsattributt, som forteller oss hvordan den lineære layouten vil bli plassert på skjermen. Dette kan vi gjøre ved å bruke android:orientation=VERTICAL|HORIZONTAL attributt.

Vi har også en annen ViewGroup kalt GridLayout, det er brukstilfellet når vi ønsker å plassere Views i en rektangulær Grid-struktur. Dette kan være av årsaker som å gjøre dataene vi presenterer for appbrukeren enkle å konsumere. Ved design muliggjør GridLayout konfigurasjoner for å hjelpe deg med å nå dette målet ved å ha konfigurasjoner der vi kan definere dimensjonene til rutenettet, for eksempel kan vi ha et 4×4 rutenett, 3 x 2 rutenett.

RecyclerView.ViewHolder

ViewHolder er en abstrakt klasse som vi også utvider fra RecyclerView. ViewHolder gir oss vanlige metoder for å hjelpe oss med å referere til en visning vi har plassert på RecyclerView selv etter at resirkuleringsmaskineriet i RecyclerView har endret ulike referanser vi ikke vet om.

Store lister

RecyclerViews brukes når vi ønsker å presentere et virkelig stort sett med visninger for brukeren, samtidig som vi ikke utmatter vår RAM på enheten vår for hver eneste forekomst av visningen som er opprettet.

Hvis vi skulle ta tilfellet med en kontaktliste, ville vi ha en generell ide om hvordan en kontakt ville se ut i listen. Det vi da ville gjort er å lage et maloppsett – som faktisk er en visning – med spor der ulike data fra kontaktlisten vår vil fylle. Følgende er en pseudokode som forklarer hele formålet:

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

Vi ville da ha en kontaktliste av denne art

 <ContactList>
</ContactList>

Hvis det er tilfelle, hardkodet vi innholdet, vi ville ikke ha en programmatisk måte å legge til nytt innhold på listen uten å omskrive appen. Heldigvis for oss. Å legge til en visning i en visningsgruppe støttes av en addView(view:View) metoden.

Selv om det er tilfelle, er det ikke hvordan RecyclerView får barnevisninger lagt til den.

I vårt brukstilfelle ville vi ha en lang liste med kontakter. For hver kontakt i listen må vi opprette OneContactView og fylle ut dataene i visningen for å matche feltene i kontaktklassen vår. Så når vi har visningen, må vi legge den til RecyclerView for å vise listen.

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)

Vi har en rekke kontakter som heter OneContactView. Den inneholder spor for å ta innhold fra kontaktklassen og vise dem. I RecyclerView må vi legge til visninger i den slik at den kan hjelpe oss med resirkuleringsevnen.

RecyclerView lar oss egentlig ikke legge til visning, men lar oss legge til en ViewHolder. Så i dette scenariet har vi to ting vi ønsker å koble til, men som ikke stemmer overens. Det er her adapteren vår kommer inn. RecyclerView gir oss en adapter som ligner på vår AmericanToBritishChargerAdapter() fra tidligere som gjorde det mulig for oss å konvertere vår AmericanCharger som var ubrukelig med vår britiske enhet til noe brukbart, i likhet med strømadapter i det virkelige liv.

I dette scenariet vil adapteren ta vårt utvalg av kontakter og visningen vår, og derfra generere ViewHolders som RecyclerView er villig til å akseptere.

RecyclerView gir et grensesnitt vi kan utvide for å lage vår Adapter gjennom RecyclerView.Adapter-klassen. Inne i denne adapteren er en måte å lage ViewHolder-klassen på som RecyclerView ønsker å jobbe med. Så det vi har er den samme situasjonen som før, men med en ekstra ting, det er adapteren.

Vi har en rekke kontakter, en visning for å vise én kontakt OneContactView. En RecyclerView er en liste over visninger som tilbyr resirkuleringstjenester, men som bare er villig til å ta på seg ViewHolders

Men i dette scenariet har vi nå RecyclerView.Adapter-klassen, som har en metode for å lage ViewHolders inne.

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

RecyclerView.ViewHolder er en abstrakt klasse som tar vårt syn som et argument og konverterer det til en ViewHolder.

Den bruker innpakningsmønsteret som brukes til å utvide klassenes evner.

Grunnarbeid #2 – Innpakningsmønster

Vi skal bruke et enkelt eksempel for å demonstrere hvordan vi kan få dyr til å snakke.

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

I eksemplet ovenfor har vi to dyr. Tilfeldigvis ønsket vi å legge til en metode for å få tale, men bibliotekforfatteren var ikke morsom, vi kunne fortsatt finne en måte. Det vi trenger er en innpakning for dyreklassen vår. Dette ville vi gjort ved å ta inn dyret som konstruktør for klassen vår

 class SpeechPoweredAnimalByWrapper(var myAnimal:Animal){

    fun sound(){
        myAnimal.sound()
    }

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

Nå kan vi overføre til SpeechPoweredAnimalByWrapper i en dyreforekomst. Å kalle sound()-metoden på den vil kalle pass in animal sound()-metoden. Vi har også en ekstra speak()-metode, som teller som ny funksjonalitet vi legger til dyrene som sendes inn. Vi kan bruke den på følgende måte:

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"

Ved å bruke dette mønsteret kan vi ta klasser og legge til funksjonalitet. Alt vi trenger er å bestå er en klasseforekomst og nye metoder definert av innpakningsklassen vår.

I vårt tilfelle ovenfor brukte vi en betongklasse. Det er også mulig å implementere det samme i en abstrakt klasse. Vi må gjøre er å legge til å endre SpeechPoweredAnimalByWrapper-klassen til abstrakt, og vi er ferdige. Vi endrer klassenavnet til noe kortere for å gjøre det mer lesbart.

abstract class SpeechPowered(var myAnimal:Animal){

    fun sound(){
        myAnimal.sound()
    }

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

Det er det samme som før, men det ville bety noe annet. I en vanlig klasse kan vi ha en forekomst av en klasse på samme måte som vi opprettet cat1 og dog1. Abstrakte klasser er imidlertid ikke ment å bli instansiert, men er ment å utvide andre klasser. Så hvordan ville vi bruke den nye abstrakte klassen SpeechPowered(var myAnimal:Animal). Vi kan bruke det ved å lage nye klasser som vil utvide det og i sin tur få dets funksjonalitet.

I vårt eksempel vil vi lage en klasse SpeechPoweredAnimal som utvider klassen

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

Dette er det samme mønsteret som brukes i ViewHolder. Klassen RecyclerView.ViewHolder er en abstrakt klasse som legger til funksjonalitet til View, omtrent som vi har lagt til talemetoden til dyrene. Den ekstra funksjonaliteten er det som får den til å fungere når du arbeider med RecyclerView.

Slik vil vi lage en OneContactViewHolder fra 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 har en adapter som lar oss koble kontaktgruppen vår til ContactsView med RecyclerView

Legge til en visning

ViewGroup tegner ikke ViewGroup på nytt automatisk, men følger en bestemt tidsplan. Det kan være tilfelle på enheten din at den tegnes på nytt hver 10 ms eller 100 ms, eller hvis vi velger et absurd tall, si 1 minutt når vi legger til en visning i en visningsgruppe, vil du se endringene 1 minutt senere når visningsgruppen «oppdateres».

RecyclerView.Recycler

Grunnarbeid #3. Buffer

Et av de beste eksemplene på hvor vi jevnlig gjør oppdateringer er i nettleseren. La oss for eksempel forestille oss at nettstedet vi besøker er statisk og ikke sender innhold dynamisk, vi må fortsette å oppdatere for å se endringene.

For dette eksemplet, la oss forestille oss at det aktuelle nettstedet er twitter. Vi ville ha en serie statiske tweets oppført, og den eneste måten vi kunne se nye tweets på ville være ved å klikke på oppdateringsknappen for å hente innholdet på nytt.

Å male hele skjermen på nytt er åpenbart en kostbar ting. Hvis vi skulle forestille oss om det var tilfelle, hadde vi begrenset båndbredde hos telefonleverandøren vår. Og tweetlisten vår hadde mange bilder og videoer, det ville være kostbart å laste ned alt innholdet på siden på nytt ved hver oppdatering.

Vi trenger en måte å lagre de allerede lastede tweetene på og sikre at vår neste forespørsel har en evne til å si de tweetene den allerede har. Derfor laster den ikke ned alt på nytt og får bare de nye tweetene den har, og sjekk også om noen Tweet som var lagret lokalt ikke lenger er der, slik at den kan slette den lokalt. Det vi beskriver kalles Caching.

Informasjonen vi sender til nettstedet om innholdet vi har, kalles metadata. Så i virkeligheten sier vi ikke bare "vi vil laste inn nettstedet ditt", vi sier "vi vil laste inn nettstedet ditt, og her er noe av innholdet vi allerede hadde lagret fra forrige gang vi lastet inn, vennligst bruk det til å send oss ​​bare det som ikke er der, så vi bruker ikke mye båndbredde siden vi ikke har mange ressurser.»

Layoutsamtaler – Tweetlisten må være gal

Et eksempel på et layout-kall er scrollToPosition

Dette er et vanlig eksempel som finnes i ting som chatteapper. Hvis noen i en chattråd svarer på en chat-boble fra tidligere, inkluderer noen chatte-apper svaret og en kobling til chat-boblen, som ved å klikke navigerer deg til den opprinnelige meldingen din.

I tilfelle kaller vi denne metoden før vi har lagt til en LayoutManager i vår RecyclerView og før vi har en RecyclerView.Adapter, blir scrollToPosition(n:Int) ganske enkelt ignorert .

Kommunikasjon mellom RecyclerView-komponenter

Grunnarbeid #4. Tilbakeringinger

RecyclerView har, i arbeidet sitt, mange bevegelige deler. Den må håndtere LayoutManager som forteller oss hvordan vi organiserer visningene, enten lineært eller i et rutenett. Den må håndtere en adapter som gjør jobben med å konvertere varekontaktlisten vår til Views OneContactView og deretter til ViewHolders OneContactViewHolder som RecyclerView er villig til å jobbe innenfor metodene den gir oss.

Råmaterialet for RecyclerView er våre Views f.eks OneContactView og datakilde.

contactList:Array<Contact>

Vi har brukt et enkelt scenario som utgangspunkt for å få en følelse av hva RecyclerView prøver å oppnå.

Et grunnleggende tilfelle av når vi vil ha en statisk gruppe med 1000 kontakter vi ønsker å vise brukeren er lett å forstå.

RecyclerView-maskineriet begynner virkelig å ta liv når listen ikke lenger er statisk.

Med en dynamisk liste må vi tenke på hva som skjer med visningen på skjermen når vi legger til et element i listen eller fjerner et element fra listen.

RecyclerView.LayoutManager

Bortsett fra å bestemme hvordan våre synspunkter skal legges ut enten lineært eller i et rutenett. LayoutManageren gjør mye arbeid under panseret for å hjelpe resirkulatoren å vite når den skal gjenvinnes.

Det er ansvarlig for å holde oversikt over visningene som for øyeblikket er synlige på skjermen og kommunisere til denne informasjonen til resirkuleringsmekanismen. Når en bruker ruller nedover, er layoutansvarlig ansvarlig for å informere resirkuleringssystemet om visningene som går ut av fokus på toppen, slik at de kan gjenbrukes i stedet for at de forblir der og forbruker minne eller i stedet for å ødelegge dem og må lage nye.

Hva dette betyr er at LayoutManager må holde styr på hvor brukeren er mens de ruller i listen vår. Den gjør dette ved å ha en liste over posisjoner som er indeksbasis, dvs. det første elementet er å starte fra 0 og øke for å matche antall elementer i listen vår.

Hvis vi kan se 10 elementer på listen vår på for eksempel 100, i begynnelsen, er LayoutManager klar over at den har i fokus view-0 hele veien til View-9 Når vi ruller, er LayoutManager i stand til å beregne visningene som kommer ut av fokus.

LayoutManager er i stand til å frigi disse visningene til resirkuleringsmekanismen slik at de kan gjenbrukes (nye data kan bindes til dem, f.eks. kan en visnings kontaktdata fjernes og nye kontaktdata fra neste segment kan erstatte plassholderne).

Dette ville være et godt tilfelle hvis listen vi har er statisk, men en av de vanligste brukssakene for bruk av en RecyclerView er med dynamiske lister der data kan komme fra et online endepunkt eller kanskje fra en sensor. Ikke bare blir data lagt til, men data på listen vår blir noen ganger fjernet eller oppdatert.

Den dynamiske tilstanden til dataene våre kan gjøre det svært vanskelig å resonnere om LayoutManager. Av denne grunn opprettholder LayoutManager en egen liste over elementene og posisjonene, som er atskilt fra listen som Resirkuleringskomponenten bruker. Dette sikrer at den gjør layoutjobben riktig.

Samtidig ønsker ikke RecyclerViews LayoutManager å feilrepresentere dataene den har. For å fungere korrekt synkroniserer LayoutManager med RecyclerView.Adapter med gitte intervaller (60ms) og deler informasjon om våre listeelementer. dvs. elementer lagt til, oppdatert, fjernet, flyttet fra en posisjon til en annen). Når LayoutManageren mottar denne informasjonen, omorganiserer innholdet på skjermen for å matche endringene når det er nødvendig.

Mange av kjerneoperasjonene som omhandler RecylerView roterer rundt kommunikasjon mellom RecyclerView.LayoutManager og RecyclerView.Adapter som lagrer listene våre med noen ganger statiske eller noen ganger dynamiske data.

Dessuten presenterer RecyclerView oss metoder vi kan bruke for å lytte til hendelser som onBindViewHolder når vår RecyclerView.Adapter binder innhold fra listen vår, f.eks. en kontakt til en ViewHolder slik at den nå blir vant til å vise informasjonen på skjermen.

En annen er onCreateViewHolder, som forteller oss når RecyclerView. Adapteren tar en vanlig visning som OneContactView og konverterer den til et ViewHolder-element som RecyclerView kan fungere med. Fra vår ViewHolder for bruk av RecyclerView. onViewDetached

Bortsett fra kjernemekanismene som muliggjør resirkulering. RecyclerView gir måter å tilpasse atferd uten å påvirke resirkulering.

Gjenbruk av visninger gjør det vanskelig å gjøre vanlige ting vi er vant til å gjøre med statiske visninger, for eksempel å reagere på onClick-hendelser.

Ettersom vi er klar over at RecyclerView.LayoutManager som presenterer visningene for brukeren, kan for et øyeblikk ha en annen liste over elementer fra RecyclerView.Adapter som har listen som vi har lagret i en database eller strømmet inn fra en kilde. Å sette OnClick-hendelser direkte på Views kan føre til uventet oppførsel, for eksempel sletting av feil kontakt eller endring.

Gradle

Hvis vi ønsker å bruke RecyclerView, må vi legge det til som en avhengighet i vår build .gradle-fil.

I det følgende eksemplet har vi brukt implementeringen "androidx.recyclerview:recyclerview:1.1.0", som er den nyeste versjonen i henhold til denne artikkelen.

Etter å ha lagt til avhengigheten til vår Gradle fil, blir vi bedt om av Android Studio til Syncharmonisere endringene,

Slik er vår Gradle filen vil se ut etter å ha lagt til en RecyclerView i et tomt prosjekt med bare standardinnstillingene.

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

Vi har bare én layoutfil for øyeblikket. Vi starter med et enkelt eksempel der vi bruker en RecyclerView for å vise en liste over fruktnavn på skjermen.

Liste over varer

Vi navigerer til MainActivity-filen vår og lager en matrise med fruktnavn inni rett før onCreate()-metoden som ble generert under oppsett.

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

Vårt neste mål vil være å presentere denne listen på skjermen ved hjelp av en RecyclerView.

For å gjøre dette, navigerer vi til layoutkatalogen som inneholder layoutene våre og lager en visning som vil være ansvarlig for å vise én frukt.

Layout som skal brukes for hvert element i listen vår

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

I tekstvisningen ovenfor har vi lagt til et id-felt som skal brukes til å identifisere en visning.

Den genereres ikke som standard. Vi har vår TextView ID fruitName for å matche dataene, som vil være bundet til den.

Legger til RecyclerView til hovedoppsettet

I den samme aktiviteten er det main_layout.xml layoutfil som ble generert for oss som standard.

Hvis vi valgte et tomt prosjekt. Det vil ha generert en XML som inneholder en Constraint Layout og innsiden vil være en TextView med "Hei"-tekst.

Vi sletter alt innholdet og har oppsettet kun inneholde RecyclerView som nedenfor:

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

Vi har også lagt til et id-attributt for RecyclerView som vi vil bruke til å referere til det i koden vår.

 android:id="@+id/fruitRecyclerView"

Vi vil deretter navigere tilbake til MainActivity-filen vår. Ved å bruke ID-ene vi har opprettet, vil vi kunne referere til visningene vi nettopp har opprettet.

Vi vil starte med å referere til RecyclerView ved å bruke findViewById()-metoden levert av Android. Vi vil gjøre dette i vår onCreate()-metode.

Vår onCreate()-metode vil se ut som følger.

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

Opprett en ViewHolder

Deretter vil vi lage en RecyclerView.ViewHolder som er ansvarlig for å ta visningen vår og konvertere den til en ViewHolder, som RecyclerView bruker til å vise varene våre.

Vi vil gjøre dette rett etter vår morsomme onCreate()-metode.

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

Lag en RecyclerViewAdapter

Deretter vil vi lage en FruitArrayAdapter-klasse som utvider RecyclerView.Adapter-klassen.

FruitArrayAdapteren vi lager vil være ansvarlig for å gjøre følgende.

Den vil ta fruktnavn fra fruktarrayen. Den vil lage en ViewHolder ved å bruke vår visning one_fruit_view.xml Den vil deretter binde frukten til en ViewHolder, og dynamisk binde innholdet til visningen vi opprettet one_fruit_view.xml.

package com.guru99.learnrecycler

import androidx.appcompat.app.AppCompatActivity
import android.os.Bundle
import android.view.View
import androidx.recyclerview.widget.RecyclerView

class MainActivity : AppCompatActivity() {

    var fruitNames:Array<String> = arrayOf<String>("Banana", "Mango", "Passion fruit", "Orange", "Grape")

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

        var myFruitRecyclerView: RecyclerView = findViewById(R.id.fruitRecyclerView)
    }
    
    class FruitViewHolder(fruitView: View): RecyclerView.ViewHolder(fruitView)
    
    class FruitArrayAdapter(var fruitArray: Array<String>) : RecyclerView.Adapter<FruitViewHolder>()
}

Android Studio vil legge til røde kruseduller på vår FruitArrayAdapter, og forteller oss at vi må implementere en metode som RecyclerView kan bruke for å koble arrayen vår til en ViewHolder som RecyclerView kan bruke.

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

Vi starter med den enkleste biten fra den genererte koden er getItemCount()-metoden. Vi vet hvordan vi får antall elementer i arrayet vårt ved å kalle array.length-metoden.

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

Deretter implementerer vi metoden overstyre moro påCreateViewHolder.

Det er her RecyclerView ber oss hjelpe den med å konstruere en FruitHolder for den.

Hvis vi husker dette er hvordan FruitViewHolder-klassen vår så ut:

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

Det krever vår fruitView som vi opprettet som en xml-fil one_fruit_view.xml

Vi kan være i stand til å lage en referanse til denne xml-en og konvertere den til en visning som følger.

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

        }
    }

Den gjenværende biten er overstyringen

fun onBindViewHolder(holder: FruitViewHolder, position: Int)

RecyclerView.Adapter spør med et posisjonsheltall, som vi bruker for å hente et element fra listen vår. Den gir oss også en holder slik at vi kan binde gjenstanden vi får fra fruitArray til visningen som holdes inne i visningsholderen.

Visningen som holdes inne i en ViewHoder er tilgjengelig gjennom feltet ViewHolder.itemView. Når vi har fått visningen, kan vi bruke ID-en fruitName vi opprettet tidligere for å angi innholdet.

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

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

            var aFruitName = fruitArray.get(position)

            ourFruitTextView.setText(aFruitName)
        }

Med det er vår FruitArrayAdapter komplett og ser ut som følger.

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

Endelig er vi klare til å koble opp de resterende delene av vår RecyclerView. Som lager en LayoutManager, som vil fortelle RecyclerView hvordan den skal vise innholdet i listen. Om det skal vises på en lineær måte ved hjelp av LinearLayoutManager eller i et rutenett med GridLayoutManager eller StaggeredGridLayoutManager.

Opprett Layout Manager

Vi vil gå tilbake til onCreate-funksjonen vår og legge til 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
        
    }

Koble adapteren vår til gjenstander og sett den på 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
    }

Vi har også laget en fruitListAdapter-forekomst og matet den med en rekke fruktnavn.

Og i bunn og grunn er vi alle ferdige.

Den komplette MainActivity.kt-filen ser ut som følger.

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

Last ned prosjektet