Android RecyclerView: Vad är, lär dig med enkla exempel
Vad är RecyclerView i Android?
Smakämnen RecyclerView är en widget som är mer flexibel och avancerad version av GridView och ListView. Det är en behållare för att visa stora datamängder som kan rullas effektivt genom att behålla ett begränsat antal vyer. Du kan använda RecyclerView-widgeten när du har datasamlingar vars element ändras vid körning beror på nätverkshändelse eller användaråtgärd.
Visningar
Smakämnen Android plattformen använder klasserna View och ViewGroup för att rita objekt på skärmen. Dessa klasser är abstrakta och utökas till olika implementeringar för att passa ett användningsfall. TextView har till exempel ett enkelt syfte att visa textinnehåll på skärmen. EditText sträcker sig från samma View-klass och lägger till mer funktionalitet för att användaren ska kunna mata in data.
Det är möjligt att skapa egna anpassade vyer för att kunna uppnå mer flexibilitet vid utveckling av användargränssnitt. View-klassen tillhandahåller metoder som vi kan åsidosätta för att rita på skärmen och ett sätt att skicka in parametrar som bredd, höjd och våra egna anpassade attribut som vi skulle vilja lägga till i vår vy för att få den att fungera som vi skulle vilja.
Visa grupper
ViewGroup-klassen är en sorts View, men till skillnad från den enkla View-klassen vars ansvar helt enkelt är att visa, ger ViewGroup oss möjligheten att lägga flera vyer i en vy, som vi kan referera till som helhet. I det här fallet kallas vyn som skapas på översta nivån som vi lägger till andra enkla vyer (vi kan också lägga till visningsgrupper) för "föräldern", och vyerna som läggs till inuti är "barn".
Vi kan avbilda en vy som en Array och en ViewGroup som en Array av Arrayer. Med tanke på att en Array of Arrays är en Array i sig kan vi se hur en ViewGroup kan behandlas 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 gör det också möjligt för oss att definiera hur barnen är organiserade inuti vyn, till exempel läggs våra vertikalt eller horisontellt. Vi kan ha olika regler för interaktion i vyn. Till exempel bör TextViews som följer varandra ha ett avstånd på 12 dp medan ImageViews följt av TextView bör ha ett avstånd på 5 dp.
Detta skulle vara fallet om vi utvecklade vår egen ViewGroup från grunden. För att göra dessa konfigurationer enklare, Android tillhandahåller en klass som heter LayoutParams, som vi kan använda för att mata in dessa konfigurationer.
Android dokumentation tillhandahåller några standardparametrar som vi skulle implementera när vi konfigurerar vår egen ViewGroup. Några vanliga parametrar är sådana som avser bredd, höjd och marginal. Som standard har dessa konfigurationer strukturen android:layout_height för höjd, till exempel android:layout_width. I detta avseende, när du skapar din ViewGroup, kan du ytterligare skapa LayoutParams specifika för hur du vill att din ViewGroup ska bete sig.
Android levereras med standardvyer och visningsgrupper som vi kan använda för att göra många av de vanliga uppgifterna vi behöver. Ett exempel som vi har nämnt är en TextView. Det är en enkel vy som kommer med konfigurerbara aspekter som höjd, bredd, textstorlek och liknande. Vi har en ImageView för att visa bilder och EditText som vi hade nämnt, bland många andra. Android har också anpassade ViewGroups som vi kan lägga till våra Views till och få det förväntade beteendet.
Linjär layout
LinearLayout låter oss lägga till View-objekt i den. LinearLayout är ett orienteringsattribut som dikterar hur det kommer att läggas ut på skärmen. Den har också LinearLayout.LayoutParams som dikterar reglerna för vyerna inuti, till exempel skulle attributet android:center_horizontal centrera vyerna längs den horisontella axeln medan `android:center_vertical skulle centrera innehållet i vyn längs den vertikala axeln.
Här är några bilder för att få en förståelse för centrering. Vi skulle ta detta som en enkel TextView inuti 200px gånger 200px utrymme, centreringsattribut skulle få det att bete sig enligt följande.
android:center_horizontal
android:center_vertical
android:center
Kärnkomponenter i RecyclerView
Följande är de viktiga komponenterna i RecyclerView:
RecyclerView.Adapter
Grundarbete #1 – Adaptermönster
En adapter är en enhet som omvandlar attribut för system eller enhet till de för en annars inkompatibel enhet eller system. Vissa av dem ändrar signal- eller effektattribut, medan andra helt enkelt anpassar den fysiska formen av en kontakt till en annan.
Ett enkelt exempel som vi hittar i verkligheten för att förklara en adapter är när vi behöver koppla ihop enheter, men de har anslutningsportar som inte matchar varandra. Detta kan vara fallet när du besöker ett annat land där de använder olika typer av uttag. Om du bär din telefon eller laptopladdare skulle det vara omöjligt att ansluta den till eluttagen. Du skulle dock inte ge upp utan helt enkelt skaffa en adapter som skulle komma in mellan eluttaget och din laddare och möjliggöra laddning.
Detta är fallet i programmering när vi vill koppla samman två datastrukturer för att utföra uppgiften, men deras standardportar inte har ett sätt att kommunicera med varandra.
Vi kommer att använda det enkla exemplet på en enhet och en laddare. Vi kommer att ha två instanser av laddare. En amerikansk och en brittisk
class AmericanCharger() { var chargingPower = 10 } class BritishCharger(){ var charginPower = 5 }
Vi skapar sedan två enheter
class AmericanDevice() class BritishDevice()
Som ett exempel kan vi sedan skapa några instanser av enheterna för att spela med.
var myAmericanPhone = new AmericanDevice() var myBritishPhone = new BritishDevice()
Vi kommer sedan att introducera begreppet laddning för båda enheterna genom att lägga till en metod i enheterna som kallas charge() .
Metoden tar som ingång sin respektive laddare och gör laddning utifrån 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 det här fallet, baserat på vår analogi, skulle vi av en eller annan anledning vara i behov av att använda en BritishCharger när vi använder en AmericanDevice eller vice versa.
I programmeringsvärlden är det oftast när man blandar ihop bibliotek som erbjuder samma funktionalitet (i vårt sammanhang är vår delade funktionalitet laddar). Vi skulle behöva hitta ett sätt att möjliggöra detta.
Om vi följer analogin måste vi gå till en elektronikaffär och köpa en adapter som vi skulle göra det möjligt för oss att ladda AmericanDevices med BritishChargers. Ur programmeringsperspektiv kommer vi att vara de som kommer att vara tillverkare av adaptern.
Vi kommer att göra en adapter för en som matchar det exakta mönstret vi skulle behöva för att skapa den andra. Vi implementerar det som en klass enligt följande. Det behöver inte nödvändigtvis vara en klass och kan vara en funktion som belyser vad adaptermönstret i allmänhet gör. Vi kommer att använda en klass eftersom den matchar mest användning 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 programmeringsvärlden är skillnaden i uttagen analog med skillnaden i metoderna inuti som används för laddning. Laddarna med olika metoder skulle göra det omöjligt att använda laddarna.
var myBritishDevice = new BritishDevice() var americanChargerIFound = new AmericanCharger()
Att försöka anropa metoden charge() i myBritishDevice med americanChargerIFound skulle inte fungera eftersom AmericanDevice bara accepterar en AmericanCharger
Så det är omöjligt att göra detta
var myBritishDevice = new BritishDevice() var americanChargerIFound = new AmericanCharger() myBritishDevice.charge(americanChargerIFound)
I det här scenariot skapade adaptern
AmericanToBritishChargerAdapter kan nu komma väl till pass. Vi kan använda metoden returnNewCharger() för att skapa en ny BritishCharger, som vi kan använda för att ladda. Allt vi behöver är att skapa en instans av vår adapter och mata den med den AmericanCharger vi har, och det kommer att skapa en BritishCharger som vi kan använda
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 att göra med en ViewGroup skulle vi ha Views placerade inuti den. Det är LayoutManager som skulle ha till uppgift att beskriva hur vyerna är upplagda inuti.
För jämförelseändamål, när vi arbetar med Linearlayout ViewGroup, är användningsfallet vi vill ha möjligheten att placera objekten antingen vertikalt eller horisontellt. Detta implementeras enkelt genom att lägga till ett orienteringsattribut, som berättar hur den linjära layouten kommer att placeras på skärmen. Vi kan göra detta genom att använda android:orientation=VERTICAL|HORIZONTAL
attribut.
Vi har också en annan ViewGroup som heter GridLayout, det är användningsfallet när vi skulle vilja placera vyer i en rektangulär Grid-struktur. Detta kan vara av skäl som att göra den data vi presenterar för appanvändaren lätt att konsumera. Genom designen möjliggör GridLayout konfigurationer för att hjälpa dig att nå detta mål genom att ha konfigurationer där vi kan definiera dimensionerna på rutnätet, till exempel kan vi ha ett 4×4 rutnät, 3 x 2 rutnät.
RecyclerView.ViewHolder
ViewHolder är en abstrakt klass som vi också utökar från RecyclerView. ViewHolder ger oss vanliga metoder för att hjälpa oss att referera till en vy som vi har placerat på RecyclerView även efter att återvinningsmaskineriet i RecyclerView har ändrat olika referenser som vi inte känner till.
Stora listor
RecyclerViews används när vi vill presentera en riktigt stor uppsättning vyer för användaren, samtidigt som vi inte utmattar vår RAM på vår enhet för varje instans av vyn som skapas.
Om vi skulle ta fallet med en kontaktlista, skulle vi ha en allmän uppfattning om hur en kontakt skulle se ut i listan. Vad vi då skulle göra är att skapa en malllayout – som egentligen är en vy – med platser där olika data från vår kontaktlista kommer att fyllas. Följande är en pseudokod som förklarar hela syftet:
//OneContactView <OneContact> <TextView>{{PlaceHolderForName}}</TextView> <TextView>{{PlaceHolderForAddress}}</TextView> <ImageView>{{PlaceHolderForProfilePicture}}</ImageView> <TextView>{{PlaceHolderForPhoneNumber}}</TextView> </OneContact>
Vi skulle då ha en kontaktlista av detta slag
<ContactList> </ContactList>
Om det är fallet, hårdkodade vi innehållet, vi skulle inte ha ett programmatiskt sätt att lägga till nytt innehåll till listan utan att skriva om appen. Lyckligtvis för oss. Att lägga till en vy till en visningsgrupp stöds av en addView(view:View)
metod.
Även om så är fallet, är det inte hur RecyclerView får barnvyer till sig.
I vårt användningsfall skulle vi ha en lång lista med kontakter. För varje kontakt i listan skulle vi behöva skapa OneContactView och fylla i data i vyn för att matcha fälten i vår kontaktklass. Sedan när vi har vyn, skulle vi behöva lägga till den i RecyclerView för att visa listan.
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 rad kontakter som heter OneContactView. Den innehåller platser för att ta innehåll från kontaktklassen och visa dem. I RecyclerView måste vi lägga till Views i den så att den kan hjälpa oss med dess återvinningsförmåga.
RecyclerView tillåter oss inte riktigt att lägga till vy men gör det möjligt för oss att lägga till en ViewHolder. Så i det här scenariot har vi två delar av saker som vi vill ansluta men inte matchar. Det är här vår adapter kommer in. RecyclerView förser oss med en adapter ungefär som vår AmericanToBritishChargerAdapter()
från tidigare som gjorde det möjligt för oss att konvertera vår AmericanCharger som var oanvändbar med vår BritishDevice till något användbart, som liknar strömadaptern i verkligheten.
I det här scenariot skulle adaptern ta vårt utbud av kontakter och vår vy och därifrån generera ViewHolders som RecyclerView är villig att acceptera.
RecyclerView tillhandahåller ett gränssnitt som vi kan utöka för att skapa vår adapter genom klassen RecyclerView.Adapter. Inuti denna adapter finns ett sätt att skapa ViewHolder-klassen som RecyclerView vill arbeta med. Så vad vi har är samma situation som tidigare, men med en extra sak, det är adaptern.
Vi har en rad kontakter, en vy för att visa en kontakt OneContactView. En RecyclerView är en lista över vyer som tillhandahåller återvinningstjänster men som bara är villiga att ta emot ViewHolders
Men i det här scenariot har vi nu klassen RecyclerView.Adapter, som har en metod för att skapa ViewHolders inuti.
fun createViewHolder(@NonNull parent: ViewGroup, viewType: Int): ViewHolder
RecyclerView.ViewHolder är en abstrakt klass som tar vår View som ett argument och konverterar den till en ViewHolder.
Den använder omslagsmönstret som används för att utöka klassernas förmåga.
Grundarbete #2 – Omslagsmönster
Vi använder ett enkelt exempel för att visa hur vi kan få djur att tala.
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 exemplet ovan har vi två djur. Om av en slump ville vi lägga till en metod för att få ordet, men biblioteksförfattaren var inte kul, vi kunde ändå hitta ett sätt. Det vi behöver skulle vara ett omslag till vår djurklass. Vi skulle göra detta genom att ta in Djuret som konstruktör för vår klass
class SpeechPoweredAnimalByWrapper(var myAnimal:Animal){ fun sound(){ myAnimal.sound() } speak(){ println("Hello, my name is ${myAnimal.name}") } }
Nu kan vi överföra i en djurinstans till SpeechPoweredAnimalByWrapper. Att anropa metoden sound() på den skulle anropa den passerade in animal sound()-metoden. Vi har också en extra speak()-metod, som räknas som ny funktionalitet vi lägger till djuren som skickas in. Vi kan använda den enligt följande:
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"
Med det här mönstret kan vi ta klasser och lägga till funktionalitet. Allt vi behöver är att godkänna en klassinstans och nya metoder definierade av vår inpackningsklass.
I vårt fall ovan använde vi en betongklass. Det är också möjligt att implementera detsamma i en abstrakt klass. Vi skulle behöva göra är att lägga till att ändra SpeechPoweredAnimalByWrapper-klassen till abstrakt och vi är klara. Vi ändrar klassnamnet till något kortare för att göra det mer läsbart.
abstract class SpeechPowered(var myAnimal:Animal){ fun sound(){ myAnimal.sound() } speak(){ println("Hello, my name is ${myAnimal.name}") } }
Det är samma som tidigare, men det skulle betyda något annat. I en normal klass kan vi ha en instans av en klass på samma sätt som vi skapade cat1 och dog1. Abstrakta klasser är dock inte avsedda att instansieras utan är avsedda att utöka andra klasser. Så hur skulle vi använda den nya abstrakta klassen SpeechPowered(var myAnimal:Animal). Vi kan använda det genom att skapa nya klasser som utökar det och i sin tur får dets funktionalitet.
I vårt exempel skapar vi en klass SpeechPoweredAnimal som utökar klassen
class SpeechPoweredAnimal(var myAnimal:Animal):SpeechPowered(myAnimal)
var cat1 = Cat("Tubby") var speakingKitty = SpeechPoweredAnimal(cat1) speakingKitty.speak() //"Hello, my name is Tubby"
Detta är samma mönster som används i ViewHolder. Klassen RecyclerView.ViewHolder är en abstrakt klass som lägger till funktionalitet till View, ungefär som vi har lagt till speak-metoden till djuren. Den extra funktionaliteten är det som gör att det fungerar när man arbetar med RecyclerView.
Så här skulle vi skapa en OneContactViewHolder från 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 gör att vi kan ansluta vår Contacts-array till ContactsView med RecyclerView
Lägga till en vy
ViewGroup ritar inte om ViewGroup automatiskt utan följer ett visst schema. Det kan vara så på din enhet att den ritas om var 10:e ms eller 100:e ms, eller om vi väljer ett absurt nummer, säg 1 minut när vi lägger till en vy till en visningsgrupp, så ser du ändringarna 1 minut senare när visningsgruppen "uppdateras".
RecyclerView.Recycler
Markarbete #3. Cachning
Ett av de bästa exemplen på var vi regelbundet gör uppdateringar är i webbläsaren. Låt oss till exempel föreställa oss att webbplatsen vi besöker är statisk och inte skickar innehåll dynamiskt, vi skulle behöva fortsätta uppdatera för att se förändringarna.
För det här exemplet, låt oss föreställa oss att webbplatsen i fråga är twitter. Vi skulle ha en serie statiska tweets listade och det enda sättet vi kunde få se nya tweets skulle vara genom att klicka på uppdateringsknappen för att hämta innehållet igen.
Att måla om hela skärmen är uppenbarligen en kostsam sak. Om vi skulle föreställa oss om det var fallet hade vi begränsad bandbredd hos vår telefonleverantör. Och vår tweetlista hade många bilder och videor, det skulle bli kostsamt att ladda ner allt innehåll på sidan igen vid varje uppdatering.
Vi skulle behöva ett sätt att lagra de redan laddade Tweets och säkerställa att vår nästa begäran har en förmåga att säga de Tweets den redan har. Därför laddar den inte ner allt igen och får bara de nya Tweets den har och kontrollerar även om någon Tweet som hade sparats lokalt inte längre finns där så att den kan radera den lokalt. Det vi beskriver kallas Caching.
Informationen vi skickar till webbplatsen om innehållet vi har kallas metadata. Så i verkligheten säger vi inte bara "vi vill ladda din webbplats", vi säger "vi vill ladda din webbplats, och här är en del av innehållet som vi redan hade sparat från förra gången vi laddade, använd det för att skicka oss bara det som inte finns där, så vi använder inte mycket bandbredd eftersom vi inte har många resurser.”
Layoutsamtal – Tweetlistan måste vara galen
Ett exempel på layoutanrop är scrollToPosition
Detta är ett vanligt exempel som finns i saker som chattappar. Om någon i en chatttråd svarar på en chattbubbla från tidigare, inkluderar vissa chattappar svaret och en länk till chattbubblan, som när du klickar navigerar dig till var ditt ursprungliga meddelande var.
I fallet kallar vi den här metoden innan vi har lagt till en LayoutManager i vår RecyclerView och innan vi har en RecyclerView.Adapter ignoreras scrollToPosition(n:Int) helt enkelt .
Kommunikation mellan RecyclerView-komponenter
Markarbete #4. Återuppringningar
RecyclerView har, i sitt arbete, många rörliga delar. Den måste ta itu med LayoutManager som talar om för oss hur vi organiserar vyerna, antingen linjärt eller i ett rutnät. Den måste ta itu med en adapter som gör jobbet med att konvertera våra objekts kontaktlista till Views OneContactView och sedan till ViewHolders OneContactViewHolder som RecyclerView är villig att arbeta inom de metoder som den tillhandahåller för oss.
Råmaterialet för RecyclerView är våra vyer t.ex. OneContactView och datakälla.
contactList:Array<Contact>
Vi har använt ett enkelt scenario som utgångspunkt för att få en känsla av vad RecyclerView försöker uppnå.
Ett basfall på när vi skulle ha en statisk uppsättning av 1000 kontakter som vi vill visa användaren är lätt att förstå.
RecyclerView-maskineriet börjar verkligen ta liv när listan inte längre är statisk.
Med en dynamisk lista måste vi tänka på vad som händer med vyn på skärmen när vi lägger till ett objekt i listan eller tar bort ett objekt från listan.
RecyclerView.LayoutManager
Förutom att bestämma hur våra åsikter ska läggas upp antingen linjärt eller i ett rutnät. LayoutManager gör mycket arbete under huven för att hjälpa återvinningsföretaget att veta när den ska göra återvinningen.
Det är ansvarigt för att hålla reda på de vyer som för närvarande är synliga på skärmen och att kommunicera till denna information till återvinningsmekanismen. När en användare scrollar nedåt ansvarar Layouthanteraren för att informera återvinningssystemet om de vyer som går ur fokus på toppen så att de kan återanvändas istället för att de stannar där och konsumerar minne eller istället för att förstöra dem och måste skapa nya.
Vad detta betyder är att LayoutManager måste hålla reda på var användaren är när de rullar i vår lista. Den gör detta genom att ha en lista med positioner som är indexbas, dvs den första posten är att börja från 0 och öka för att matcha antalet poster i vår lista.
Om vi kan se 10 objekt på vår lista med säg 100, i början, är LayoutManager medveten om att den har i fokus view-0 hela vägen till View-9 När vi rullar kan LayoutManager beräkna vyerna som kommer ut av fokus.
LayoutManager kan släppa dessa vyer till återvinningsmekanismen så att de kan återanvändas (ny data kan bindas till dem, t.ex. kan en vys kontaktdata tas bort och ny kontaktdata från nästa segment kan ersätta platshållarna).
Detta skulle vara ett lyckligt fall om listan vi har är statisk, men ett av de vanligaste användningsfallen för att använda en RecyclerView är med dynamiska listor där data kan komma från en online-slutpunkt eller till och med kanske från en sensor. Data läggs inte bara till, utan data på vår lista tas ibland bort eller uppdateras.
Det dynamiska tillståndet för vår data kan göra det mycket svårt att resonera om LayoutManager. Av denna anledning upprätthåller LayoutManager en egen lista över objekten och positionerna, som är separat från listan som återvinningskomponenten använder. Detta säkerställer att den gör sitt layoutjobb korrekt.
Samtidigt vill RecyclerViews LayoutManager inte förvränga den data den har. För att fungera korrekt synkroniserar LayoutManager med RecyclerView.Adapter med givna intervall (60ms) och delar information om våra listobjekt. dvs objekt som har lagts till, uppdaterats, tagits bort, flyttats från en position till en annan). När LayoutManager får denna information, omorganiserar innehållet på skärmen för att matcha ändringarna när det är nödvändigt.
Många av kärnverksamheterna som handlar om RecylerView roterar kring kommunikation mellan RecyclerView.LayoutManager och RecyclerView.Adapter som lagrar våra listor med ibland statisk eller ibland dynamisk data.
Dessutom ger RecyclerView oss metoder som vi kan använda för att lyssna på händelser som onBindViewHolder när vår RecyclerView.Adapter binder innehåll från vår lista, t.ex. en kontakt till en ViewHolder så att den nu vänjer sig vid att visa informationen på skärmen.
En annan är onCreateViewHolder, som talar om för oss när RecyclerView. Adaptern tar en vanlig vy som OneContactView och konverterar den till ett ViewHolder-objekt som RecyclerView kan arbeta med. Från vår ViewHolder för användning av RecyclerView. onViewDetached
Förutom kärnmekanismerna som möjliggör återvinning. RecyclerView tillhandahåller sätt att anpassa beteendet utan att påverka återvinningen.
Återanvändning av vyer gör det svårt att göra vanliga saker som vi är vana vid att göra med statiska vyer som att reagera på onClick-händelser.
Eftersom vi är medvetna om att RecyclerView.LayoutManager
som presenterar vyerna för användaren kan för ett ögonblick ha en annan lista över objekt från RecyclerView.Adapter
som har listan som vi har lagrat i en databas eller strömmar in från en källa. Att lägga OnClick-händelser direkt på Views kan leda till oväntat beteende, som att ta bort fel kontakt eller ändra.
Gradle
Om vi vill använda RecyclerView måste vi lägga till det som ett beroende i vår build .gradle-fil.
I följande exempel har vi använt implementeringen "androidx.recyclerview:recyclerview:1.1.0" som är den senaste versionen enligt denna artikel.
Efter att ha lagt till beroendet till vår Gradle fil kommer vi att uppmanas av Android Studio till Syncharmonisera förändringarna,
Så här är vår Gradle filen kommer att se ut efter att ha lagt till en RecyclerView i ett tomt projekt med bara standardinställningarna.
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 bara en layoutfil för tillfället. Vi börjar med ett enkelt exempel där vi använder en RecyclerView för att visa en lista med fruktnamn på skärmen.
Lista av föremål
Vi navigerar till vår MainActivity-fil och skapar en array med fruktnamn inuti precis före onCreate()-metoden som genererades under installationen.
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 nästa mål blir att presentera den här listan på skärmen med hjälp av en RecyclerView.
För att göra detta navigerar vi till layoutkatalogen som innehåller våra layouter och skapar en vy som ansvarar för att visa en frukt.
Layout som ska användas för varje objekt i vår lista
<?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 textvyn ovan har vi lagt till ett id-fält som kommer att användas för att identifiera en vy.
Den genereras inte som standard. Vi har vår TextView id fruitName för att matcha data, som kommer att bindas till den.
Lägger till RecyclerView till huvudlayouten
I samma aktivitet finns layoutfilen main_layout.xml som genererades åt oss som standard.
Om vi väljer ett tomt projekt. Det kommer att ha genererat en XML som innehåller en ConstraintLayout och inuti kommer att finnas en TextView med "Hej" text.
Vi tar bort allt innehåll och har layouten endast innehålla RecyclerView enligt nedan:
<?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 också lagt till ett id-attribut för RecyclerView som vi kommer att använda för att referera till det i vår kod.
android:id="@+id/fruitRecyclerView"
Vi kommer sedan att navigera tillbaka till vår MainActivity-fil. Med hjälp av de id vi har skapat kommer vi att kunna referera till de vyer vi just har skapat.
Vi börjar med att referera till RecyclerView med metoden findViewById() som tillhandahålls av Android. Vi kommer att göra detta i vår onCreate()-metod.
Vår onCreate()-metod kommer att se ut som följer.
override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContentView(R.layout.activity_main) var myFruitRecyclerView: RecyclerView = findViewById(R.id.fruitRecyclerView) }
Skapa en ViewHolder
Därefter kommer vi att skapa en RecyclerView.ViewHolder som är ansvarig för att ta vår vy och konvertera den till en ViewHolder, som RecyclerView använder för att visa våra föremål.
Vi kommer att göra detta direkt efter vår roliga onCreate()-metod.
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) }
Skapa en RecyclerViewAdapter
Därefter kommer vi att skapa en FruitArrayAdapter-klass som utökar klassen RecyclerView.Adapter.
FruitArrayAdaptern vi skapar kommer att ansvara för att göra följande.
Det kommer att ta fruktnamn från fruktarrayen. Den kommer att skapa en ViewHolder med vår vy one_fruit_view.xml Den kommer sedan att binda frukten till en ViewHolder och dynamiskt binda innehållet till den vy vi skapade 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 kommer att lägga till röda squiggles på vår FruitArrayAdapter, som talar om för oss att vi måste implementera en metod som RecyclerView kan använda för att ansluta vår array till en ViewHolder som RecyclerView kan använda.
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 börjar med att den enklaste biten från den genererade koden är metoden getItemCount(). Vi vet hur man får fram antalet objekt i vår array genom att anropa 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) { } }
Sedan kommer vi att implementera metoden åsidosätta fun onCreateViewHolder.
Det är här som RecyclerView ber oss hjälpa den att konstruera en FruitHolder för den.
Om vi minns så här såg vår FruitViewHolder-klass ut:
class FruitViewHolder(fruitView: View): RecyclerView.ViewHolder(fruitView)
Det kräver vår fruitView som vi skapade som en xml-fil one_fruit_view.xml
Vi kan skapa en referens till denna xml och konvertera den till en vy enligt följande.
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 återstående biten är åsidosättningen
fun onBindViewHolder(holder: FruitViewHolder, position: Int)
RecyclerView.Adapter frågar med ett positionsheltal, som vi använder för att hämta ett objekt från vår lista. Den förser oss också med en hållare så att vi kan binda objektet vi får från fruitArrayen till vyn som hålls inne i vyhållaren.
Vyn som hålls inuti en ViewHoder är tillgänglig via fältet ViewHolder.itemView. När vi väl har fått vyn kan vi använda id fruitName vi skapade tidigare för att ställa in innehållet.
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 är vår FruitArrayAdapter komplett och ser ut som följer.
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) } }
Äntligen är vi redo att koppla ihop de återstående delarna av vår RecyclerView. Som skapar en LayoutManager, som kommer att berätta för RecyclerView hur man visar innehållet i listan. Om det ska visas på ett linjärt sätt med LinearLayoutManager eller i ett rutnät med GridLayoutManager eller StaggeredGridLayoutManager.
Skapa layouthanterare
Vi kommer att gå tillbaka in i vår onCreate-funktion och lägga till 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 }
Anslut vår adapter till föremål och ställ in 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 också skapat en fruitListAdapter-instans och matat den med en mängd fruktnamn.
Och i princip är vi alla klara.
Den fullständiga filen MainActivity.kt ser ut som följer.
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) } } }