Android RecyclerView: Hvad er, lær med simple eksempler
⚡ Smart opsummering
RecyclerView er et avanceret Android En widget, der effektivt viser store, rulbare datasæt ved at genbruge et begrænset antal visninger. Den er afhængig af en adapter, en layoutadministrator og en visningsholder til at binde datasamlinger, der ændrer sig under kørsel.

Hvad er RecyclerView i Android?
RecyclerView er en mere fleksibel og avanceret version af GridView og ListView. Det er en beholder til visning af store datasæt, der kan rulles effektivt ved at opretholde et begrænset antal visninger. Du kan bruge RecyclerView-widgetten, når du har datasamlinger, hvis elementer ændres under kørsel afhængigt af en netværkshændelse eller brugerhandling.
Views
Android Platformen bruger klasserne View og ViewGroup til at tegne elementer på skærmen. Disse klasser er abstract og udvides til forskellige implementeringer, der passer til en use case. TextView har for eksempel det simple formål at vise tekstindhold på skærmen. EditText udvider sig fra den samme View-klasse og tilføjer mere funktionalitet, der gør det muligt for brugeren at indtaste data.
Det er muligt at oprette vores egne brugerdefinerede visninger for at opnå mere fleksibilitet under udvikling.ping brugergrænseflader. View-klassen indeholder metoder, vi kan tilsidesætte for at tegne på skærmen, og en metode til at indtaste parametre som bredde, højde og vores egne brugerdefinerede attributter, som vi ønsker at tilføje til vores View for at få den 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 mulighed for at sætte flere views ind i én view, som vi kan referere til som en helhed. I dette tilfælde kaldes den View, der oprettes på øverste niveau, og som vi tilføjer andre simple views til, for "parent", og de views, der tilføjes indeni, kaldes "children".
Vi kan forestille os et View som et Array og en ViewGroup som et Array af Arrays. Da et Array af Arrays i sig selv er et Array, kan vi se, hvordan en ViewGroup kan behandles som et 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 analogy, 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 i visningen, for eksempel om de er placeret lodret eller vandret. Vi kan have forskellige regler for interaktion i visningen. For eksempel bør tekstvisninger, der følger efter hinanden, have en afstand på 12 dp, mens en billedvisning efterfulgt af en tekstvisning bør have en afstand på 5 dp.
Dette ville være tilfældet, hvis vi udvikledeping 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 indeholder nogle standardparametre, vi ville implementere, når vi konfigurerer vores egen ViewGroup. Nogle almindelige parametre vedrører bredde, højde og margin. Som standard har disse konfigurationer strukturen android:layout_height for højde og android:layout_width for bredde. I denne henseende kan du, når du opretter din ViewGroup, 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, som vi kan bruge til at udføre mange af de almindelige opgaver, vi har brug for. Et eksempel, vi har nævnt, er en tekstvisning. Det er en simpel visning, der leveres med konfigurerbare aspekter som højde, bredde og tekststørrelse. Vi har også en billedvisning til visning af billeder og EditText, som vi nævnte, 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. LinearLayout har en orientation-attribut, der dikterer, hvordan den skal layoutes på skærmen. Den har også LinearLayout.LayoutParams, der dikterer reglerne for visningerne indeni. For eksempel vil attributten android:center_horizontal centrere visningerne langs den vandrette akse, hvorimod android:center_vertical vil centrere indholdet af visningen langs den lodrette akse.
Her er nogle billeder, der kan hjælpe med at forstå centrering. Vi antager, at dette er en simpel TextView i et rum på 200 x 200 pixels; centreringsattributterne ville få den til at opføre sig som følger.
android:center_horizontal
Vandret centreret indhold
android:center_vertical
Lodret centreret indhold
android:center
Centreret indhold
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 attributterne for et system eller en enhed til attributterne for en ellers inkompatibel enhed eller et system. Nogle adaptere ændrer signal- eller strømattributter, mens andre blot tilpasser den fysiske form af et stik til et andet.
Et simpelt eksempel fra det virkelige liv, der forklarer en adapter, er, når vi skal forbinde enheder sammen, men de har tilslutningsporte, der ikke passer til hinanden. Dette kan være tilfældet, hvis du besøger et andet land, der bruger forskellige typer stikkontakter. Hvis du medbringer din telefon- eller bærbare computeroplader, ville det være umuligt at tilslutte den til stikkontakterne. Du skal dog ikke give op, men blot købe en adapter, der placeres mellem stikkontakten og din oplader og muliggør opladning.
Dette er tilfældet i programmering, når vi ønsker at forbinde to datastrukturer for at udføre en opgave, men deres standardporte ikke har en måde at kommunikere med hinanden.
Vi bruger det simple eksempel med en enhed og en oplader. Vi har to typer opladere, en amerikansk og en britisk.
class AmericanCharger() { var chargingPower = 10 } class BritishCharger(){ var chargingPower = 5 }
Så opretter vi 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 vil derefter introducere konceptet med opladning for begge enheder ved at tilføje en metode i enhederne kaldet charge(). Metoden tager den respektive oplader og udfører opladning baseret 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 tilfælde, baseret på vores analogi, ville vi af en eller anden grund have brug for at bruge en britisk oplader, når vi bruger en amerikansk enhed, 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, der gør det muligt at oplade amerikanske enheder med BritishChargers. Fra et programmeringsperspektiv vil vi være producenten af adapteren.
Vi vil lave en adapter, der matcher præcis det mønster, vi skal bruge til at oprette den anden. Vi vil implementere den som en klasse som følger. Det behøver ikke nødvendigvis at være en klasse og kan 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 chargingPower:Int = theAmericanCharger.chargingPower / 2 var newBritishCharger = new BritishCharger() newBritishCharger.chargingPower = theAmericanCharger.chargingPower/2 return newBritishCharger } }
I programmeringsverdenen er forskellen i stikkene analog med forskellen i de metoder, der bruges til opladning. Opladere med forskellige metoder ville gøre det umuligt at bruge opladerne.
var myBritishDevice = new BritishDevice() var americanChargerIFound = new AmericanCharger()
Det ville ikke virke at kalde charge()-metoden på myBritishDevice med americanChargerIFound, 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 kan den adapter, vi har oprettet, AmericanToBritishChargerAdapter, nu være nyttig. Vi kan bruge returnNewCharger()-metoden til at oprette en ny BritishCharger, som vi kan bruge til at oplade. Alt, hvad vi skal gøre, er at oprette en instans af vores adapter og tilføre den den AmericanCharger, vi har, og det vil oprette en BritishCharger, vi kan bruge.
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 ViewGroup at gøre, har vi Views placeret i den. LayoutManager har til opgave at beskrive, hvordan Views er layoutet indeni.
Til sammenligning, når vi arbejder med en LinearLayout ViewGroup, ønsker vi at kunne placere elementerne enten lodret eller vandret. Dette implementeres nemt ved at tilføje en orientation-attribut, som fortæller os, hvordan LinearLayout placeres på skærmen. Vi kan gøre dette ved at bruge android:orientation=VERTICAL|HORIZONTAL attribut.
Vi har også en anden ViewGroup kaldet GridLayout. Dens anvendelse er, når vi ønsker at placere Views i en rektangulær gitterstruktur. Dette kan f.eks. skyldes, at det er nemt at bruge de data, vi præsenterer for appbrugeren. GridLayout er designet til at muliggøre konfigurationer, der hjælper dig med at nå dette mål ved at definere gitterets dimensioner; for eksempel kan vi have et 4×4 gitter eller et 3×2 gitter.
RecyclerView.ViewHolder
ViewHolderen er en mavemuskeltract-klassen, som vi også udvider fra RecyclerView. ViewHolderen giver os almindelige metoder, der hjælper 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 virkelig stort sæt af visninger for brugeren, uden at udtømme RAM på vores enhed for hver eneste forekomst af den oprettede visning.
Hvis vi tager en kontaktliste som eksempel, har vi en generel idé om, hvordan en kontakt ville se ud på listen. Det, vi så ville gøre, er at oprette et skabelonlayout, som faktisk er en visning, med pladser, hvor forskellige data fra vores kontaktliste vil blive udfyldt. Følgende er pseudokode, der forklarer hele formålet:
//OneContactView
<OneContact>
<TextView>{{PlaceHolderForName}}</TextView>
<TextView>{{PlaceHolderForAddress}}</TextView>
<ImageView>{{PlaceHolderForProfilePicture}}</ImageView>
<TextView>{{PlaceHolderForPhoneNumber}}</TextView>
</OneContact>
Så ville vi have en kontaktliste af denne art:
<ContactList> </ContactList>
Hvis vi hardcodede indholdet, ville vi ikke have en programmatisk måde at tilføje nyt indhold til listen uden at omskrive appen. Heldigvis for os understøttes tilføjelse af en visning til en visningsgruppe af en addView(view:View) metode. Alligevel er det ikke sådan, RecyclerView får tilføjet undervisningsvisninger.
I vores use case ville vi have en lang liste over kontakter. For hver kontakt på listen skal vi oprette en OneContactView og udfylde dataene i View'en, så de matcher felterne i vores Contact-klasse. Når vi har view'en, skal vi tilføje den til RecyclerView'en 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 et array af kontakter. OneContactView indeholder pladser til at tage indhold fra Contact-klassen og vise det. I RecyclerView skal vi tilføje Views, så det kan hjælpe os med dets genbrugsfunktioner.
RecyclerView tillader os ikke rigtigt at tilføje en visning, men den gør det muligt for os at tilføje en ViewHolder. Så i dette scenarie har vi to dele, vi vil forbinde, men som ikke matcher. Det er her, vores adapter kommer ind i billedet. RecyclerView giver os en adapter, ligesom vores AmericanToBritishChargerAdapter() fra tidligere, der gjorde det muligt for os at konvertere vores AmericanCharger, som var ubrugelig med vores BritishDevice, til noget brugbart, i stil med en strømadapter i virkeligheden.
I dette scenarie ville adapteren tage vores array af kontakter og vores View, og derfra generere ViewHolders, som RecyclerView er villig til at acceptere.
RecyclerView leverer en grænseflade, som vi kan udvide for at oprette vores adapter via RecyclerView.Adapter-klassen. Inde i denne adapter er en måde at oprette ViewHolder-klassen, som RecyclerView vil arbejde med. Så det, vi har, er den samme situation som før, men med én ekstra ting, adapteren.
Vi har en række kontakter, en visning til at vise én kontakt (OneContactView) og en RecyclerView, som er en liste over visninger, der leverer genbrugstjenester, men kun er villige til at acceptere ViewHolders. I dette scenarie har vi nu en RecyclerView.Adapter-klasse, som har en metode til at oprette ViewHolders indeni.
fun createViewHolder(@NonNull parent: ViewGroup, viewType: Int): ViewHolder
RecyclerView.ViewHolder er en abstract-klasse, der tager vores View som et argument og konverterer det til en ViewHolder. Den bruger det wrapper-mønster, der bruges til at udvide klassernes muligheder.
Grundarbejde #2 – Indpakningsmønster
Vi vil bruge et simpelt eksempel for 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. Hvis vi tilfældigvis ville tilføje en metode til at få dem til at tale, men bibliotekets forfatter ikke var sjov, kunne vi stadig finde en måde. Det, vi ville bruge, er en wrapper til vores Animal-klasse. Vi ville gøre dette ved at inkludere Animal som en konstruktør for vores klasse.
class SpeechPoweredAnimalByWrapper(var myAnimal:Animal){ fun sound(){ myAnimal.sound() } fun speak(){ println("Hello, my name is ${myAnimal.name}") } }
Nu kan vi sende en dyreinstans til SpeechPoweredAnimalByWrapper. Hvis vi kalder sound()-metoden på den, kaldes den sendte dyre-sound()-metode. Vi har også en ekstra speak()-metode, som tæller som ny funktionalitet, vi tilføjer til de sendte dyr. Vi kan bruge 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 at bruge dette mønster kan vi tage klasser og tilføje funktionalitet. Alt vi behøver er at overføre en klasseinstans og nye metoder defineret af vores wrapping klasse.
I ovenstående tilfælde brugte vi en betonklasse. Det er også muligt at implementere den samme i en abstract-klassen. Vi skal ændre SpeechPoweredAnimalByWrapper-klassen til abstract, og så er vi 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() } fun 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 instans af en klasse på samme måde, som vi oprettede cat1 og dog1. Abstract-klasser er imidlertid ikke beregnet til at blive instantieret, men er beregnet til at udvide andre klasser. Så hvordan ville vi bruge den nye SpeechPowered(var myAnimal:Animal) abstract-klassen? Vi kan bruge den ved at oprette nye klasser, der udvider den og dermed får dens 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 ViewHolderen. Klassen RecyclerView.ViewHolder er en abstract-klasse, der tilføjer funktionalitet til View'en, ligesom vi tilføjede speak-metoden til dyrene. Den ekstra funktionalitet er det, der får den til at fungere, når den arbejder med RecyclerView'en.
Sådan opretter 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, der giver os mulighed for at forbinde vores kontaktarray til ContactsView med RecyclerView.
Tilføjelse af en visning
ViewGroup tegner ikke automatisk igen, men følger en bestemt tidsplan. Det kan være tilfældet på din enhed, at den tegner igen hvert 10. eller 100. ms; eller, hvis vi vælger et absurd tal, f.eks. 1 minut, vil du se ændringerne 1 minut senere, når vi tilføjer en ViewGroup til en ViewGroup, når ViewGroup "opdateres".
RecyclerView.Recycler
Grundlag #3 – Caching
Et af de bedste eksempler på, hvor vi regelmæssigt opdaterer, er i browseren. Lad os for eksempel forestille os, at det websted, vi besøger, er statisk og ikke sender indhold dynamisk; vi ville være nødt til at blive ved med at opdatere for at se ændringerne.
I dette eksempel forestiller vi os, at det pågældende websted er Twitter. Vi ville have en række statiske tweets på listen, og den eneste måde, vi kunne se nye tweets på, ville være ved at klikke på opdateringsknappen for at hente indholdet igen.
Det er naturligvis dyrt at male hele skærmen om. Forestil dig, at vi havde begrænset båndbredde hos vores telefonudbyder, og vores tweet-liste havde en masse billeder og videoer; det ville være dyrt at downloade alt indholdet af siden igen 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 muligheden for at fortælle, hvilke tweets den allerede har. Derfor downloader den ikke alt igen, men henter kun de nye tweets, og den tjekker også, om et 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 praksis siger vi ikke bare "vi vil gerne indlæse dit websted", vi siger "vi vil gerne indlæse dit websted, og her er noget af det indhold, vi allerede har gemt fra sidste gang, vi indlæste det. Brug det venligst til kun at sende os det, der ikke er derinde, så vi ikke bruger meget båndbredde."
Layoutopkald – Tweetlisten må være skør
Et eksempel på et layoutkald er scrollToPosition. Dette er et almindeligt eksempel, der findes i ting som chat-apps. Hvis nogen i en chattråd svarer på en chatboble fra tidligere, inkluderer nogle chat-apps svaret og et link til chatboblen, som, når du klikker, navigerer dig til, hvor din oprindelige besked var.
I det tilfælde, hvor vi kalder 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
Grundlag #4 – Tilbagekald
RecyclerView har mange bevægelige dele i udførelsen af sit arbejde. Den skal håndtere LayoutManager, som fortæller os, hvordan vi organiserer visningerne, enten lineært eller i et gitter. Den skal håndtere en adapter, der konverterer vores elementer (contactList) til visninger (OneContactView) og derefter til ViewHolders (OneContactViewHolder), som RecyclerView er villig til at arbejde med ved hjælp af de metoder, den stiller til rådighed.
Råmaterialet til RecyclerView er vores Views, f.eks. OneContactView, og en 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 basisscenarie for, hvornår vi har et statisk array på 1000 kontakter, som 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 er vi nødt til at tænke over, hvad der sker med View 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 visninger skal layoutes, enten lineært eller i et gitter, udfører LayoutManager en masse arbejde under motorhjelmen i hel.ping Genbrugsvirksomheden ved, hvornår de skal genbruge.
Det er ansvarligt for at holdeping track af de visninger, der aktuelt er synlige på skærmen, og kommunikerer denne information til genbrugsmekanismen. Når en bruger ruller nedad, er LayoutManager ansvarlig for at informere genbrugssystemet om de visninger, der mister fokus øverst, så de kan genbruges i stedet for at blive der og bruge hukommelse eller blive ødelagt for blot at oprette nye.
Det betyder, at LayoutManager skal holde track af, hvor brugeren er, når de ruller gennem vores liste. Dette gøres ved at have en liste over positioner, der er indeksbaserede, dvs. det første element starter fra 0 og øges for at matche antallet af elementer på vores liste.
Hvis vi kan se 10 elementer på vores liste på, lad os sige, 100, er LayoutManageren i starten klar over, at den har fokus fra Visning-0 helt op til Visning-9. Når vi ruller, kan LayoutManageren beregne de visninger, der kommer ud af fokus.
LayoutManager kan 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 et af de mest almindelige anvendelsesscenarier for at bruge en RecyclerView er med dynamiske lister, hvor data kan komme fra et online endpoint eller måske endda fra en sensor. Ikke alene tilføjes data, men data på vores liste fjernes eller opdateres nogle gange.
Den dynamiske tilstand af vores data kan gøre det meget vanskeligt at ræsonnere om LayoutManager. Af denne grund vedligeholder LayoutManager sin egen liste over elementer og positioner, adskilt fra den liste, som genbrugskomponenten bruger. Dette sikrer, at den udfører sit layout-job korrekt.
Samtidig ønsker LayoutManager i RecyclerView ikke at give en forkert fremstilling af de data, den har. For at fungere korrekt synkroniserer LayoutManager med RecyclerView.Adapter med givne intervaller (60 ms) og deler information om vores listeelementer, dvs. elementer, der er tilføjet, opdateret, fjernet eller flyttet fra én position til en anden. Når LayoutManager modtager disse oplysninger, omorganiserer den indholdet på skærmen, så det matcher ændringerne, når det er nødvendigt.
Mange af kerneoperationerne, der omhandler RecyclerView, roterer omkring kommunikationen mellem RecyclerView.LayoutManager og RecyclerView.Adapter, der lagrer vores lister med sommetider statiske eller sommetider 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 (f.eks. en kontakt) til en ViewHolder, så den bruges til at vise informationen på skærmen.
En anden er onCreateViewHolder, som fortæller os, hvornår RecyclerView.Adapter tager en almindelig visning som OneContactView og konverterer den til et ViewHolder-element, som RecyclerView kan arbejde med.
Udover de centrale mekanismer, der muliggør genbrug, giver RecyclerView mulighed for at tilpasse adfærd uden at påvirke genbrug. Genbrug af visninger gør det vanskeligt at udføre almindelige ting, vi er vant til at gøre med statiske visninger, såsom at reagere på onClick-hændelser.
Som vi er klar over, er RecyclerView.LayoutManager der præsenterer visningerne for brugeren kan et øjeblik have en anden liste over elementer fra RecyclerView.Adapter der har listen gemt i en database eller streamet ind fra en kilde. At placere OnClick-hændelser direkte på Views kan føre til uventet adfærd, såsom sletning af den forkerte kontakt.
Gradle
Hvis vi vil bruge RecyclerView, skal vi tilføje det som en afhængighed i vores build.gradle-fil. I følgende eksempel har vi brugt implementeringen "androidx.recyclerview:recyclerview:1.1.0", som er den seneste version ifølge denne artikel.
Efter at have tilføjet afhængigheden til vores Gradle fil, bliver vi bedt om af Android Studio for at synkronisere ændringerne. Sådan gør vores Gradle Filen vil se 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 bliver at præsentere denne liste på skærmen ved hjælp af en RecyclerView. For at gøre dette skal vi navigere til layoutmappen, der indeholder vores layouts, og oprette en View, der skal 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 TextView ovenfor har vi tilføjet et id-felt, der skal bruges til at identificere en View. Det genereres ikke som standard. Vi har givet vores TextView id'et fruitName, så det matcher de data, som vil blive bundet til den.
Tilføjelse af RecyclerView til hovedlayoutet
I den samme aktivitet er der en main_layout.xml layoutfil, der som standard blev genereret for os. Hvis vi valgte et tomt projekt, vil det have genereret en XML indeholder et ConstraintLayout, og indeni vil der være en TextView med teksten "Hello". Vi sletter alt indholdet, og layoutet indeholder kun 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å tilføjet en id-attribut til RecyclerView, som vi vil bruge til at referere til den i vores kode.
android:id="@+id/fruitRecyclerView"
Vi navigerer derefter tilbage til vores MainActivity-fil. Ved hjælp af de id'er, vi har oprettet, vil vi kunne referere til de Views, vi lige har oprettet. Vi starter med at referere til RecyclerView ved hjælp af findViewById()-metoden leveret af AndroidVi 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 opretter vi en RecyclerView.ViewHolder, som er ansvarlig for at tage vores View og konvertere den til en ViewHolder, som RecyclerView bruger til at vise vores elementer. Vi gør dette lige efter vores 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 opretter vi en FruitArrayAdapter-klasse, der udvider RecyclerView.Adapter-klassen. Den FruitArrayAdapter, vi opretter, vil være ansvarlig for følgende: den vil tage frugtnavne fra frugtarrayet, oprette en ViewHolder ved hjælp af vores view one_fruit_view.xml, derefter binde frugten til en ViewHolder og dynamisk binde indholdet til den view, vi oprettede.
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 metoder, som RecyclerView kan bruge til at forbinde vores array 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 nemmeste del af den genererede kode, metoden getItemCount(). Vi ved, hvordan man får antallet af elementer i vores array ved at kalde egenskaben array size.
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 onCreateViewHolder. Det er her, RecyclerView beder os om at hjælpe den med at konstruere en FruitViewHolder. Hvis vi husker det korrekt, så vores FruitViewHolder-klasse således ud:
class FruitViewHolder(fruitView: View): RecyclerView.ViewHolder(fruitView)
Det kræver vores fruitView, som vi har oprettet som en XML-fil one_fruit_view.xml. Vi kan oprette en reference til denne XML og konvertere den til en View på følgende måde.
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 onBindViewHolder-overstyringen.
fun onBindViewHolder(holder: FruitViewHolder, position: Int)
RecyclerView.Adapter spørger med et positionsheltal, som vi bruger til at hente et element fra vores liste. Det giver os også en holder, så vi kan binde det element, vi får fra fruitArray'et, til den visning, der er indeholdt i visningsholderen. Den visning, der er indeholdt i en ViewHolder, er tilgængelig via feltet ViewHolder.itemView. Når vi har fået visningen, kan vi bruge id'et fruitName, som 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) }
Med det er vores FruitArrayAdapter færdig 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 forbinde de resterende dele af vores RecyclerView, som opretter en LayoutManager, der fortæller RecyclerView, hvordan indholdet af listen skal vises, om det skal vises lineært ved hjælp af LinearLayoutManager eller i et gitter ved hjælp af GridLayoutManager eller StaggeredGridLayoutManager.
Opret Layout Manager
Vi går tilbage til vores onCreate-funktion og tilføjer 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 givet den et array af frugtnavne. Og stort set er vi 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) } } }




