Android RecyclerView: Hvad er, lær med simple eksempler

Hvad er RecyclerView i Android?

RecyclerView er en widget, der er mere fleksibel og avanceret version af GridView og ListView. Det er en beholder til visning af store datasæt, som kan rulles effektivt ved at opretholde et begrænset antal visninger. Du kan bruge RecyclerView-widget, når du har datasamlinger, hvis elementer ændres ved kørsel afhænger af netværksbegivenhed eller brugerhandling.

Views

Android platformen bruger klasserne View og ViewGroup til at tegne elementer på skærmen. Disse klasser er abstrakte og udvides til forskellige implementeringer for at passe til en use-case. TextView har for eksempel et simpelt formål at vise tekstindhold på skærmen. EditText strækker sig fra den samme View-klasse og tilføjer mere funktionalitet for at gøre det muligt for brugeren at indtaste data.

Det er muligt at skabe vores egne brugerdefinerede visninger for at kunne opnå mere fleksibilitet ved udvikling af brugergrænseflader. View-klassen giver metoder, vi kan tilsidesætte for at tegne på skærmen, og et middel til at overføre parametre såsom bredde, højde og vores egne brugerdefinerede attributter, vi gerne vil tilføje til vores View for at få det til at opføre sig, som vi ønsker.

Vis grupper

ViewGroup-klassen er en slags View, men i modsætning til den simple View-klasse, hvis ansvar blot er at vise, giver ViewGroup os muligheden for at sætte flere views i én view, som vi kan referere til som helhed. I dette tilfælde kaldes den visning, der er oprettet på det øverste niveau, som vi tilføjer andre simple visninger (vi kan også tilføje visningsgrupper) "forælderen", og de visninger, der tilføjes indeni, er "børn".

Vi kan afbilde en View som en Array og en ViewGroup som en Array af Arrays. I betragtning af at en Array of Arrays er en Array i sig 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 giver os også mulighed for at definere, hvordan børnene er organiseret inde i visningen, for eksempel lægges vores lodret eller vandret. Vi kan have forskellige regler for interaktion inde i visningen. For eksempel skal TextViews efter hinanden have en afstand på 12dp, mens ImageViews efterfulgt af TextView skal have en afstand på 5dp.

Dette ville være tilfældet, hvis vi udviklede vores egen ViewGroup fra bunden. For at gøre disse konfigurationer nemmere, Android giver en klasse kaldet LayoutParams, som vi kan bruge til at indtaste disse konfigurationer.

Android dokumentation giver nogle standardparametre, vi ville implementere, når vi konfigurerer vores egen ViewGroup. Nogle almindelige parametre er dem, der vedrører bredde, højde og margin. Som standard har disse konfigurationer strukturen android:layout_height for højde, for eksempel android:layout_width. I denne forbindelse, når du opretter din ViewGroup, kan du yderligere oprette LayoutParams, der er specifikke for den måde, du ønsker, at din ViewGroup skal opføre sig på.

Android leveres med standardvisninger og visningsgrupper, vi kan bruge til at udføre mange af de almindelige opgaver, vi har brug for. Et eksempel, vi har nævnt, er en TextView. Det er en enkel visning, der kommer med konfigurerbare aspekter såsom højde, bredde, tekststørrelse og lignende. Vi har en ImageView til visning af billeder og EditText, som vi havde nævnt, blandt mange andre. Android har også brugerdefinerede ViewGroups, som vi kan tilføje vores Views til og få den forventede adfærd.

Lineær layout

LinearLayout giver os mulighed for at tilføje View-elementer i den. LinearLayout er en orienteringsattribut, der dikterer, hvordan den vil blive lagt ud på skærmen. Den har også LinearLayout.LayoutParams, der dikterer reglerne for visningerne inde, f.eks. vil attributten android:center_horizontal centrere visningerne langs den vandrette akse, hvorimod `android:center_vertical ville centrere indholdet af visningen langs den lodrette akse.

Her er nogle billeder for at få en forståelse af centrering. Vi ville tage dette som en simpel TextView inden for 200px gange 200px plads, centreringsattributter ville få det til at opføre sig som følger.

android:center_horizontal

Vandret centreret indhold
Vandret centreret indhold

android:center_vertical

lodret centreret indhold
lodret centreret indhold

android:center

Centreret indhold
Centreret indhold

Kernekomponenter i RecyclerView

Kernekomponenter i RecyclerView
Kernekomponenter i RecyclerView

Følgende er de vigtige komponenter i RecyclerView:

RecyclerView.Adapter

Grundværk #1 – Adaptermønster

En adapter er en enhed, der transformerer attributter for system eller enhed til dem for en ellers inkompatibel enhed eller system. Nogle af dem ændrer signal- eller strømattributter, mens andre blot tilpasser den fysiske form af et stik til et andet.

Et simpelt eksempel, vi finder i det virkelige liv for at forklare en Adapter, er, når vi skal forbinde enheder sammen, men de har forbindelsesporte, der ikke matcher hinanden. Dette kan være tilfældet, når du besøger et andet land, hvor de bruger forskellige slags stikkontakter. Hvis du bærer din telefon eller laptop oplader, ville det være umuligt at tilslutte den til stikkontakten. Du ville dog ikke give op, men blot få en adapter, der ville komme ind mellem stikkontakten og din oplader og gøre det muligt at oplade.

Dette er tilfældet i programmering, når vi ønsker at forbinde to datastrukturer sammen for at udføre opgaven, men deres standardporte har ikke en måde at kommunikere med hinanden på.

Vi vil bruge det enkle eksempel på en enhed og en oplader. Vi har to forekomster af opladere. En amerikansk og en britisk

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

Vi opretter derefter to enheder

class AmericanDevice() 
class BritishDevice()

Som et eksempel kan vi så oprette nogle forekomster af enhederne til at spille sammen.

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

Vi introducerer derefter begrebet opladning for begge enhederne ved at tilføje en metode i enhederne kaldet charge() .

Metoden tager som input sin respektive oplader og oplader ud fra det.

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 tilfælde, baseret på vores analogi, ville vi af den ene eller anden grund have behov for at bruge en BritishCharger, når vi bruger en AmericanDevice eller omvendt.

I programmeringsverdenen er det normalt, når man blander biblioteker sammen, der tilbyder den samme funktionalitet (i vores sammenhæng er vores delte funktionalitet opladning). Vi bliver nødt til at finde en måde at muliggøre dette.

Hvis vi følger analogien, bliver vi nødt til at gå til en elektronikbutik og købe en adapter, som vi vil gøre det muligt for os at oplade AmericanDevices givet BritishChargers. Fra et programmeringsperspektiv vil vi være dem, der vil være producenten af ​​adapteren.

Vi laver en adapter til den ene, som matcher det nøjagtige mønster, vi skal bruge for at skabe den anden. Vi implementerer det som en klasse som følger. Det behøver ikke nødvendigvis at være en klasse og kunne være en funktion, der fremhæver, hvad adaptermønsteret generelt gør. Vi bruger en klasse, da den matcher det meste af brugen 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 forskellen i stikkontakterne analog med forskellen i metoderne indeni, der bruges til opladning. Opladere med forskellige metoder ville gøre det umuligt at bruge opladerne.

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

At prøve at kalde charge()-metoden i myBritishDevice med americanChargerIFound ville ikke fungere, da AmericanDevice kun accepterer en AmericanCharger

Så det er umuligt at gøre dette

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

I dette scenarie den adapter, vi oprettede

AmericanToBritishChargerAdapter kan nu komme til nytte. Vi kan bruge returnNewCharger() metoden til at oprette en ny BritishCharger, som vi kan bruge til at oplade. Det eneste, vi skal bruge, er at oprette en instans af vores adapter og fodre den med den amerikanske oplader, vi har, og den vil skabe en britisk oplader, vi kan bruge

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 at gøre med en ViewGroup, ville vi have Views placeret inde i den. Det er LayoutManager ville have til opgave at beskrive, hvordan visningerne er lagt ud inde.

Til sammenligningsformål, når vi arbejder med Linearlayout ViewGroup, er den use case, vi ønsker, muligheden for at placere elementerne enten lodret eller vandret. Dette implementeres nemt ved at tilføje en orienteringsattribut, som fortæller os, hvordan det lineære layout vil blive placeret på skærmen. Det kan vi gøre ved at bruge android:orientation=VERTICAL|HORIZONTAL attribut.

Vi har også en anden ViewGroup kaldet GridLayout, det er et tilfælde, hvor vi ønsker at placere Views i en rektangulær Grid-struktur. Dette kan være af årsager som at gøre de data, vi præsenterer for appbrugeren, nemme at forbruge. Ved design muliggør GridLayout konfigurationer, der hjælper dig med at nå dette mål ved at have konfigurationer, hvor vi kan definere dimensionerne af gitteret, for eksempel kan vi have et 4×4 gitter, 3 x 2 gitter.

RecyclerView.ViewHolder

ViewHolder er en abstrakt klasse, som vi også udvider fra RecyclerView. ViewHolder giver os almindelige metoder til at hjælpe os med at referere til en visning, vi har placeret på RecyclerView, selv efter at genbrugsmaskineriet i RecyclerView har ændret forskellige referencer, vi ikke kender til.

Store lister

RecyclerViews bruges, når vi ønsker at præsentere et rigtig stort sæt visninger for brugeren, alt imens vi ikke udmatter vores RAM på vores enhed for hver eneste forekomst af den oprettede visning.

Hvis vi skulle tage en kontaktliste, ville vi have en generel idé om, hvordan en kontakt ville se ud på listen. Det, vi så ville gøre, er at lave et skabelonlayout – som faktisk er en visning – med pladser, hvor forskellige data fra vores kontaktliste vil fylde. Følgende er en pseudokode, der forklarer hele formålet:

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

Vi ville så have en kontaktliste af denne art

 <ContactList>
</ContactList>

Hvis det er tilfældet, har vi hårdkodet indholdet, vi ville ikke have en programmatisk måde at tilføje nyt indhold til listen uden at omskrive appen. Heldigvis for os. Tilføjelse af en visning til en visningsgruppe understøttes af en addView(view:View) fremgangsmåde.

Selvom det er tilfældet, er det ikke, hvordan RecyclerView får børnevisninger tilføjet til det.

I vores brugstilfælde ville vi have en lang liste af kontakter. For hver kontakt på listen skal vi oprette OneContactView og udfylde dataene i visningen for at matche felterne i vores kontaktklasse. Så når vi har visningen, skal vi tilføje den til RecyclerView for at 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 række kontakter kaldet OneContactView. Den indeholder pladser til at tage indhold fra kontaktklassen og vise dem. I RecyclerView er vi nødt til at tilføje visninger til det, så det kan hjælpe os med dets genbrugsevne.

RecyclerView giver os ikke rigtig mulighed for at tilføje visning, men giver os mulighed for at tilføje en ViewHolder. Så i dette scenarie har vi to ting, vi gerne vil forbinde, men som ikke matcher. Det er her, vores adapter kommer ind i billedet. RecyclerView giver os en adapter, der ligner vores AmericanToBritishChargerAdapter() fra tidligere, der gjorde det muligt for os at konvertere vores AmericanCharger, som var ubrugelig med vores britiske enhed, til noget brugbart, beslægtet med strømadapter i det virkelige liv.

I dette scenarie ville adapteren tage vores række af kontakter og vores visning og derfra generere ViewHolders, som RecyclerView er villig til at acceptere.

RecyclerView giver en grænseflade, som vi kan udvide til at skabe vores adapter gennem klassen RecyclerView.Adapter. Inde i denne adapter er en måde at skabe ViewHolder-klassen på, som RecyclerView ønsker at arbejde med. Så det, vi har, er den samme situation som før, men med en ekstra ting, det er adapteren.

Vi har en række kontakter, en visning til at vise én kontakt OneContactView. En RecyclerView er en liste over Views, der leverer genbrugstjenester, men som kun er villig til at tage imod ViewHolders

Men i dette scenarie har vi nu RecyclerView.Adapter-klassen, som har en metode til at oprette ViewHolders inde.

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

RecyclerView.ViewHolder er en abstrakt klasse, der tager vores View som et argument og konverterer den til en ViewHolder.

Den bruger indpakningsmønsteret, der bruges til at udvide klassernes evner.

Grundarbejde #2 – Indpakningsmønster

Vi vil bruge et simpelt eksempel til at demonstrere, hvordan vi kan få dyr til at tale.

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 ovenstående eksempel har vi to dyr. Tilfældigt ville vi tilføje en metode til at få tale, men biblioteksforfatteren var ikke sjov, vi kunne stadig finde en måde. Det, vi skal bruge, er en indpakning til vores dyreklasse. Det ville vi gøre ved at tage dyret ind som konstruktør for vores klasse

 class SpeechPoweredAnimalByWrapper(var myAnimal:Animal){

    fun sound(){
        myAnimal.sound()
    }

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

Nu kan vi i en dyreforekomst videregive til SpeechPoweredAnimalByWrapper. Hvis du kalder sound()-metoden på den, kalder du bestået i animal sound()-metoden. Vi har også en ekstra speak()-metode, som tæller som ny funktionalitet, vi tilføjer til de dyr, der sendes ind. Vi kan bruge den på følgende måde:

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 at bruge dette mønster kan vi tage klasser og tilføje funktionalitet. Alt, hvad vi skal bruge, er at bestå er en klasseinstans og nye metoder defineret af vores indpakningsklasse.

I vores tilfælde ovenfor brugte vi en konkret klasse. Det er også muligt at implementere det samme i en abstrakt klasse. Vi skulle gøre er at tilføje ændre SpeechPoweredAnimalByWrapper-klassen til abstrakt, og vi er færdige. Vi ændrer klassenavnet til noget kortere for at gøre det mere læsbart.

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 betyde noget andet. I en normal klasse kan vi have en forekomst af en klasse på samme måde, som vi oprettede kat1 og hund1. Abstrakte klasser er dog ikke beregnet til at blive instansieret, men er beregnet til at udvide andre klasser. Så hvordan ville vi bruge den nye SpeechPowered(var myAnimal:Animal) abstrakt klasse. Vi kan bruge det ved at oprette nye klasser, der vil udvide det og igen få dets funktionalitet.

I vores eksempel opretter vi en klasse SpeechPoweredAnimal, der udvider 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ønster, der bruges i ViewHolder. Klassen RecyclerView.ViewHolder er en abstrakt klasse, der tilføjer funktionalitet til View, ligesom vi har tilføjet talemetoden til dyrene. Den ekstra funktionalitet er det, der får det til at fungere, når man har at gøre med RecyclerView.

Sådan ville vi oprette 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, der giver os mulighed for at forbinde vores Kontakter-array til ContactsView med RecyclerView

Tilføjelse af en visning

ViewGroup gentegner ikke ViewGroup automatisk, men følger en bestemt tidsplan. Det kan være tilfældet på din enhed, at den gentegner hver 10 ms eller 100 ms, eller hvis vi vælger et absurd tal, siger 1 minut, når vi føjer en visning til en visningsgruppe, vil du se ændringerne 1 minut senere, når visningsgruppen "opdateres".

RecyclerView.Recycler

Grundværk #3. Caching

Et af de bedste eksempler på, hvor vi regelmæssigt foretager opdateringer, er i browseren. Lad os f.eks. forestille os, at det websted, vi besøger, er statisk og ikke sender indhold dynamisk. Vi bliver nødt til at blive ved med at opdatere for at se ændringerne.

For dette eksempel, lad os forestille os, at det pågældende websted er twitter. Vi ville have en række statiske tweets opført, og den eneste måde, vi kunne se nye tweets på, ville være ved at klikke på opdateringsknappen for at genhente indholdet.

At male hele skærmen om er naturligvis en bekostelig ting. Hvis vi skulle forestille os, hvis det var tilfældet, havde vi begrænset båndbredde hos vores telefonudbyder. Og vores tweet-liste havde en masse billeder og videoer, det ville være dyrt at gendownloade alt indholdet på siden ved hver opdatering.

Vi ville have brug for en måde at gemme de allerede indlæste tweets og sikre, at vores næste anmodning har en evne til at sige de tweets, den allerede har. Derfor gendownloader den ikke alt og får kun de nye Tweets, den har, og kontroller også, om nogle Tweet, der var blevet gemt lokalt, ikke længere er der, så den kan slette det lokalt. Det, vi beskriver, kaldes Caching.

De oplysninger, vi sender til hjemmesiden om det indhold, vi har, kaldes metadata. Så i reel forstand siger vi ikke bare "vi vil indlæse dit websted", vi siger "vi vil indlæse dit websted, og her er noget af det indhold, vi allerede havde gemt fra sidste gang, vi indlæste, brug det til at send os kun det, der ikke er derinde, så vi ikke bruger meget båndbredde, da vi ikke har mange ressourcer.”

Layoutopkald – Tweetlisten må være skør

Et eksempel på et layoutkald er scrollToPosition

Dette er et almindeligt eksempel, der er til stede i ting som chat-apps. Hvis en person i en chattråd svarer på en chatboble fra tidligere, inkluderer nogle chat-apps svaret og et link til chatboblen, som ved at klikke navigerer dig til, hvor din oprindelige besked var.

I tilfældet kalder vi denne metode, før vi har tilføjet en LayoutManager til vores RecyclerView, og før vi har en RecyclerView.Adapter, ignoreres scrollToPosition(n:Int) simpelthen .

Kommunikation mellem RecyclerView-komponenter

Grundværk #4. Tilbagekald

I sit arbejde har RecyclerView en masse bevægelige dele. Det skal handle med LayoutManager, som fortæller os, hvordan vi organiserer visningerne, enten lineært eller i et gitter. Det skal handle med en Adapter, der udfører arbejdet med at konvertere vores kontaktliste for varer til Views OneContactView og derefter til ViewHolders OneContactViewHolder, som RecyclerView er villig til at arbejde inden for de metoder, den giver os.

Råmaterialet til RecyclerView er vores Views fx OneContactView og datakilde.

contactList:Array<Contact>

Vi har brugt et simpelt scenarie som udgangspunkt for at få en fornemmelse af, hvad RecyclerView forsøger at opnå.

Et basistilfælde af, hvornår vi ville have et statisk array på 1000 kontakter, vi ønsker at vise brugeren, er let at forstå.

RecyclerView-maskineriet begynder virkelig at tage liv, når listen ikke længere er statisk.

Med en dynamisk liste skal vi tænke på, hvad der sker med visningen på skærmen, når vi tilføjer et element til listen eller fjerner et element fra listen.

RecyclerView.LayoutManager

Udover at bestemme, hvordan vores synspunkter skal udformes enten lineært eller i et gitter. LayoutManageren gør en masse arbejde under motorhjelmen for at hjælpe genbrugeren med at vide, hvornår genbrugen skal udføres.

Det er ansvarligt for at holde styr på de visninger, der i øjeblikket er synlige på skærmen, og formidle disse oplysninger til genbrugsmekanismen. Når en bruger ruller nedad, er Layout-manageren ansvarlig for at informere genbrugssystemet om de visninger, der går ude af fokus i toppen, så de kan genbruges i stedet for at de bliver der og optager hukommelse eller i stedet for at ødelægge dem og skulle oprette Nye.

Hvad dette betyder er, at LayoutManager skal holde styr på, hvor brugeren er, mens de ruller gennem vores liste. Det gør den ved at have en liste over positioner, der er indeksbase, dvs. det første punkt er at starte fra 0 og øge for at matche antallet af poster på vores liste.

Hvis vi kan se 10 punkter på vores liste med f.eks. 100, i begyndelsen, er LayoutManager klar over, at den har i fokus view-0 hele vejen til View-9 Mens vi ruller, er LayoutManageren i stand til at beregne de visninger, der kommer ud af fokus.

LayoutManageren er i stand til at frigive disse visninger til genbrugsmekanismen, så de kan genbruges (nye data kan bindes til dem, f.eks. kan en visnings kontaktdata fjernes, og nye kontaktdata fra det næste segment kan erstatte pladsholderne).

Dette ville være et godt tilfælde, hvis den liste, vi har, er statisk, men en af ​​de mest almindelige brugssager ved at bruge en RecyclerView er med dynamiske lister, hvor data kan komme fra et online-endepunkt eller endda måske fra en sensor. Ikke kun er data tilføjet, men data på vores liste bliver nogle gange fjernet eller opdateret.

Den dynamiske tilstand af vores data kan gøre det meget vanskeligt at ræsonnere om LayoutManager. Af denne grund vedligeholder LayoutManager en egen liste over emnerne og positionerne, som er adskilt fra den liste, som genbrugskomponenten bruger. Dette sikrer, at den udfører sit layout-job korrekt.

Samtidig ønsker RecyclerView's LayoutManager ikke at misrepræsentere de data, den har. For at fungere korrekt synkroniserer LayoutManager med RecyclerView.Adapteren med givne intervaller (60ms) og deler information om vores listeelementer. dvs. elementer tilføjet, opdateret, fjernet, flyttet fra en position til en anden). Når LayoutManageren modtager disse oplysninger, omorganiserer indholdet på skærmen for at matche ændringerne, når det er nødvendigt.

Mange af de kerneoperationer, der omhandler RecylerView, roterer omkring kommunikation mellem RecyclerView.LayoutManager og RecyclerView.Adapter, der gemmer vores lister over nogle gange statiske eller nogle gange dynamiske data.

Derudover præsenterer RecyclerView os for metoder, vi kan bruge til at lytte med på begivenheder såsom onBindViewHolder, når vores RecyclerView.Adapter binder indhold fra vores liste, fx en kontakt til en ViewHolder, så den nu vænner sig til at vise informationen på skærmen.

En anden er onCreateViewHolder, som fortæller os, hvornår RecyclerView. Adapteren tager en almindelig visning som OneContactView og konverterer den til en ViewHolder-vare, som RecyclerView kan arbejde med. Fra vores ViewHolder til brug for RecyclerView. onViewDetached

Bortset fra kernemekanismerne, der muliggør genbrug. RecyclerView giver måder til at tilpasse adfærd uden at påvirke genbrug.

Genbrug af visninger gør det vanskeligt at gøre almindelige ting, vi er vant til at gøre med statiske visninger, såsom at reagere på onClick-begivenheder.

Da vi er klar over, at RecyclerView.LayoutManager der præsenterer visningerne for brugeren kan et øjeblik have en anden liste over elementer fra RecyclerView.Adapter der har den liste, som vi har gemt i en database eller streamer ind fra en kilde. At sætte OnClick-begivenheder direkte på Views kan føre til uventet adfærd, såsom sletning af den forkerte kontakt eller ændring.

Gradle

Hvis vi vil bruge RecyclerView, skal vi tilføje det som en afhængighed i vores build .gradle-fil.

I det følgende eksempel har vi brugt implementeringen "androidx.recyclerview:recyclerview:1.1.0", som er den nyeste version ifølge denne artikel.

Efter at have tilføjet afhængigheden til vores Gradle fil, bliver vi bedt om af Android Studie til Syncharmonisere ændringerne,

Sådan er vores Gradle fil vil se ud efter tilføjelse af en RecyclerView i et tomt projekt med kun standardindstillingerne.

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 kun én layoutfil i øjeblikket. Vi starter med et simpelt eksempel, hvor vi bruger en RecyclerView til at vise en liste over frugtnavne på skærmen.

Liste over varer

Vi navigerer til vores MainActivity-fil og opretter et array med frugtnavne indeni lige før onCreate()-metoden, der blev genereret under opsætningen.

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

Vores næste mål vil være at præsentere denne liste på skærmen ved hjælp af en RecyclerView.

For at gøre dette, navigerer vi til layoutbiblioteket, der indeholder vores layouts, og opretter en visning, der vil være ansvarlig for at vise én frugt.

Layout, der skal bruges til hvert element på vores liste

<?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 tilføjet et id-felt, der vil blive brugt til at identificere en visning.

Det genereres ikke som standard. Vi har vores TextView id'et fruitName til at matche dataene, som vil være bundet til det.

Tilføjelse af RecyclerView til hovedlayoutet

I den samme aktivitet er der en layoutfil main_layout.xml, der som standard blev genereret til os.

Hvis vi vælger et tomt projekt. Det vil have genereret en XML indeholdende et ConstraintLayout og indeni vil være en TextView med "Hej" tekst.

Vi sletter alt indhold og har layoutet kun indeholde 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å tilføjet en id-attribut for RecyclerView, som vi vil bruge til at referere til den i vores kode.

 android:id="@+id/fruitRecyclerView"

Vi vil derefter navigere tilbage til vores MainActivity-fil. Ved at bruge de id'er, vi har oprettet, vil vi være i stand til at referere til de visninger, vi lige har oprettet.

Vi starter med at referere til RecyclerView ved hjælp af findViewById() metoden leveret af Android. Vi vil gøre dette i vores onCreate() metode.

Vores onCreate() metode vil se ud som følger.

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

Opret en ViewHolder

Dernæst vil vi oprette en RecyclerView.ViewHolder, som er ansvarlig for at tage vores View og konvertere den til en ViewHolder, som RecyclerView bruger til at vise vores genstande.

Vi vil gøre dette lige efter vores sjove 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)
    
}

Opret en RecyclerViewAdapter

Dernæst vil vi oprette en FruitArrayAdapter-klasse, der udvider RecyclerView.Adapter-klassen.

FruitArrayAdapteren, vi opretter, vil være ansvarlig for at gøre følgende.

Det vil tage frugtnavne fra frugtarrayet. Det vil oprette en ViewHolder ved hjælp af vores view one_fruit_view.xml Det vil derefter binde frugten til en ViewHolder, og dynamisk binde indholdet til den View, vi oprettede 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 tilføje røde kruseduller på vores FruitArrayAdapter, der fortæller os, at vi skal implementere en metode, som RecyclerView kan bruge til at forbinde vores array til en ViewHolder, som RecyclerView kan bruge.

   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 at den nemmeste bit fra den genererede kode er getItemCount() metoden. Vi ved, hvordan man får antallet af elementer i vores array ved at kalde metoden array.length.

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å implementerer vi metoden tilsidesætter sjov påCreateViewHolder.

Det er her, RecyclerView beder os om at hjælpe den med at konstruere en FruitHolder til den.

Hvis vi husker sådan så vores FruitViewHolder-klasse ud:

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

Det kræver vores fruitView, som vi oprettede som en xml-fil one_fruit_view.xml

Vi kan være i stand til at oprette en reference til denne xml 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 resterende bit er tilsidesættelsen

fun onBindViewHolder(holder: FruitViewHolder, position: Int)

RecyclerView.Adapter spørger med et positionsheltal, som vi vil bruge til at hente et element fra vores liste. Det giver os også en holder, så vi kan binde den genstand, vi får fra fruitArray'en, til den visning, der holdes inde i visningsholderen.

Visningen, der holdes inde i en ViewHoder, er tilgængelig via feltet ViewHolder.itemView. Når vi har fået visningen, kan vi bruge id'et fruitName, vi oprettede tidligere, til at indstille indholdet.

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

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

            var aFruitName = fruitArray.get(position)

            ourFruitTextView.setText(aFruitName)
        }

Dermed er vores FruitArrayAdapter komplet og ser ud 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 klar til at tilslutte de resterende dele af vores RecyclerView. Som opretter en LayoutManager, som fortæller RecyclerView, hvordan indholdet af listen vises. Om der skal vises på en lineær måde ved hjælp af LinearLayoutManager eller i et gitter ved hjælp af GridLayoutManager eller StaggeredGridLayoutManager.

Opret Layout Manager

Vi vil gå tilbage i vores onCreate-funktion og tilføje 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
        
    }

Tilslut vores adapter til varer og sæt 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å oprettet en fruitListAdapter-instans og tilført den rækken af ​​frugtnavne.

Og i bund og grund er vi alle færdige.

Den komplette MainActivity.kt-fil ser ud 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)
        }
    }
}

Download projektet