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

⚡ Smart oppsummering

RecyclerView er et avansert Android widget som viser store, rullbare datasett effektivt ved å resirkulere et begrenset antall visninger. Den er avhengig av en adapter, en layoutbehandler og en visningsholder for å binde datasamlinger som endres under kjøretid.

  • 📱 Hva det er: RecyclerView er en fleksibel etterfølger til ListView og GridView for visning av store datasamlinger.
  • 🧱 Visninger og visningsgrupper: Android tegner grensesnittet ved hjelp av klassene View og ViewGroup, der LayoutParams kontrollerer størrelse og posisjon.
  • 🔄 Adapter: Adaptermønsteret konverterer en datamatrise til ViewHolders som RecyclerView kan akseptere.
  • 📐 LayoutManager: Layoutbehandleren arrangerer elementer lineært eller i et rutenett og signaliserer når visninger kan resirkuleres.
  • ♻️ Gjenvinning: Visninger utenfor skjermen brukes om igjen i stedet for å gjenskapes, noe som sparer minne og sørger for jevn rulling.
  • 🛠️ Byggetrinn: Legg til Gradle avhengighet, opprett et elementlayout, en ViewHolder, en adapter og en LayoutManager.

Android RecyclerView

Hva er RecyclerView i Android?

Ocuco RecyclerView er en mer fleksibel og avansert versjon av GridView og ListView. Det er en beholder for å vise store datasett som kan rulles effektivt ved å opprettholde et begrenset antall visninger. Du kan bruke RecyclerView-widgeten når du har datasamlinger der elementene endres under kjøretid avhengig av en nettverkshendelse eller brukerhandling.

Visninger

Ocuco Android Plattformen bruker klassene View og ViewGroup til å tegne elementer på skjermen. Disse klassene er abstract og utvides til forskjellige implementeringer for å passe til et brukstilfelle. TextView har for eksempel det enkle formålet å vise tekstinnhold på skjermen. EditText utvider seg fra den samme View-klassen og legger til mer funksjonalitet som lar brukeren legge inn data.

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

Vis grupper

ViewGroup-klassen er en type View, men i motsetning til den enkle View-klassen, som kun har som ansvar å vise, gir ViewGroup oss muligheten til å sette flere visninger inn i én visning, som vi kan referere til som en helhet. I dette tilfellet kalles View-en som opprettes på toppnivå, og som vi legger til andre enkle visninger i, for «forelderen», og visningene som legges til inni kalles «barn».

Vi kan forestille oss en visning som en array og en visningsgruppe som en array av arrayer. Gitt at en array av arrayer er en array i seg selv, kan vi se hvordan en visningsgruppe kan behandles som en visning.

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

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

ViewGroup lar oss også definere hvordan barna er organisert i visningen, for eksempel om de er plassert vertikalt eller horisontalt. Vi kan ha forskjellige regler for interaksjon i visningen. For eksempel bør tekstvisninger som følger etter hverandre ha en avstand på 12 dp, mens en bildevisning etterfulgt av en tekstvisning bør ha en avstand på 5 dp.

Dette ville vært tilfellet hvis vi utvikletping 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.

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

Android kommer med standardvisninger og visningsgrupper som vi kan bruke til å gjøre mange av de vanlige oppgavene vi trenger. Et eksempel vi har nevnt er en tekstvisning. Det er en enkel visning som kommer med konfigurerbare aspekter som høyde, bredde og tekststørrelse. Vi har også en bildevisning for å vise bilder og rediger tekst, som vi nevnte, 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 View-elementer i den. LinearLayout har et orientation-attributt som dikterer hvordan det skal legges ut på skjermen. Det har også LinearLayout.LayoutParams som dikterer reglene for visningene inni. 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 som hjelper deg å forstå sentrering. Vi tar dette for å være en enkel TextView i et område på 200 x 200 piksler. Sentreringsattributtene ville 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 egenskapene til ett system eller en enhet til egenskapene til en ellers inkompatibel enhet eller et system. Noen adaptere endrer signal- eller strømegenskaper, mens andre ganske enkelt tilpasser den fysiske formen til én kontakt til en annen.

Et enkelt eksempel fra virkeligheten som forklarer en adapter, er når vi trenger å koble enheter sammen, men de har tilkoblingsporter som ikke passer sammen. Dette kan være tilfellet når du besøker et annet land som bruker andre typer stikkontakter. Hvis du har med deg telefon- eller bærbarlader, vil det være umulig å koble den til stikkontaktene. Du vil imidlertid ikke gi opp, men bare skaffe deg en adapter som kommer mellom stikkontakten og laderen din og muliggjør lading.

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

Vi bruker det enkle eksemplet med en enhet og en lader. Vi har to typer ladere, en amerikansk og en britisk.

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

Deretter lager 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 og utfører lading 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å analogien vår, ville vi av en eller annen grunn trenge å bruke en britisk lader når vi bruker en amerikansk enhet, 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 lar oss lade amerikanske enheter gitt britiske ladere. Fra et programmeringsperspektiv vil vi være produsenten av adapteren.

Vi skal lage en adapter som samsvarer nøyaktig med det mønsteret vi trenger for å lage den andre. Vi skal implementere den 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 mesteparten av bruken 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 chargingPower:Int = theAmericanCharger.chargingPower / 2
           var newBritishCharger = new BritishCharger()
           newBritishCharger.chargingPower = theAmericanCharger.chargingPower/2

        return newBritishCharger
    }
}

I programmeringsverdenen er forskjellen i stikkontaktene analog med forskjellen i metodene som brukes for lading. Ladere med forskjellige metoder ville gjort det umulig å bruke laderne.

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

Å forsøke å kalle charge()-metoden på myBritishDevice med americanChargerIFound ville ikke fungere, ettersom AmericanDevice bare aksepterer en AmericanCharger. Så det er umulig å gjøre dette:

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

I dette scenariet kan adapteren vi opprettet, AmericanToBritishChargerAdapter, nå komme godt med. Vi kan bruke returnNewCharger()-metoden til å opprette en ny BritishCharger, som vi kan bruke til å lade. Alt vi trenger å gjøre er å opprette en instans av adapteren vår og mate den med AmericanCharger vi har, og det vil opprette 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(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

Når vi har med en visningsgruppe å gjøre, har vi visninger plassert inni den. LayoutManager har som oppgave å beskrive hvordan visningene er lagt ut inni.

Til sammenligning, når vi jobber med en LinearLayout ViewGroup, ønsker vi å kunne plassere elementene enten vertikalt eller horisontalt. Dette implementeres enkelt ved å legge til et orientation-attributt, som forteller oss hvordan LinearLayout skal plasseres på skjermen. Vi kan gjøre dette ved å bruke android:orientation=VERTICAL|HORIZONTAL attributt.

Vi har også en annen ViewGroup kalt GridLayout. Bruksområdet er når vi ønsker å plassere Views i en rektangulær rutenettstruktur. Dette kan være av grunner som å gjøre dataene vi presenterer for appbrukeren enkle å bruke. GridLayout er designet slik at konfigurasjoner hjelper deg med å nå dette målet ved å definere dimensjonene til rutenettet. For eksempel kan vi ha et 4×4-rutenett eller et 3×2-rutenett.

RecyclerView.ViewHolder

ViewHolder er en magemuskeltract-klassen som vi også utvider fra RecyclerView. ViewHolder gir oss vanlige metoder som hjelper oss med å referere til en visning vi har plassert på RecyclerView, selv etter at resirkuleringsmaskineriet i RecyclerView har endret diverse referanser vi ikke kjenner til.

Store lister

RecyclerViews brukes når vi ønsker å presentere et veldig stort sett med visninger for brukeren, uten å uttømme RAM på enheten vår for hver eneste forekomst av visningen som er opprettet.

Hvis vi tar tilfellet med en kontaktliste, ville vi ha en generell idé om hvordan én kontakt ville se ut i listen. Det vi da ville gjort er å lage en maloppsett, som egentlig er en visning, med spor der ulike data fra kontaktlisten vår vil fylles ut. Følgende er pseudokode som forklarer hele formålet:

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

Da ville vi ha en kontaktliste av denne typen:

<ContactList>
</ContactList>

Hvis vi skulle hardkode innholdet, ville vi ikke hatt en programmatisk måte å legge til nytt innhold i listen uten å omskrive appen. Heldigvis for oss støttes det å legge til en visning i en visningsgruppe av en addView(view:View) metode. Likevel er det ikke slik RecyclerView får barnevisninger lagt til.

I vårt brukstilfelle ville vi hatt en lang liste med kontakter. For hver kontakt i listen må vi opprette en OneContactView og fylle ut dataene i View slik at de samsvarer med feltene i Contact-klassen vår. Når vi har viewet, må vi legge det til i 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. OneContactView inneholder spor for å hente innhold fra Contact-klassen og vise det. I RecyclerView må vi legge til Views slik at den kan hjelpe oss med resirkuleringsmulighetene.

RecyclerView lar oss ikke egentlig legge til en visning, men den lar oss legge til en ViewHolder. Så i dette scenariet har vi to deler vi ønsker å koble sammen, men som ikke matcher. Det er her adapteren vår kommer inn i bildet. RecyclerView gir oss en adapter, omtrent som vår AmericanToBritishChargerAdapter() fra tidligere som gjorde det mulig for oss å konvertere vår AmericanCharger, som var ubrukelig med vår BritishDevice, til noe brukbart, i likhet med en strømadapter i virkeligheten.

I dette scenariet ville adapteren ta kontaktgruppen vår og visningen vår, og derfra generere ViewHolders som RecyclerView er villig til å godta.

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

Vi har en rekke kontakter, en visning for å vise én kontakt (OneContactView), og en RecyclerView, som er en liste over visninger som tilbyr resirkuleringstjenester, men som bare er villige til å ta imot ViewHolders. I dette scenariet har vi nå en RecyclerView.Adapter-klasse, som har en metode for å opprette ViewHolders inni.

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

RecyclerView.ViewHolder er en abstract-klasse som tar vårt View som et argument og konverterer det til en ViewHolder. Den bruker wrapper-mønsteret som brukes til å utvide klassenes funksjoner.

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. Hvis vi tilfeldigvis skulle ønske vi kunne legge til en metode for å få dem til å snakke, men bibliotekforfatteren ikke var morsom, kunne vi fortsatt finne en måte. Det vi ville trenge er en wrapper for Animal-klassen vår. Vi ville gjort dette ved å ta inn Animal som en konstruktør for klassen vår.

class SpeechPoweredAnimalByWrapper(var myAnimal:Animal){

    fun sound(){
        myAnimal.sound()
    }

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

Nå kan vi sende inn en dyreinstans til SpeechPoweredAnimalByWrapper. Å kalle sound()-metoden på den vil kalle den sendte dyre-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 som følger:

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 å sende en klasseinstans og nye metoder definert av wrap-mønsteret vårt.ping klasse.

I tilfellet ovenfor brukte vi en betongklasse. Det er også mulig å implementere den samme i en abs.tract-klassen. Vi må endre SpeechPoweredAnimalByWrapper-klassen til abstract, 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()
    }

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

Det er det samme som før, men det ville betydd noe annet. I en vanlig klasse kan vi ha en instans av en klasse på samme måte som vi opprettet cat1 og dog1. Abstract-klasser er imidlertid ikke ment å instansieres, men er ment for å utvide andre klasser. Så hvordan skulle vi bruke den nye SpeechPowered(var myAnimal:Animal) abstract-klassen? Vi kan bruke den ved å opprette nye klasser som vil utvide den og dermed få dens funksjonalitet.

I vårt eksempel skal vi opprette 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 abstract-klasse som legger til funksjonalitet i View, omtrent som vi la til speak-metoden i dyrene. Den ekstra funksjonaliteten er det som gjør at den fungerer når den håndteres med RecyclerView.

Slik lager vi 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 kontaktarrayet vårt til ContactsView med RecyclerView.

Legge til en visning

ViewGroup tegner ikke automatisk på nytt, men følger en bestemt tidsplan. Det kan hende at den tegner på nytt hvert 10. eller 100. ms på enheten din; eller, hvis vi velger et absurd tall, for eksempel 1 minutt, når vi legger til en View i en ViewGroup, vil du se endringene 1 minutt senere når ViewGroup «oppdateres».

RecyclerView.Recycler

Grunnarbeid #3 – Caching

Et av de beste eksemplene på hvor vi regelmessig oppdaterer innhold 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.

I dette eksempelet kan vi forestille oss at nettstedet det gjelder 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 å laste inn innholdet på nytt.

Å male hele skjermen på nytt er selvsagt en kostbar ting. Tenk deg om vi hadde 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 hver gang du oppdaterer.

Vi trenger en måte å lagre de allerede lastede tweetene på, og sørge for at den neste forespørselen vår kan si hvilke tweeter den allerede har. Derfor laster den ikke ned alt på nytt, men henter bare de nye tweetene. Den sjekker også om en tweet som har blitt lagret lokalt ikke lenger er der, slik at den kan slette den lokalt. Det vi beskriver kalles mellomlagring.

Informasjonen vi sender til nettstedet om innholdet vi har kalles metadata. Så i praksis 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 har lagret fra sist vi lastet inn. Vennligst bruk det til å bare sende oss det som ikke er der, slik at vi ikke bruker mye båndbredde.»

Layoutsamtaler – Tweetlisten må være gal

Et eksempel på et layoutkall er scrollToPosition. Dette er et vanlig eksempel i ting som chatteapper. Hvis noen i en chattetråd svarer på en chatteboble fra tidligere, inkluderer noen chatteapper svaret og en lenke til chatteboblen, som når du klikker på den, navigerer deg til der den opprinnelige meldingen din var.

I tilfellet der vi kaller denne metoden før vi har lagt til en LayoutManager i RecyclerView og før vi har en RecyclerView.Adapter, ignoreres ganske enkelt scrollToPosition(n:Int).

Kommunikasjon mellom RecyclerView-komponenter

Grunnarbeid #4 – Tilbakeringinger

RecyclerView har mange bevegelige deler i arbeidet sitt. 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 elementene våre (contactList) til visninger (OneContactView) og deretter til ViewHolders (OneContactViewHolder) som RecyclerView er villig til å jobbe med ved hjelp av metodene den tilbyr.

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

contactList:Array<Contact>

Vi har brukt et enkelt scenario som utgangspunkt for å få en følelse av hva RecyclerView prøver å oppnå. Et basisscenario der vi har en statisk matrise 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 View på skjermen når vi legger til et element i listen eller fjerner et element fra listen.

RecyclerView.LayoutManager

Bortsett fra å bestemme hvordan visningene våre skal legges ut, enten lineært eller i et rutenett, gjør LayoutManager mye arbeid under panseret i hel.ping Gjenvinningsfirmaet vet når de skal resirkulere.

Det er ansvarlig for å holdeping track av visningene som for øyeblikket er synlige på skjermen og kommuniserer denne informasjonen til resirkuleringsmekanismen. Når en bruker ruller nedover, er LayoutManager ansvarlig for å informere resirkuleringssystemet om visningene som mister fokus øverst, slik at de kan brukes på nytt, i stedet for å bli værende der og bruke opp minne eller bli ødelagt bare for å opprette nye.

Dette betyr at LayoutManager må beholde track av hvor brukeren er når de blar gjennom listen vår. Dette gjøres ved å ha en liste med posisjoner som er indeksbaserte, dvs. det første elementet starter fra 0 og øker for å samsvare med antallet 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 fokus fra View-0 helt til View-9. Når vi blar, kan LayoutManager beregne visningene som blir ute av fokus.

LayoutManager kan frigi disse visningene til resirkuleringsmekanismen slik at de kan brukes på nytt (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ært et godt tilfelle hvis listen vi har er statisk, men et av de vanligste bruksområdene for bruk av RecyclerView er med dynamiske lister der data kan komme fra et online endepunkt eller kanskje til og med fra en sensor. Ikke bare legges data til, men data på listen vår fjernes eller oppdateres noen ganger.

Den dynamiske tilstanden til dataene våre kan gjøre det svært vanskelig å resonnere rundt LayoutManager. Av denne grunn vedlikeholder LayoutManager sin egen liste over elementene og posisjonene, separat fra listen som resirkuleringskomponenten bruker. Dette sikrer at den gjør layoutjobben riktig.

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

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

I tillegg presenterer RecyclerView oss metoder vi kan bruke for å lytte til hendelser, for eksempel onBindViewHolder når RecyclerView.Adapter binder innhold fra listen vår (f.eks. en kontakt) til en ViewHolder slik at den brukes til å vise informasjonen på skjermen.

En annen er onCreateViewHolder, som forteller oss når RecyclerView.Adapter tar en vanlig visning som OneContactView og konverterer den til et ViewHolder-element som RecyclerView kan jobbe med.

Bortsett fra kjernemekanismene som muliggjør resirkulering, gir RecyclerView 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.

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

Gradle

Hvis vi vil bruke RecyclerView, må vi legge den til som en avhengighet i build.gradle-filen vår. 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 for å synkronisere endringene. Slik gjør vår Gradle filen vil se etter å ha lagt til en RecyclerView i et tomt prosjekt med bare standardverdiene.

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 oppretter en matrise med fruktnavn inni, rett før onCreate()-metoden som ble generert under oppsettet.

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 blir å presentere denne listen på skjermen ved hjelp av en RecyclerView. For å gjøre dette, navigerer vi til layoutkatalogen som inneholder layoutene våre og oppretter en View som skal 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 TextView ovenfor har vi lagt til et id-felt som skal brukes til å identifisere en View. Det genereres ikke som standard. Vi har gitt TextView ID-en fruitName for å matche dataene, som vil bli bundet til den.

Legger til RecyclerView til hovedoppsettet

I samme aktivitet finnes det en layoutfil av typen main_layout.xml som ble generert for oss som standard. Hvis vi valgte et tomt prosjekt, ville det ha generert en XML inneholder et ConstraintLayout, og inni vil det være en TextView med «Hello»-tekst. Vi sletter alt innholdet og lar layouten kun inneholde RecyclerView som vist 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"

Deretter navigerer vi tilbake til MainActivity-filen vår. Ved å bruke ID-ene vi har opprettet, kan vi referere til Views vi nettopp har opprettet. Vi starter med å referere til RecyclerView ved hjelp av findViewById()-metoden levert av AndroidVi vil gjøre dette i vår onCreate()-metode. Vår onCreate()-metode vil se slik ut.

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

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

Opprett en ViewHolder

Deretter skal vi opprette en RecyclerView.ViewHolder, som er ansvarlig for å ta View-en vår og konvertere den til en ViewHolder, som RecyclerView bruker til å vise elementene våre. Vi gjør dette rett etter onCreate()-metoden vår.

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 skal vi opprette en FruitArrayAdapter-klasse som utvider RecyclerView.Adapter-klassen. FruitArrayAdapteren vi oppretter vil være ansvarlig for følgende: den vil ta fruktnavn fra fruktarrayet, opprette en ViewHolder ved hjelp av visningen vår one_fruit_view.xml, deretter binde frukten til en ViewHolder og dynamisk binde innholdet til visningen vi opprettet.

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å FruitArrayAdapteren vår, som forteller oss at vi må implementere metoder som RecyclerView kan bruke for å koble arrayet vårt til en ViewHolder.

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

     }

     override fun getItemCount(): Int {

     }

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

     }
 }

Vi starter med den enkleste delen av den genererte koden, metoden getItemCount(). Vi vet hvordan vi får antall elementer i arrayet vårt ved å kalle array size-egenskapen.

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

        }
    }

Så skal vi implementere metoden onCreateViewHolder. Det er her RecyclerView ber oss om å hjelpe den med å konstruere en FruitViewHolder. Hvis vi husker riktig, så FruitViewHolder-klassen vår slik 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 opprette en referanse til denne XML-filen og konvertere den til en View 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 onBindViewHolder-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 elementet vi får fra fruitArray til Viewen som holdes inne i viewholderen. Viewen som holdes inne i en ViewHolder er tilgjengelig via feltet ViewHolder.itemView. Når vi har fått viewen, 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 FruitArrayAdapteren vår komplett og ser slik ut.

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 sammen de gjenværende delene av RecyclerView, som oppretter en LayoutManager som forteller RecyclerView hvordan innholdet i listen skal vises, enten det skal vises lineært ved hjelp av LinearLayoutManager eller i et rutenett ved hjelp av GridLayoutManager eller StaggeredGridLayoutManager.

Opprett Layout Manager

Vi går tilbake inn i onCreate-funksjonen vår og legger 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å opprettet en fruitListAdapter-instans og matet den med arrayet med fruktnavn. Og i bunn og grunn er vi ferdige. Den komplette MainActivity.kt-filen ser slik ut.

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

Spørsmål og svar

RecyclerView er den mer fleksible og effektive etterfølgeren til ListView. Den håndhever ViewHolder-mønsteret, støtter lineære, rutenett- og forskjøvne layouter gjennom layoutadministratorer, og legger til innebygde elementanimasjoner, mens ListView ikke tilbyr noen av disse som standard.

GridView viser bare elementer i et fast rutenett. RecyclerView kan reprodusere et rutenett ved hjelp av GridLayoutManager, men det støtter også lineære og forskjøvne oppsett, visning av resirkulering og animasjoner, noe som gjør det langt mer allsidig enn GridView.

Dette RecyclerView-eksemplet er skrevet i Kotlin, språket som offisielt anbefales av Google forum Android utvikling. De samme API-ene for RecyclerView, adapter og layout manager fungerer også i Java.

Koble til en klikklytter i ViewHolder eller onBindViewHolder, og eksponer den deretter gjennom et grensesnitttilbakekall til aktiviteten eller fragmentet. Bruk av ViewHolder-posisjonen unngår å binde klikk til feil resirkulert element.

DiffUtil er en verktøyklasse som beregner differansen mellom to lister og sender det minimale settet med oppdateringer til adapteren. Den effektiviserer dynamiske listeendringer og animerer tillegg, fjerning og flyttinger automatisk.

Jetpack Compose bruker LazyColumn og LazyRow i stedet for RecyclerView for nye skjermbilder. RecyclerView er imidlertid fortsatt mye brukt i eksisterende View-baserte prosjekter og kan bygges inn i Compose gjennom interoperabilitets-API-er.

Ja. AI-assistenter som GitHub Copilot og GPT-baserte verktøy kan sette opp en RecyclerView-adapter, ViewHolder og onBindViewHolder standardtekst fra en kort ledetekst. Utviklere bør fortsatt gjennomgå den genererte koden for korrekthet og ytelse.

Maskinlæring Android Apper bruker RecyclerView til å gjengi dynamiske utdata som anbefalinger, søkerangeringer eller bildeklassifiseringer. Adapteren binder modellresultater til listeelementer, slik at store, ofte endrede datasett ruller jevnt.

Oppsummer dette innlegget med: