Android RecyclerView: Što je, naučite na jednostavnim primjerima
⚡ Pametni sažetak
RecyclerView je napredni Android widget koji učinkovito prikazuje velike, pomične skupove podataka recikliranjem ograničenog broja prikaza. Oslanja se na adapter, upravitelj izgleda i držač prikaza za povezivanje zbirki podataka koje se mijenjaju tijekom izvođenja.

U čemu je RecyclerView Android?
The RecyclerView je fleksibilnija i naprednija verzija GridViewa i ListViewa. To je spremnik za prikaz velikih skupova podataka koji se može 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 ovisno o mrežnom događaju ili radnji korisnika.
Posjeta
The Android Platforma koristi klase View i ViewGroup za crtanje elemenata na ekranu. Ove klase su apsolutnotract i proširuju se na različite implementacije kako bi odgovarale slučaju upotrebe. TextView, na primjer, ima jednostavnu svrhu prikazivanja tekstualnog sadržaja na zaslonu. EditText se proširuje na istu View klasu i dodaje više funkcionalnosti kako bi korisnik mogao unositi podatke.
Moguće je kreirati vlastite prilagođene prikaze kako bismo postigli veću fleksibilnost prilikom razvojaping korisnička sučelja. Klasa View pruža metode koje možemo nadjačati za crtanje na zaslonu i način 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 kako želimo.
ViewGroups
Klasa ViewGroup je vrsta Viewa, ali za razliku od jednostavne klase View čija je odgovornost samo prikazivanje, ViewGroup nam daje mogućnost stavljanja više prikaza u jedan prikaz, na koji se možemo pozivati kao na cjelinu. U ovom slučaju, View stvoren na najvišoj razini kojem dodajemo druge jednostavne prikaze naziva se "roditelj", a prikazi koji se dodaju unutra su "djeca".
Možemo zamisliti View kao Array i ViewGroup kao Array of Arrays. S obzirom na to da je Array of Arrays sam po sebi Array, 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 analogy, 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 definiranje načina na koji su podređeni elementi organizirani unutar prikaza, na primjer, jesu li postavljeni vertikalno ili horizontalno. Možemo imati različita pravila za interakciju unutar prikaza. Na primjer, TextView-ovi koji slijede jedan drugoga trebali bi imati razmak od 12 dp, dok ImageView nakon kojeg slijedi TextView treba imati razmak od 5 dp.
To bi bio slučaj kad bismo razviliping vlastitu ViewGroup od nule. Kako bismo olakšali ove konfiguracije, Android pruža klasu pod nazivom LayoutParams, koju možemo koristiti za unos ovih konfiguracija.
The Android dokumentacija pruža neke zadane parametre koje bismo implementirali prilikom konfiguriranja vlastite ViewGroup grupe. Neki uobičajeni parametri odnose se na širinu, visinu i marginu. Prema zadanim postavkama, ove konfiguracije imaju strukturu android:layout_height za visinu i android:layout_width za širinu. U tom smislu, kada stvarate svoju ViewGroup grupu, možete dodatno stvoriti LayoutParams specifične za način na koji želite da se vaša ViewGroup ponaša.
Android dolazi s zadanim prikazima i grupama prikaza koje možemo koristiti za obavljanje mnogih uobičajenih zadataka koji su nam potrebni. Jedan primjer koji smo spomenuli je TextView. To je jednostavan prikaz koji dolazi s konfiguriranim aspektima kao što su visina, širina i veličina teksta. Također imamo ImageView za prikaz slika i EditText, kao što smo spomenuli, među mnogim drugima. 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 dodavanje elemenata prikaza (View). LinearLayout ima atribut orijentacije koji određuje kako će biti raspoređen na zaslonu. Također ima LinearLayout.LayoutParams koji određuju pravila za prikaze unutar njega. Na primjer, atribut android:center_horizontal centrira prikaze duž horizontalne osi, dok android:center_vertical centrira sadržaj prikaza duž vertikalne osi.
Evo nekoliko slika koje će vam pomoći da shvatite centriranje. Smatramo da je ovo jednostavan TextView unutar prostora od 200px x 200px; atributi centriranja bi ga učinili sljedećim.
android:centar_horizontalno
Horizontalno centriran sadržaj
android:centar_vertical
Vertikalno centriran sadržaj
android:centar
Centrirani sadržaj
Osnovne komponente RecyclerViewa
Osnovne komponente RecyclerViewa
Slijede važne komponente RecyclerViewa:
RecyclerView.Adapter
Temelj #1 – uzorak adaptera
Adapter je uređaj koji transformira atribute jednog sustava ili uređaja u atribute inače nekompatibilnog uređaja ili sustava. Neki adapteri mijenjaju atribute signala ili napajanja, dok drugi jednostavno prilagođavaju fizički oblik jednog konektora drugom.
Jednostavan primjer iz stvarnog života koji objašnjava adapter je kada trebamo spojiti uređaje, ali imaju priključke koji se ne podudaraju. To bi mogao biti slučaj kada posjetite drugu zemlju koja koristi različite vrste utičnica. Ako nosite punjač za telefon ili prijenosno računalo, bilo bi ga nemoguće spojiti na utičnice. Međutim, ne biste odustali, već biste jednostavno nabavili adapter koji dolazi između utičnice i punjača i omogućuje punjenje.
To je slučaj u programiranju kada želimo povezati dvije podatkovne strukture kako bismo ispunili zadatak, ali njihovi zadani portovi nemaju način za međusobnu komunikaciju.
Koristit ćemo jednostavan primjer uređaja i punjača. Imat ćemo dva primjera punjača, američki i britanski.
class AmericanCharger() { var chargingPower = 10 } class BritishCharger(){ var chargingPower = 5 }
Zatim ćemo stvoriti 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 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 jednog ili drugog razloga bismo trebali 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 nam omogućuje punjenje AmericanDevices uređaja s BritishChargers. Iz programske perspektive, mi ćemo biti proizvođač adaptera.
Napravit ćemo adapter koji točno odgovara uzorku koji bi nam bio potreban za izradu drugog. Implementirat ćemo ga kao klasu na sljedeći način. Ne mora nužno biti klasa, već može biti funkcija koja ističe što uzorak adaptera općenito radi. Klasu koristimo jer odgovara većini upotreba 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 chargingPower:Int = theAmericanCharger.chargingPower / 2 var newBritishCharger = new BritishCharger() newBritishCharger.chargingPower = theAmericanCharger.chargingPower/2 return newBritishCharger } }
U svijetu programiranja, razlika u utičnicama analogna je razlici u metodama punjenja. Punjači s različitim metodama punjenja onemogućili bi korištenje punjača.
var myBritishDevice = new BritishDevice() var americanChargerIFound = new AmericanCharger()
Pokušaj pozivanja metode charge() na myBritishDevice s americanChargerIFound ne bi funkcionirao, jer AmericanDevice prihvaća samo AmericanCharger. Dakle, to je nemoguće učiniti:
var myBritishDevice = new BritishDevice() var americanChargerIFound = new AmericanCharger() myBritishDevice.charge(americanChargerIFound)
U ovom scenariju, adapter koji smo kreirali, AmericanToBritishChargerAdapter, sada može biti koristan. Možemo koristiti metodu returnNewCharger() za kreiranje novog BritishCharger-a, koji možemo koristiti za punjenje. Sve što trebamo učiniti je kreirati instancu našeg adaptera i poslati mu AmericanCharger koji imamo, a on će kreirati 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(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
Kada se bavimo ViewGroup-om, unutar njega imamo Views-e. LayoutManager ima zadatak opisati kako su Views-i raspoređeni unutra.
Za usporedbu, pri radu s LinearLayout ViewGroup, slučaj upotrebe koji želimo je mogućnost postavljanja elemenata vertikalno ili horizontalno. To se lako implementira dodavanjem atributa orijentacije, koji nam govori kako će LinearLayout biti postavljen na ekranu. To možemo učiniti pomoću android:orientation=VERTICAL|HORIZONTAL atribut.
Također imamo još jednu ViewGroup pod nazivom GridLayout. Njen slučaj upotrebe je kada želimo postaviti Views u pravokutnu mrežnu strukturu. To bi moglo biti iz razloga kao što je olakšavanje korištenja podataka koje prikazujemo korisniku aplikacije. GridLayout dizajnom omogućuje konfiguracije koje će vam pomoći da postignete taj cilj definiranjem dimenzija mreže; na primjer, možemo imati mrežu 4×4 ili mrežu 3×2.
RecyclerView.ViewHolder
ViewHolder je trbušnjaktract klasu 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 mehanizam za recikliranje u RecyclerView-u promijenio razne reference za koje ne znamo.
Veliki popisi
RecyclerViews se koriste kada želimo korisniku predstaviti stvarno veliki skup Views-a, a da pritom ne iscrpimo RAM na našem uređaju za svaku stvorenu instancu Prikaza.
Ako uzmemo slučaj popisa kontakata, imali bismo opću ideju o tome kako bi jedan kontakt izgledao na popisu. Ono što bismo tada učinili jest stvoriti predložak izgleda, koji je zapravo Prikaz, s utorima gdje će se popunjavati različiti podaci s našeg popisa kontakata. Slijedeći pseudokod objašnjava cijelu svrhu:
//OneContactView
<OneContact>
<TextView>{{PlaceHolderForName}}</TextView>
<TextView>{{PlaceHolderForAddress}}</TextView>
<ImageView>{{PlaceHolderForProfilePicture}}</ImageView>
<TextView>{{PlaceHolderForPhoneNumber}}</TextView>
</OneContact>
Tada bismo imali popis kontakata ove vrste:
<ContactList> </ContactList>
Da smo sadržaj hardkodirali, ne bismo imali programski način dodavanja novog sadržaja na popis bez prepisivanja aplikacije. Srećom za nas, dodavanje Viewa u ViewGroup podržano je od strane addView(view:View) metoda. Unatoč tome, to nije način na koji se RecyclerViewu dodaju podređeni prikazi.
U našem slučaju korištenja, imali bismo dugi popis kontakata. Za svaki kontakt na popisu, trebali bismo stvoriti OneContactView i popuniti podatke unutar Viewa kako bi odgovarali poljima u našoj klasi Contact. Nakon što imamo prikaz, trebali bismo 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. OneContactView sadrži utore za preuzimanje sadržaja iz klase Contact i njihov prikaz. U RecyclerViewu moramo dodati Views kako bi nam pomogao s njegovom mogućnošću recikliranja.
RecyclerView nam zapravo ne dopušta dodavanje prikaza, ali nam omogućuje dodavanje ViewHoldera. Dakle, u ovom scenariju imamo dva dijela koja želimo spojiti, ali se ne podudaraju. Tu dolazi do izražaja naš adapter. RecyclerView nam pruža adapter, slično našem AmericanToBritishChargerAdapter() od ranije što nam je omogućilo da pretvorimo naš AmericanCharger, koji se nije mogao koristiti s našim BritishDeviceom, u nešto upotrebljivo, slično adapteru za napajanje u stvarnom životu.
U ovom scenariju, adapter bi uzeo naš niz kontakata i naš View, te odatle generirao ViewHoldere koje je RecyclerView spreman prihvatiti.
RecyclerView pruža sučelje koje možemo proširiti kako bismo stvorili naš adapter putem klase RecyclerView.Adapter. Unutar ovog adaptera nalazi se način za stvaranje klase ViewHolder s kojom RecyclerView želi raditi. Dakle, imamo istu situaciju kao i prije, ali s jednom dodatnom stvari, adapterom.
Imamo niz kontakata, prikaz za prikaz jednog kontakta (OneContactView) i RecyclerView, što je popis prikaza koji pruža usluge recikliranja, ali je spreman prihvatiti samo ViewHoldere. 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 ABStract klasa koja uzima naš View kao argument i pretvara ga u ViewHolder. Koristi omotački uzorak koji se koristi za proširenje mogućnosti klasa.
Temelj #2 – Uzorak omota
Upotrijebit ćemo jednostavan primjer kako bismo pokazali 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 htjeli dodati metodu koja bi ih natjerala da govore, ali autor biblioteke nije bio zabavan, još uvijek bismo mogli pronaći način. Trebao bi nam omotač za našu klasu Animal. To bismo učinili tako da uzmemo Animal kao konstruktor za našu klasu.
class SpeechPoweredAnimalByWrapper(var myAnimal:Animal){ fun sound(){ myAnimal.sound() } fun speak(){ println("Hello, my name is ${myAnimal.name}") } }
Sada možemo proslijediti instancu životinje metodi SpeechPoweredAnimalByWrapper. Pozivanje metode sound() na njoj pozvalo bi i proslijedjenu metodu animal sound(). Također imamo dodatnu metodu speak() koja se računa kao nova funkcionalnost koju dodajemo proslijedjenim ž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 obrazac, možemo uzimati klase i dodavati funkcionalnosti. Sve što nam treba je proslijediti instancu klase i nove metode definirane našim wrapom.ping klase.
U gornjem slučaju koristili smo klasu betona. Također je moguće implementirati isto u abstract klasa. Trebali bismo promijeniti klasu SpeechPoweredAnimalByWrapper u abstract i gotovi smo. Promijenit ćemo naziv klase u nešto kraće kako bi bio čitljiviji.
abstract class SpeechPowered(var myAnimal:Animal){ fun sound(){ myAnimal.sound() } fun speak(){ println("Hello, my name is ${myAnimal.name}") } }
Isto je kao i prije, ali bi značilo nešto drugo. U normalnoj klasi možemo imati instancu klase na isti način na koji smo stvorili cat1 i dog1. AbstracMeđutim, klase t nisu namijenjene instanciranju, već proširenju drugih klasa. Pa kako bismo koristili novi SpeechPowered(var myAnimal:Animal) abstract klasa? Možemo je koristiti stvaranjem novih klasa koje će je proširiti i zauzvrat dobiti njezinu funkcionalnost.
U našem primjeru, kreirat ć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 abstract klasa koja dodaje funkcionalnost Viewu, slično kao što smo dodali metodu speak životinjama. Dodana funkcionalnost je ono što ga čini funkcionalnim pri radu s RecyclerViewom.
Ovako bismo kreirali OneContactViewHolder iz 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 ima adapter koji nam omogućuje povezivanje našeg niza kontakata s ContactsViewom pomoću RecyclerViewa.
Dodavanje prikaza
ViewGroup se ne ponovno iscrtava automatski, već slijedi određeni raspored. Na vašem uređaju može biti slučaj da se ponovno iscrtava svakih 10 ms ili 100 ms; ili, ako odaberemo apsurdan broj, recimo 1 minutu, kada dodamo View u ViewGroup, vidjet ćete promjene 1 minutu kasnije kada se ViewGroup "osvježi".
RecyclerView.Recycler
Osnova #3 – Predmemoriranje
Jedan od najboljih primjera gdje redovito osvježavamo stranicu je u pregledniku. Zamislimo, na primjer, da je stranica koju posjećujemo statična i ne šalje dinamički sadržaj; morali bismo stalno osvježavati stranicu da bismo vidjeli promjene.
Za ovaj primjer, zamislimo da je dotična stranica Twitter. Imali bismo niz statičnih tweetova, a jedini način da vidimo nove tweetove bio bi klikom na gumb za osvježavanje kako bismo ponovno dohvatili sadržaj.
Preslikavanje cijelog ekrana očito je skupa stvar. Zamislite da imamo ograničenu propusnost kod našeg telefonskog operatera, a naša lista tweetova ima puno slika i videozapisa; bilo bi skupo ponovno preuzeti sav sadržaj stranice pri svakom osvježavanju.
Trebali bismo način za pohranjivanje već učitanih tweetova i osigurati da naš sljedeći zahtjev ima mogućnost reći koje tweetove već ima. Stoga se ne preuzima sve ponovno, već se dobivaju samo novi tweetovi, a također se provjerava je li neki tweet koji je spremljen lokalno više tamo kako bi se mogao lokalno izbrisati. Ono što opisujemo naziva se keširanje.
Informacije koje šaljemo web stranici o sadržaju koji imamo nazivaju se metapodaci. Dakle, u pravom smislu, ne kažemo samo „želimo učitati vašu stranicu“, već kažemo „želimo učitati vašu stranicu, a evo i dijela sadržaja koji smo već spremili od posljednjeg učitavanja; molimo vas da ga koristite kako biste nam poslali samo ono što nije tamo, kako ne bismo koristili puno propusnosti.“
Layout Calls – Tweet lista mora biti luda
Primjer poziva rasporeda je scrollToPosition. Ovo je čest primjer prisutan u stvarima poput aplikacija za chat. Ako netko u niti chata odgovori na balončić chata od ranije, neke aplikacije za chat uključuju odgovor i vezu na balončić chata, koja vas nakon klika vodi tamo gdje se nalazila vaša izvorna poruka.
U slučaju da pozovemo ovu metodu prije nego što dodamo LayoutManager našem RecyclerView-u i prije nego što imamo RecyclerView.Adapter, scrollToPosition(n:Int) se jednostavno ignorira.
Komunikacija između RecyclerView komponenti
Temelj #4 – Povratni pozivi
RecyclerView, u svom radu, ima mnogo pokretnih dijelova. Mora se nositi s LayoutManagerom, koji nam govori kako organizirati Views, bilo linearno ili u mreži. Mora se nositi s adapterom koji obavlja posao pretvaranja naših stavki (contactList) u Views (OneContactView), a zatim u ViewHolders (OneContactViewHolder) s kojima je RecyclerView spreman raditi koristeći metode koje pruža.
Sirovi materijal za RecyclerView su naši Views, npr. OneContactView, i izvor podataka.
contactList:Array<Contact>
Kao početnu točku koristili smo jednostavan scenarij kako bismo dobili osjećaj za ono što RecyclerView pokušava postići. Osnovni slučaj kada imamo statički niz od 1000 kontakata koje želimo prikazati korisniku lako je razumjeti. Mehanizam RecyclerView-a zaista počinje oživljavati kada popis više nije statičan. S dinamičkim popisom moramo razmisliti što se događa s Viewom na zaslonu kada dodamo stavku na popis ili uklonimo stavku s popisa.
RecyclerView.LayoutManager
Osim što odlučuje o tome kako će naši prikazi biti raspoređeni, bilo linearno ili u mreži, LayoutManager obavlja puno posla "ispod haube" u Helu.ping Recikler zna kada treba reciklirati.
Odgovoran je za keeping track prikaza trenutno vidljivih na zaslonu i prenošenje tih informacija mehanizmu recikliranja. Dok se korisnik pomiče prema dolje, LayoutManager je odgovoran za obavještavanje sustava recikliranja o prikazima koji nestaju iz fokusa na vrhu kako bi se mogli ponovno upotrijebiti, umjesto da ostanu tamo i troše memoriju ili budu uništeni samo da bi se stvorili novi.
To znači da LayoutManager mora održavati track mjesta gdje se korisnik nalazi dok pomiče naš popis. To postiže popisom pozicija koje su temeljene na indeksu, 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, LayoutManager je na početku svjestan da ima u fokusu View-0 sve do View-9. Kako se pomičemo, LayoutManager može izračunati prikaze koji su izvan fokusa.
LayoutManager može osloboditi ove prikaze mehanizmu recikliranja kako bi se mogli ponovno koristiti (novi podaci mogu se vezati za njih; npr. kontaktni podaci prikaza mogu se ukloniti, a novi kontaktni podaci 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 RecyclerView-a je s dinamičkim popisima gdje podaci mogu dolaziti s online krajnje točke ili čak možda sa senzora. Ne samo da se podaci dodaju, već se podaci na našem popisu ponekad uklanjaju ili ažuriraju.
Dinamičko stanje naših podataka može uvelike otežati rasuđivanje o LayoutManageru. Zbog toga LayoutManager održava vlastiti popis stavki i pozicija, odvojen od popisa koji koristi komponenta za recikliranje. To osigurava da ispravno obavlja svoj posao rasporeda.
Istovremeno, LayoutManager u RecyclerViewu ne želi krivo prikazati podatke koje ima. Kako bi ispravno radio, LayoutManager se sinkronizira s RecyclerView.Adapterom u zadanim intervalima (60 ms), dijeleći informacije o stavkama našeg popisa, tj. stavkama koje su dodane, ažurirane, uklonjene ili premještene s jedne pozicije na drugu. LayoutManager, nakon što primi te informacije, reorganizira sadržaj na zaslonu kako bi odgovarao promjenama kada je to potrebno.
Mnoge osnovne operacije koje se bave RecyclerViewom vrte se oko komunikacije između RecyclerView.LayoutManagera i RecyclerView.Adaptera koji pohranjuje naše popise ponekad statičnih, a ponekad dinamičkih podataka.
Osim toga, RecyclerView nam nudi metode koje možemo koristiti za osluškivanje događaja, kao što je onBindViewHolder kada naš RecyclerView.Adapter veže sadržaj s našeg popisa (npr. kontakt) na ViewHolder kako bi se on koristio za prikaz informacija na zaslonu.
Drugi je onCreateViewHolder, koji nam govori kada RecyclerView.Adapter uzima regularni View poput OneContactView i pretvara ga u ViewHolder stavku s kojom RecyclerView može raditi.
Osim osnovnih mehanizama koji omogućuju recikliranje, RecyclerView pruža načine prilagođavanja ponašanja bez utjecaja na recikliranje. Ponovna upotreba prikaza otežava uobičajene stvari na koje smo navikli sa statičkim prikazima, poput reagiranja na događaje onClick.
Kao što nam je poznato, RecyclerView.LayoutManager koji korisniku predstavlja Prikaze može na trenutak imati drugačiji popis stavki od RecyclerView.Adapter koji ima popis pohranjen u bazi podataka ili se struji iz izvora. Stavljanje OnClick događaja izravno na Views može dovesti do neočekivanog ponašanja, kao što je brisanje pogrešnog kontakta.
Gradle
Ako želimo koristiti RecyclerView, moramo ga dodati kao ovisnost u našu build.gradle datoteku. U sljedećem primjeru koristili smo implementaciju „androidx.recyclerview:recyclerview:1.1.0“, što je najnovija verzija prema ovom članku.
Nakon dodavanja ovisnosti našem Gradle datoteku, bit ćemo upitani Android Studio sinkronizirati promjene. Ovako naši Gradle Datoteka će se pobrinuti nakon dodavanja RecyclerView-a u prazan projekt samo s 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" }
Trenutno imamo samo jednu datoteku izgleda. Počet ćemo s jednostavnim primjerom gdje koristimo RecyclerView za prikaz popisa naziva voća na zaslonu.
Popis predmeta
Doći ćemo do naše MainActivity datoteke i kreirati niz s nazivima voća unutra, 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 RecyclerView-a. Da bismo to učinili, otvorit ćemo direktorij izgleda koji sadrži naše izglede i stvoriti View koji će biti odgovoran za prikaz 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 gornjem TextViewu dodali smo polje id koje će se koristiti za identifikaciju Viewa. Ne generira se prema zadanim postavkama. Dali smo našem TextViewu id fruitName kako bi odgovarao podacima koji će biti vezani za njega.
Dodavanje RecyclerView-a u glavni izgled
U istoj aktivnosti postoji datoteka izgleda main_layout.xml koja je generirana za nas prema zadanim postavkama. Ako odaberemo prazan projekt, generirat će se XML koji sadrži ConstraintLayout, a unutra će biti TextView s tekstom "Hello". Izbrisat ćemo sav sadržaj i layout će sadržavati samo RecyclerView kao što je prikazano 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 atribut id za RecyclerView, koji ćemo koristiti za referenciranje na njega u našem kodu.
android:id="@+id/fruitRecyclerView"
Zatim ćemo se vratiti u našu MainActivity datoteku. Koristeći ID-ove koje smo kreirali, moći ćemo referencirati View-ove koje smo upravo kreirali. Počet ćemo referenciranjem RecyclerView-a pomoću metode findViewById() koju pruža AndroidTo ć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 kreirati 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 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 kreirati klasu FruitArrayAdapter koja proširuje klasu RecyclerView.Adapter. FruitArrayAdapter koji kreiramo bit će odgovoran za sljedeće: uzimat će nazive voća iz polja voća, kreirati ViewHolder koristeći naš prikaz one_fruit_view.xml, zatim vezati voće s ViewHolderom i dinamički vezati sadržaj s prikazom koji smo kreirali.
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 dodat će crvene vijugave linije na naš FruitArrayAdapter, što nam govori da moramo implementirati metode koje RecyclerView može koristiti za povezivanje našeg niza s ViewHolderom.
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 elemenata u našem nizu pozivanjem svojstva veličine niza.
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 metodu onCreateViewHolder. Ovdje nas RecyclerView traži da mu pomognemo konstruirati FruitViewHolder. Ako se dobro sjećamo, naša klasa FruitViewHolder je izgledala ovako:
class FruitViewHolder(fruitView: View): RecyclerView.ViewHolder(fruitView)
Zahtijeva naš fruitView, koji smo kreirali kao XML datoteku one_fruit_view.xml. Možemo kreirati referencu na ovaj XML i pretvoriti ga u View 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 onBindViewHolder-a.
fun onBindViewHolder(holder: FruitViewHolder, position: Int)
RecyclerView.Adapter pita s cijelim brojem pozicije, koji koristimo za dohvaćanje stavke s našeg popisa. Također nam pruža držač (holder) kako bismo mogli povezati stavku koju dobijemo iz fruitArray s Viewom koji se nalazi unutar držača prikaza. Prikaz koji se nalazi unutar ViewHoldera dostupan je putem polja ViewHolder.itemView. Nakon što dobijemo prikaz, možemo koristiti ID fruitName koji smo ranije kreirali 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) }
S 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 povezati preostale dijelove našeg RecyclerView-a, koji stvaraju LayoutManager koji će RecyclerView-u reći kako prikazati sadržaj liste, treba li ga prikazati linearno pomoću LinearLayoutManager-a ili u mreži pomoću GridLayoutManager-a ili StaggeredGridLayoutManager-a.
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 kreirali instancu fruitListAdapter i u nju unijeli niz naziva voća. I u osnovi, gotovi smo. Potpuna 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) } } }




