Android RecyclerView: Što je, naučite na jednostavnim primjerima
U čemu je RecyclerView Android?
Korištenje električnih romobila ističe RecyclerView je widget koji je fleksibilnija i naprednija verzija GridView-a i ListView-a. To je spremnik za prikaz velikih skupova podataka koji se mogu učinkovito pomicati održavanjem ograničenog broja prikaza. Widget RecyclerView možete koristiti kada imate zbirke podataka čiji se elementi mijenjaju tijekom izvođenja ovise o mrežnom događaju ili radnji korisnika.
Posjeta
Korištenje električnih romobila ističe Android platforma koristi klase View i ViewGroup za crtanje stavki na ekranu. Ove su klase apstraktne i proširene su na različite implementacije kako bi odgovarale slučaju upotrebe. TextView, na primjer, ima jednostavnu svrhu prikazivanja tekstualnog sadržaja na ekranu. EditText proteže se od iste klase View i dodaje više funkcionalnosti kako bi korisniku omogućio unos podataka.
Moguće je stvoriti naše vlastite prilagođene poglede kako bismo postigli veću fleksibilnost pri razvoju korisničkih sučelja. Klasa View pruža metode koje možemo nadjačati za crtanje na ekranu i sredstva za prosljeđivanje parametara kao što su širina, visina i vlastiti prilagođeni atributi koje bismo željeli dodati našem Viewu kako bi se ponašao onako kako želimo.
ViewGroups
Klasa ViewGroup je vrsta View ali, za razliku od jednostavne klase View čija je odgovornost jednostavno prikazivanje, ViewGroup nam daje mogućnost stavljanja više pogleda u jedan pogled, na koji možemo referirati kao na cjelinu. U ovom slučaju, View koji je kreiran na najvišoj razini kojoj dodajemo druge jednostavne poglede (možemo dodati i ViewGroups) naziva se “roditelj”, a pogledi koji se dodaju unutra su “djeca”.
Pogled možemo zamisliti kao niz, a ViewGroup kao niz nizova. S obzirom da je niz nizova sam niz, možemo vidjeti kako se ViewGroup može tretirati kao 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 nam također omogućuje da definiramo kako su djeca organizirana unutar prikaza, na primjer, postavljena okomito ili vodoravno. Možemo imati različita pravila za interakciju unutar Pogleda. Na primjer, TextViews koji slijede jedan za drugim trebali bi imati udaljenost od 12dp dok bi ImageViews iza kojih slijedi TextView trebao imati udaljenost od 5dp.
To bi bio slučaj da razvijamo vlastitu ViewGroup od nule. Da biste olakšali ove konfiguracije, Android pruža klasu pod nazivom LayoutParams, koju možemo koristiti za unos ovih konfiguracija.
Android dokumentacija pruža neke zadane parametre koje bismo implementirali kada konfiguriramo vlastitu ViewGroup. Neki uobičajeni parametri su oni koji se odnose na širinu, visinu i marginu. Prema zadanim postavkama, ove konfiguracije imaju strukturu android:layout_height za visinu, na primjer, android:layout_width U tom smislu, kada stvorite svoju ViewGroup, možete dodatno izraditi LayoutParams specifične za način na koji želite da se vaša ViewGroup ponaša.
Android dolazi sa zadanim Views i ViewGroups koje možemo koristiti za obavljanje mnogih uobičajenih zadataka koji su nam potrebni. Jedan primjer koji smo spomenuli je TextView. To je jednostavan pogled koji dolazi s podesivim aspektima kao što su visina, širina, veličina teksta i slično. Imamo ImageView za prikaz slika i EditText kao što smo spomenuli, između mnogih drugih. Android također ima prilagođene ViewGroups kojima možemo dodati svoje Views i dobiti očekivano ponašanje.
Linearni raspored
LinearLayout nam omogućuje da u njega dodamo View stavke. LinearLayout je atribut orijentacije koji diktira kako će biti postavljen na zaslonu. Također ima LinearLayout.LayoutParams koji diktiraju pravila za unutarnje poglede, na primjer, atribut android:center_horizontal bi centrirao poglede duž vodoravne osi, dok bi `android:center_vertical centrirao sadržaj pogleda duž okomite osi.
Evo nekoliko slika za razumijevanje centriranja. Uzeli bismo da je ovo jednostavan TextView unutar prostora veličine 200 x 200 piksela, a atributi centriranja učinili bi da se ponaša na sljedeći način.
android:centar_horizontalno
android:centar_vertical
android:centar
Osnovne komponente RecyclerViewa
Slijede važne komponente RecyclerViewa:
RecyclerView.Adapter
Temelj #1 – uzorak adaptera
Adapter je uređaj koji pretvara atribute sustava ili uređaja u one inače nekompatibilnog uređaja ili sustava. Neki od njih mijenjaju atribute signala ili snage, dok drugi jednostavno prilagođavaju fizički oblik jednog konektora drugom.
Jednostavan primjer koji nalazimo u stvarnom životu za objašnjenje adaptera je kada trebamo spojiti uređaje zajedno, ali oni imaju spojne priključke koji se međusobno ne podudaraju. To bi mogao biti slučaj kada posjetite drugu zemlju u kojoj se koriste različite vrste utičnica. Ako nosite punjač za telefon ili prijenosno računalo, bilo bi ga nemoguće spojiti na utičnicu. Međutim, ne biste odustali nego biste jednostavno nabavili adapter koji bi došao između utičnice i vašeg punjača i omogućio punjenje.
To je slučaj u programiranju kada želimo spojiti dvije podatkovne strukture kako bismo ispunili zadatak, ali njihovi zadani portovi nemaju način međusobne komunikacije.
Koristit ćemo se jednostavnim primjerom uređaja i punjača. Imat ćemo dva primjerka punjača. Jedan američki i jedan britanski
class AmericanCharger() { var chargingPower = 10 } class BritishCharger(){ var charginPower = 5 }
Zatim ćemo izraditi dva uređaja
class AmericanDevice() class BritishDevice()
Kao primjer, tada možemo stvoriti neke instance uređaja za igru.
var myAmericanPhone = new AmericanDevice() var myBritishPhone = new BritishDevice()
Zatim ćemo predstaviti koncept punjenja za oba uređaja dodavanjem metode u uređaje pod nazivom charge() .
Metoda uzima kao ulaz svoj odgovarajući punjač i vrši punjenje na temelju njega.
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 } }
U ovom slučaju, na temelju naše analogije, iz ovog ili onog razloga trebat ćemo koristiti BritishCharger kada koristimo AmericanDevice ili obrnuto.
U svijetu programiranja to je obično kada se zajedno miješaju biblioteke koje nude istu funkcionalnost (u našem kontekstu, naša zajednička funkcionalnost se naplaćuje). Morali bismo pronaći način da to omogućimo.
Ako slijedimo analogiju, morat ćemo otići u trgovinu elektronikom i kupiti adapter koji bi nam omogućio punjenje američkih uređaja s britanskim punjačima. Iz programske perspektive, mi ćemo biti ti koji će biti proizvođači adaptera.
Napravit ćemo adapter za jedan koji odgovara točnom uzorku koji bi nam trebao za izradu drugog. Implementirat ćemo ga kao klasu na sljedeći način. To ne mora nužno biti klasa i može biti funkcija koja ističe što uzorak adaptera općenito radi. Koristit ćemo klasu koja odgovara većini upotrebe na 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 } }
U svijetu programiranja, razlika u utičnicama je analogna razlici u metodama koje se koriste za punjenje. Punjači koji imaju različite metode onemogućili bi korištenje punjača.
var myBritishDevice = new BritishDevice() var americanChargerIFound = new AmericanCharger()
Pokušaj pozivanja metode charge() u myBritishDevice s americanChargerIFound ne bi funkcionirao jer AmericanDevice prihvaća samo AmericanCharger
Dakle, nemoguće je to učiniti
var myBritishDevice = new BritishDevice() var americanChargerIFound = new AmericanCharger() myBritishDevice.charge(americanChargerIFound)
U ovom scenariju adapter koji smo izradili
AmericanToBritishChargerAdapter sada može dobro doći. Možemo koristiti metodu returnNewCharger() za stvaranje novog BritishChargera, koji možemo koristiti za punjenje. Sve što trebamo je stvoriti primjerak našeg adaptera i nahraniti ga AmericanChargerom koji imamo, a on će stvoriti BritishCharger koji možemo koristiti
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
Kad se radi o ViewGroup-u, unutar nje bismo imali postavljene View-ove. LayoutManager bi imao zadatak opisati kako su Views postavljeni unutra.
Za potrebe usporedbe, kada radimo s Linearlayout ViewGroup, slučaj upotrebe koji želimo je mogućnost postavljanja stavki okomito ili vodoravno. To se lako implementira dodavanjem atributa orijentacije, koji nam govori kako će linearni raspored biti postavljen na ekranu. To možemo učiniti korištenjem android:orientation=VERTICAL|HORIZONTAL
atribut.
Također imamo još jednu ViewGroup koja se zove GridLayout, to je slučaj kada želimo postaviti Views u pravokutnu Grid strukturu. To bi moglo biti iz razloga kao što je olakšavanje upotrebe podataka koje prikazujemo korisniku aplikacije. Dizajnom, GridLayout omogućuje konfiguracije koje vam pomažu u postizanju ovog cilja tako što imaju konfiguracije u kojima možemo definirati dimenzije mreže, na primjer, možemo imati mrežu 4×4, mrežu 3 x 2.
RecyclerView.ViewHolder
ViewHolder je apstraktna klasa koju također proširujemo iz RecyclerView-a. ViewHolder nam pruža uobičajene metode koje nam pomažu referencirati View koji smo postavili na RecyclerView čak i nakon što je stroj za recikliranje u RecyclerViewu promijenio razne reference za koje ne znamo.
Veliki popisi
RecyclerViews se koriste kada korisniku želimo predstaviti stvarno velik skup Viewsa, a da pritom ne iscrpljujemo RAM na našem uređaju za svaku stvorenu instancu Prikaza.
Kad bismo uzeli popis kontakata, imali bismo opću ideju o tome kako bi jedan kontakt izgledao na popisu. Ono što bismo tada učinili jest stvoriti izgled predloška – koji je zapravo Pogled – s utorima u koje će se popuniti različiti podaci s našeg popisa kontakata. Slijedi pseudo kod koji objašnjava cijelu svrhu:
//OneContactView <OneContact> <TextView>{{PlaceHolderForName}}</TextView> <TextView>{{PlaceHolderForAddress}}</TextView> <ImageView>{{PlaceHolderForProfilePicture}}</ImageView> <TextView>{{PlaceHolderForPhoneNumber}}</TextView> </OneContact>
Tada bismo imali ContactList ove prirode
<ContactList> </ContactList>
Ako je to slučaj, mi smo tvrdo kodirali sadržaj, ne bismo imali programski način dodavanja novog sadržaja na popis bez ponovnog pisanja aplikacije. Na našu sreću. Dodavanje pogleda u ViewGroup podržava an addView(view:View)
metoda.
Čak i ako je to slučaj, to nije način na koji RecyclerView dodaje dječje poglede.
U našem slučaju, imali bismo dugačak popis kontakata. Za svaki kontakt na popisu morali bismo stvoriti OneContactView i popuniti podatke unutar View kako bi odgovarali poljima u našoj klasi Kontakt. Nakon što dobijemo prikaz, trebat ćemo ga dodati u RecyclerView kako bismo prikazali popis.
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)
Imamo niz kontakata pod nazivom OneContactView. Sadrži utore za preuzimanje sadržaja iz klase Kontakt i njihov prikaz. U RecyclerViewu moramo dodati Views kako bi nam mogao pomoći sa svojom sposobnošću recikliranja.
RecyclerView nam zapravo ne dopušta dodavanje prikaza, ali nam omogućuje dodavanje ViewHoldera. Dakle, u ovom scenariju, imamo dva dijela stvari koje želimo povezati, ali se ne podudaraju. Ovdje uskače naš adapter. RecyclerView nam daje adapter sličan našem AmericanToBritishChargerAdapter()
od ranije što nam je omogućilo da pretvorimo naš AmericanCharger koji je bio neupotrebljiv s našim BritishDevice u nešto upotrebljivo, slično adapteru za napajanje u stvarnom životu.
U ovom scenariju, adapter bi uzeo naš niz kontakata i naš pogled, i odatle generirao ViewHoldere koje je RecyclerView voljan prihvatiti.
RecyclerView pruža sučelje koje možemo proširiti za stvaranje našeg adaptera kroz klasu RecyclerView.Adapter. Unutar ovog adaptera nalazi se način za stvaranje klase ViewHolder s kojom RecyclerView želi raditi. Dakle, ono što imamo je ista situacija kao i prije, ali s jednom dodatnom stvari, a to je adapter.
Imamo niz kontakata, pogled za prikaz jednog kontakta OneContactView. RecyclerView je popis Viewsa koji pružaju usluge recikliranja, ali je spreman preuzeti samo ViewHoldere
Ali u ovom scenariju sada imamo klasu RecyclerView.Adapter, koja ima metodu za stvaranje ViewHoldera unutra.
fun createViewHolder(@NonNull parent: ViewGroup, viewType: Int): ViewHolder
RecyclerView.ViewHolder je apstraktna klasa koja uzima naš View kao argument i pretvara ga u ViewHolder.
Koristi obrazac omotača koji se koristi za proširenje mogućnosti klasa.
Temelj #2 – Uzorak omota
Koristit ćemo jednostavan primjer da pokažemo kako možemo natjerati životinje da govore.
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
U gornjem primjeru imamo dvije životinje. Ako bismo slučajno željeli dodati metodu da natjeramo govor, ali autor biblioteke nije bio zabavan, ipak bismo mogli pronaći način. Ono što nam treba je omot za našu klasu životinja. To bismo učinili uzimajući Životinju kao konstruktora za našu klasu
class SpeechPoweredAnimalByWrapper(var myAnimal:Animal){ fun sound(){ myAnimal.sound() } speak(){ println("Hello, my name is ${myAnimal.name}") } }
Sada možemo proslijediti instancu životinje u SpeechPoweredAnimalByWrapper. Pozivanje metode sound() na njoj pozvalo bi proslijeđenu metodu animal sound(). Također imamo dodatnu speak() metodu, koja se računa kao nova funkcionalnost koju dodajemo proslijeđenim životinjama. Možemo je koristiti na sljedeći način:
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"
Koristeći ovaj uzorak, možemo pohađati tečajeve i dodavati funkcionalnost. Sve što bismo trebali je proslijediti instancu klase i nove metode definirane našom klasom za omatanje.
U našem gore navedenom slučaju koristili smo konkretnu klasu. Također je moguće implementirati isto u apstraktnoj klasi. Trebali bismo dodati promjenu klase SpeechPoweredAnimalByWrapper u apstraktnu i gotovi smo. Promijenit ćemo naziv klase u nešto kraće kako bi bilo čitljivije.
abstract class SpeechPowered(var myAnimal:Animal){ fun sound(){ myAnimal.sound() } speak(){ println("Hello, my name is ${myAnimal.name}") } }
Isto je kao i prije, ali značilo bi nešto drugo. U normalnoj klasi možemo imati instancu klase na isti način na koji smo stvorili cat1 i dog1. Apstraktne klase, međutim, nisu namijenjene za instanciranje, već za proširenje drugih klasa. Dakle, kako bismo koristili novu apstraktnu klasu SpeechPowered(var myAnimal:Animal). Možemo ga koristiti stvaranjem novih klasa koje će ga proširiti i zauzvrat dobiti njegovu funkcionalnost.
U našem primjeru, stvorit ćemo klasu SpeechPoweredAnimal koja proširuje klasu
class SpeechPoweredAnimal(var myAnimal:Animal):SpeechPowered(myAnimal)
var cat1 = Cat("Tubby") var speakingKitty = SpeechPoweredAnimal(cat1) speakingKitty.speak() //"Hello, my name is Tubby"
Ovo je isti obrazac koji se koristi u ViewHolderu. Klasa RecyclerView.ViewHolder je apstraktna klasa koja dodaje funkcionalnost Viewu, slično kao što smo životinjama dodali metodu speak. Dodana funkcionalnost je ono što ga čini učinkovitim kada se radi s RecyclerViewom.
Ovako bismo stvorili OneContactViewHolder iz OneContactViewa
//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 ima adapter koji nam omogućuje povezivanje niza kontakata na ContactsView s RecyclerViewom
Dodavanje prikaza
ViewGroup ne iscrtava ViewGroup automatski, već slijedi određeni raspored. Može se dogoditi da se na vašem uređaju ponovno iscrtava svakih 10 ms ili 100 ms, ili ako odaberemo apsurdni broj, recimo 1 minutu kada dodamo View u ViewGroup, vidjet ćete promjene 1 minutu kasnije kada se ViewGroup "osvježi".
RecyclerView.Recycler
Temelj #3. Predmemoriranje
Jedan od najboljih primjera gdje redovito radimo osvježavanja je preglednik. Zamislimo, na primjer, da je stranica koju posjećujemo statična i da ne šalje sadržaj dinamički, morali bismo se stalno osvježavati da bismo vidjeli promjene.
Za ovaj primjer, zamislimo da je dotična stranica twitter. Imali bismo niz statičnih tweetova na popisu i jedini način na koji bismo mogli vidjeti nove tweetove bio bi klik na gumb za osvježavanje kako bismo ponovno dohvatili sadržaj.
Ponovno bojanje cijelog zaslona očito je skupa stvar. Ako bismo zamislili da je to slučaj, imali smo ograničenu propusnost kod našeg pružatelja telefonskih usluga. A naš popis tweetova imao je puno slika i videa, bilo bi skupo ponovno preuzeti sav sadržaj stranice pri svakom osvježavanju.
Trebao bi nam način da pohranimo već učitane Tweetove i osiguramo da naš sljedeći zahtjev ima mogućnost izgovoriti Tweetove koje već ima. Stoga ne preuzima ponovno sve i dobiva samo nove tweetove koje ima, a također provjerava je li neki tweet koji je spremljen lokalno više tamo kako bi ga mogao lokalno izbrisati. Ono što opisujemo zove se predmemoriranje.
Informacije koje šaljemo web stranici o sadržajima koje imamo nazivaju se metapodaci. Dakle, u pravom smislu ne kažemo samo "želimo učitati vašu web-lokaciju", mi kažemo "želimo učitati vašu web-lokaciju, a evo nekih sadržaja koje smo već spremili od zadnjeg učitavanja, upotrijebite ih za šaljite nam samo ono što nije tamo, tako da ne koristimo puno propusnosti jer nemamo mnogo resursa.”
Layout Calls – Tweet lista mora biti luda
Primjer poziva izgleda je scrollToPosition
Ovo je uobičajeni primjer koji je prisutan u stvarima poput aplikacija za chat. Ako netko u nizu chata odgovori na oblačić za chat od ranije, neke aplikacije za chat uključuju odgovor i vezu na oblačić za chat, koji vas nakon klika vodi do mjesta gdje je bila vaša izvorna poruka.
U tom slučaju, pozivamo ovu metodu prije nego što smo dodali LayoutManager u naš RecyclerView i prije nego što imamo RecyclerView.Adapter, scrollToPosition(n:Int) se jednostavno zanemaruje.
Komunikacija između RecyclerView komponenti
Temelj #4. Povratni pozivi
RecyclerView, dok radi svoj posao, ima mnogo pokretnih dijelova. Mora se nositi s LayoutManagerom koji nam govori kako organizirati poglede, bilo linearno ili u mreži. Mora se nositi s adapterom koji obavlja posao pretvaranja naših stavki contactList u Views OneContactView i zatim u ViewHolders OneContactViewHolder s kojim je RecyclerView voljan raditi unutar metoda koje nam pruža.
Sirovi materijal za RecyclerView su naši Views npr. OneContactView i izvor podataka.
contactList:Array<Contact>
Koristili smo jednostavan scenarij kao početnu točku da dobijemo dojam o tome što RecyclerView pokušava postići.
Lako je razumjeti osnovni slučaj kada bismo imali statički niz od 1000 kontakata koje želimo pokazati korisniku.
RecyclerView strojevi stvarno počinju oduzimati život kada popis više nije statičan.
S dinamičkim popisom moramo razmišljati o tome što se događa s prikazom na zaslonu kada dodamo stavku na popis ili uklonimo stavku s popisa.
RecyclerView.LayoutManager
Osim odlučivanja kako će naši pogledi biti postavljeni linearno ili u mreži. LayoutManager obavlja puno posla ispod haube pomažući Recycler-u da zna kada treba obaviti Recycling.
Odgovoran je za praćenje Pogleda koji su trenutno vidljivi na Zaslonu i priopćavanje tih informacija mehanizmu za recikliranje. Dok se korisnik pomiče prema dolje, upravitelj izgleda je odgovoran za obavještavanje sustava recikliranja o pogledima koji izlaze iz fokusa na vrhu kako bi se mogli ponovno upotrijebiti umjesto da ostanu tamo i troše memoriju ili umjesto da ih uništi i mora stvoriti nove.
To znači da LayoutManager treba pratiti gdje se korisnik nalazi dok se pomiče po našem popisu. To čini tako što ima popis pozicija koje su baza indeksa, tj. prva stavka počinje od 0 i povećava se kako bi odgovarala broju stavki na našem popisu.
Ako možemo vidjeti 10 stavki na našem popisu od recimo 100, na početku, LayoutManager je svjestan da ima u fokusu view-0 sve do View-9 Dok se pomičemo, LayoutManager može izračunati prikaze koji izlaze fokusa.
LayoutManager može otpustiti te prikaze mehanizmu za recikliranje kako bi se mogli ponovno upotrijebiti (novi podaci se mogu vezati uz njih, npr. podaci o kontaktu pogleda mogu se ukloniti, a novi podaci o kontaktu iz sljedećeg segmenta mogu zamijeniti rezervirana mjesta).
Ovo bi bio sretan slučaj ako je popis koji imamo statičan, ali jedan od najčešćih slučajeva korištenja RecyclerViewa je s dinamičkim popisima gdje podaci mogu doći s online krajnje točke ili čak možda sa senzora. Ne samo da se podaci dodaju, već se podaci s našeg popisa ponekad uklanjaju ili ažuriraju.
Dinamičko stanje naših podataka može jako otežati rasuđivanje o LayoutManageru. Iz tog razloga, LayoutManager održava vlastiti popis o stavkama i pozicijama, koji je odvojen od popisa koji koristi komponenta Recycling. To osigurava da ispravno radi svoj posao rasporeda.
U isto vrijeme, RecyclerView's LayoutManager ne želi lažno predstavljati podatke koje ima. Kako bi ispravno radio, LayoutManager se sinkronizira s RecyclerView.Adapterom u zadanim intervalima (60ms) dijeleći informacije o našim stavkama popisa. tj. stavke dodane, ažurirane, uklonjene, premještene s jedne pozicije na drugu). LayoutManager, po primitku ove informacije, reorganizira sadržaj na ekranu kako bi odgovarao promjenama kada je to potrebno.
Mnoge osnovne operacije koje se bave RecylerViewom rotiraju se oko komunikacije između RecyclerView.LayoutManager i RecyclerView.Adapter koji pohranjuje naše popise ponekad statičnih ili ponekad dinamičkih podataka.
Štoviše, RecyclerView nam predstavlja metode koje možemo koristiti za slušanje događaja kao što je onBindViewHolder kada naš RecyclerView.Adapter veže sadržaj s našeg popisa, npr. kontakt na ViewHolder tako da se sada koristi za prikaz informacija na ekranu.
Drugi je onCreateViewHolder, koji nam govori kada RecyclerView. Adapter uzima regularni prikaz kao što je OneContactView i pretvara ga u stavku ViewHolder s kojom RecyclerView može raditi. Iz našeg ViewHoldera za korištenje od strane RecyclerViewa. onViewDetached
Osim temeljnih mehanizama koji omogućuju Recikliranje. RecyclerView pruža načine za prilagodbu ponašanja bez utjecaja na recikliranje.
Ponovna upotreba prikaza otežava obavljanje uobičajenih stvari koje smo navikli raditi sa statičnim prikazima, kao što je reagiranje na onClick događaje.
Kao što smo svjesni da RecyclerView.LayoutManager
koji korisniku predstavlja Prikaze može na trenutak imati drugačiji popis stavki od RecyclerView.Adapter
koji ima popis koji smo pohranili u bazi podataka ili ga struji iz izvora. Postavljanje OnClick događaja izravno u Views može dovesti do neočekivanog ponašanja, poput brisanja pogrešnog kontakta ili promjene.
Gradle
Ako želimo koristiti RecyclerView, moramo ga dodati kao ovisnost u našoj build .gradle datoteci.
U sljedećem primjeru upotrijebili smo implementaciju "androidx.recyclerview:recyclerview:1.1.0" koja je najnovija verzija prema ovom članku.
Nakon dodavanja ovisnosti našem Gradle datoteku, bit ćemo upitani Android Studio do Synchronizirati promjene,
Ovako je naš Gradle izgledat će nakon dodavanja RecyclerViewa u prazan projekt samo sa zadanim postavkama.
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" }
Trenutačno imamo samo jednu datoteku izgleda. Počet ćemo s jednostavnim primjerom u kojem ćemo koristiti RecyclerView za prikaz popisa naziva voća na zaslonu.
Popis predmeta
Doći ćemo do naše datoteke MainActivity i stvoriti niz s imenima voća unutar neposredno prije metode onCreate() koja je generirana tijekom postavljanja.
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) } }
Naš sljedeći cilj bit će prikazati ovaj popis na zaslonu pomoću RecyclerViewa.
Da bismo to učinili, otići ćemo do direktorija rasporeda koji sadrži naše izglede i stvoriti prikaz koji će biti odgovoran za prikazivanje jednog voća.
Izgled koji će se koristiti za svaku stavku na našem popisu
<?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>
U TextView iznad, dodali smo ID polje koje će se koristiti za identifikaciju View.
Ne generira se prema zadanim postavkama. Imamo naš TextView id fruitName da odgovara podacima koji će biti vezani za njega.
Dodavanje RecyclerView-a u glavni izgled
U istoj aktivnosti postoji datoteka rasporeda main_layout.xml koja je generirana za nas prema zadanim postavkama.
Ako smo odabrali prazan projekt. To će generirati XML koji sadrži ConstraintLayout, a unutra će biti TextView s tekstom "Hello".
Izbrisat ćemo sav sadržaj, a izgled će sadržavati samo RecyclerView kao u nastavku:
<?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" />
Također smo dodali id atribut za RecyclerView koji ćemo koristiti za referencu u našem kodu.
android:id="@+id/fruitRecyclerView"
Zatim ćemo se vratiti na našu datoteku MainActivity. Koristeći ID-ove koje smo stvorili, moći ćemo referencirati View-ove koje smo upravo stvorili.
Počet ćemo referiranjem na RecyclerView pomoću metode findViewById() koju pruža Android. To ćemo učiniti u našoj metodi onCreate().
Naša metoda onCreate() izgledat će ovako.
override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContentView(R.layout.activity_main) var myFruitRecyclerView: RecyclerView = findViewById(R.id.fruitRecyclerView) }
Stvorite ViewHolder
Zatim ćemo stvoriti RecyclerView.ViewHolder koji je odgovoran za preuzimanje našeg Viewa i njegovo pretvaranje u ViewHolder, koji RecyclerView koristi za prikaz naših stavki.
To ćemo učiniti odmah nakon naše zabavne metode onCreate().
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) }
Stvorite RecyclerViewAdapter
Zatim ćemo stvoriti klasu FruitArrayAdapter koja proširuje klasu RecyclerView.Adapter.
FruitArrayAdapter koji kreiramo bit će odgovoran za sljedeće.
Uzet će imena voća iz niza voća. Stvorit će ViewHolder koristeći naš pogled one_fruit_view.xml Zatim će povezati voće s ViewHolderom i dinamički vezati sadržaj za View koji smo stvorili 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 će dodati crvene vijuge na naš FruitArrayAdapter, govoreći nam da moramo implementirati metodu koju RecyclerView može koristiti za povezivanje našeg niza s ViewHolderom koji RecyclerView može koristiti.
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) { } }
Počet ćemo s najlakšim dijelom generiranog koda metodom getItemCount(). Znamo kako dobiti broj stavki u našem nizu pozivanjem metode 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) { } }
Zatim ćemo implementirati zabavu nadjačavanja metode onCreateViewHolder.
Ovdje nas RecyclerView traži da mu pomognemo konstruirati FruitHolder za njega.
Ako se prisjetimo, ovako je izgledala naša klasa FruitViewHolder:
class FruitViewHolder(fruitView: View): RecyclerView.ViewHolder(fruitView)
Zahtijeva naš fruitView koji smo kreirali kao xml datoteku one_fruit_view.xml
Možemo stvoriti referencu na ovaj xml i pretvoriti ga u prikaz na sljedeći način.
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) { } }
Preostali bit je nadjačavanje
fun onBindViewHolder(holder: FruitViewHolder, position: Int)
RecyclerView.Adapter pita s cijelim brojem pozicije, koji ćemo koristiti za dohvaćanje stavke s našeg popisa. Također nam daje držač tako da možemo vezati stavku koju dobijemo iz fruitArraya na View koji se drži unutar pogleda.
Pogled koji se drži unutar ViewHoder-a dostupan je putem polja ViewHolder.itemView. Nakon što dobijemo prikaz, možemo upotrijebiti id fruitName koji smo ranije stvorili za postavljanje sadržaja.
override fun onBindViewHolder(holder: FruitViewHolder, position: Int) { var ourFruitTextView = holder.itemView.findViewById<TextView>(R.id.fruitName) var aFruitName = fruitArray.get(position) ourFruitTextView.setText(aFruitName) }
Time je naš FruitArrayAdapter završen i izgleda ovako.
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) } }
Konačno, spremni smo spojiti preostale dijelove našeg RecyclerViewa. Koji stvaraju LayoutManager, koji će reći RecyclerViewu kako prikazati sadržaj popisa. Treba li prikazati linearno pomoću LinearLayoutManagera ili u mreži pomoću GridLayoutManagera ili StaggeredGridLayoutManagera.
Stvorite upravitelja izgleda
Vratit ćemo se unutar naše funkcije onCreate i dodati 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 }
Priključite naš adapter na stavke i postavite ga na 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 }
Također smo stvorili instancu fruitListAdapter i dodali joj niz naziva voća.
I uglavnom, svi smo gotovi.
Kompletna datoteka MainActivity.kt izgleda ovako.
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) } } }