Android RecyclerView: Mi az, tanuljon egyszerű példákkal
Miben található a RecyclerView Android?
A RecyclerView egy widget, amely a GridView és a ListView rugalmasabb és fejlettebb változata. Ez egy konténer nagy adatkészletek megjelenítésére, amelyek korlátozott számú nézet fenntartásával hatékonyan görgethetők. A RecyclerView widgetet akkor használhatja, ha olyan adatgyűjteményei vannak, amelyek elemei futás közben változnak a hálózati eseménytől vagy a felhasználói művelettől függően.
Nézetek
A Android platform a View és ViewGroup osztályokat használja az elemek képernyőre való rajzolásához. Ezek az osztályok absztraktak, és különböző megvalósításokra terjeszthetők ki, hogy megfeleljenek egy használati esetnek. A TextView például egyszerű célja a szöveges tartalom megjelenítése a képernyőn. Az EditText ugyanabból a View osztályból indul ki, és további funkciókat ad hozzá, hogy lehetővé tegye a felhasználó számára az adatok bevitelét.
Lehetőség van saját egyéni nézetek létrehozására, hogy nagyobb rugalmasságot érhessünk el a felhasználói felületek fejlesztése során. A Nézet osztály olyan metódusokat biztosít, amelyeket felülírhatunk a képernyőn történő rajzoláshoz, és lehetőséget biztosít olyan paraméterek átadására, mint a szélesség, magasság és saját egyéni attribútumaink, amelyeket hozzá szeretnénk adni a nézetünkhöz, hogy az úgy viselkedjen, ahogyan szeretnénk.
ViewGroups
A ViewGroup osztály egyfajta nézet, de az egyszerű Nézet osztálytól eltérően, amelynek feladata a pusztán megjelenítés, a ViewGroup lehetőséget ad arra, hogy több nézetet helyezzünk el egy nézetbe, amelyekre összességében hivatkozhatunk. Ebben az esetben a legfelső szinten létrehozott nézetet, amelyhez más egyszerű nézeteket is hozzáadunk (nézetcsoportokat is hozzáadhatunk), „szülőnek”, a benne hozzáadott nézeteket pedig „gyermekeknek” nevezzük.
Képezhetünk egy nézetet tömbként és egy nézetcsoportot tömbtömbként. Tekintettel arra, hogy a tömbök tömbje maga egy tömb, láthatjuk, hogyan lehet egy ViewGroup-ot nézetként kezelni.
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
A ViewGroup azt is lehetővé teszi, hogy meghatározzuk, hogyan szerveződjenek a gyerekek a nézeten belül, például függőlegesen vagy vízszintesen fektessük le őket. Különféle szabályok vonatkozhatnak a Nézeten belüli interakcióra. Például az egymást követő TextView-k távolsága 12 dp, míg a TextView által követett ImageView-k távolsága 5 dp.
Ez lenne a helyzet, ha a saját ViewGroupunkat a semmiből fejlesztenénk. A konfigurációk megkönnyítése érdekében Android biztosít egy LayoutParams nevű osztályt, amelyet ezen konfigurációk bevitelére használhatunk.
Android dokumentáció biztosít néhány alapértelmezett paramétert, amelyet a saját ViewGroup konfigurálásakor alkalmaznánk. Néhány általános paraméter a szélességre, magasságra és margóra vonatkozik. Alapértelmezés szerint ezeknek a konfigurációknak a magassága android:layout_height szerkezete van, például android:layout_width Ebben a tekintetben, amikor létrehozza a ViewGroup-ot, további LayoutParams-eket hozhat létre, amelyek specifikusak a ViewGroup viselkedésének megfelelően.
Android alapértelmezett Nézetekkel és ViewGroupokkal rendelkezik, amelyek segítségével számos gyakori feladatot elvégezhetünk. Az egyik példa, amelyet említettünk, a TextView. Ez egy egyszerű nézet, amely olyan konfigurálható szempontokkal rendelkezik, mint a magasság, szélesség, szövegméret és hasonlók. Van egy ImageView a képek és az EditText megjelenítéséhez, amint azt sok más mellett már említettük. Android egyedi ViewGroupokkal is rendelkezik, amelyekhez hozzáadhatjuk nézeteinket, és elérhetjük a várt viselkedést.
Lineáris elrendezés
A LinearLayout lehetővé teszi, hogy View elemeket adjunk hozzá. A LinearLayout egy tájolási attribútum, amely meghatározza, hogyan kerüljön elrendezésre a képernyőn. LinearLayout.LayoutParams is rendelkezik, amelyek megszabják a belső nézetek szabályait, például az android:center_horizontal attribútum a nézeteket a vízszintes tengely mentén, míg az `android:center_vertical a nézet tartalmát a függőleges tengely mentén központosítja.
Íme néhány kép a központosítás megértéséhez. Ezt egy egyszerű TextView-nak tekintenénk 200x200 képpontos térben, az attribútumok központosítása a következőképpen viselkedne.
android:center_horizontal

android:center_vertical

android:center

A RecyclerView fő összetevői

A RecyclerView fontos összetevői a következők:
RecyclerView.Adapter
1. alapvetés – Adapterminta
Az adapter olyan eszköz, amely átalakítja a rendszer vagy eszköz attribútumait egy egyébként nem kompatibilis eszköz vagy rendszer attribútumaivá. Némelyikük módosítja a jel- vagy teljesítményjellemzőket, míg mások egyszerűen hozzáigazítják az egyik csatlakozó fizikai formáját a másikhoz.
Egy egyszerű példa, amelyet a való életben találunk az adapter magyarázatára, amikor az eszközöket egymáshoz kell csatlakoztatnunk, de olyan csatlakozóportjaik vannak, amelyek nem egyeznek egymással. Ez akkor fordulhat elő, ha egy másik országba látogat, ahol különböző típusú aljzatokat használnak. Ha magánál van a telefon vagy laptop töltő, lehetetlen lenne csatlakoztatni a konnektorhoz. Azonban nem adná fel, hanem egyszerűen beszerezne egy adaptert, amely a konnektor és a töltő közé kerül, és lehetővé teszi a töltést.
Ez a helyzet a programozásban, amikor két adatszerkezetet szeretnénk összekapcsolni a feladat teljesítése érdekében, de az alapértelmezett portjaik nem kommunikálnak egymással.
Az eszköz és a töltő egyszerű példáját fogjuk használni. Két darab töltőnk lesz. Egy amerikai és egy brit
class AmericanCharger() { var chargingPower = 10 } class BritishCharger(){ var charginPower = 5 }
Ezután létrehozunk két eszközt
class AmericanDevice() class BritishDevice()
Példaként ezután létrehozhatunk néhány példányt az eszközökből, hogy együtt játszhassunk.
var myAmericanPhone = new AmericanDevice() var myBritishPhone = new BritishDevice()
Ezután mindkét eszköz esetében bemutatjuk a töltés fogalmát úgy, hogy az eszközökhöz hozzáadunk egy charge() nevű módszert.
A módszer bemenetként veszi a megfelelő töltőjét, és az alapján tölti.
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 } }
Ebben az esetben, analógiánk alapján, valamilyen okból szükségünk lenne BritishCharger használatára, amikor AmericanDevice-t használunk, vagy fordítva.
A programozási világban ez általában olyan könyvtárak keverésekor történik, amelyek ugyanazt a funkcionalitást kínálják (a mi kontextusunkban a megosztott funkciónk töltés). Meg kell találnunk a módját ennek lehetővé tételére.
Ha követjük az analógiát, akkor el kell mennünk egy elektronikai boltba, és vásárolnunk kell egy adaptert, amely lehetővé teszi az AmericanDevices töltését a BritishChargers töltőkkel. A programozás szempontjából mi leszünk az adapter gyártói.
Az egyikhez készítünk egy adaptert, amely pontosan megegyezik a másik létrehozásához szükséges mintával. Osztályként valósítjuk meg az alábbiak szerint. Nem feltétlenül kell osztálynak lennie, és lehet egy függvény, amely kiemeli, hogy az adapterminta általában mit csinál. Olyan osztályt fogunk használni, amely megfelel a legtöbb használatnak 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 } }
A programozási világban az aljzatok közötti különbség analóg a töltésre használt módszerek különbségével. A különböző módszerekkel rendelkező töltők lehetetlenné tennék a töltők használatát.
var myBritishDevice = new BritishDevice() var americanChargerIFound = new AmericanCharger()
Ha megpróbálja meghívni a charge() metódust a myBritishDevice-ben az americanChargerIFound segítségével, az nem fog működni, mivel az AmericanDevice csak AmericanChargert fogad el.
Tehát ezt lehetetlen megtenni
var myBritishDevice = new BritishDevice() var americanChargerIFound = new AmericanCharger() myBritishDevice.charge(americanChargerIFound)
Ebben a forgatókönyvben az általunk létrehozott adapter
Az AmericanToBritishChargerAdapter most jól jöhet. A returnNewCharger() metódussal létrehozhatunk egy új BritishChargert, amivel tölthetünk. Csak létre kell hoznunk az adapterünk példányát, és meg kell táplálnunk a rendelkezésünkre álló AmericanChargerrel, és ez létrehoz egy BritishCharger-t, amelyet használhatunk.
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
Amikor egy ViewGroup-pal foglalkozunk, a nézeteket helyezzük el benne. A LayoutManager feladata lenne, hogy leírja, hogyan vannak elhelyezve a nézetek belül.
Összehasonlítás céljából, ha a Linearlayout ViewGroup-pal dolgozunk, azt szeretnénk, ha az elemeket függőlegesen vagy vízszintesen elhelyezhetjük. Ez könnyen megvalósítható egy orientációs attribútum hozzáadásával, amely megmondja, hogyan kerül a lineáris elrendezés a képernyőre. Ezt használatával tehetjük meg android:orientation=VERTICAL|HORIZONTAL
tulajdonság.
Van egy másik ViewGroupunk is, a GridLayout, ez az a használati eset, amikor a Nézeteket egy téglalap alakú Grid struktúrában szeretnénk elhelyezni. Ennek oka lehet például az általunk az alkalmazás felhasználója számára megjelenített adatok egyszerű felhasználása. A GridLayout tervezésénél fogva olyan konfigurációkat tesz lehetővé, amelyek segítenek elérni ezt a célt azáltal, hogy vannak olyan konfigurációk, ahol meghatározhatjuk a rács méreteit, például lehet 4 × 4-es, 3 × 2-es rácsunk.
RecyclerView.ViewHolder
A ViewHolder egy absztrakt osztály, amelyet a RecyclerView-ból is kiterjesztünk. A ViewHolder olyan általános módszereket biztosít számunkra, amelyek segítségével hivatkozhatunk egy nézetre, amelyet a RecyclerView-ban helyeztünk el, még azután is, hogy a RecyclerView újrahasznosítási gépe megváltoztatott különböző referenciákat, amelyekről nem tudunk.
Nagy listák
A RecyclerView-t akkor használjuk, ha a nézetek igazán nagy halmazát szeretnénk bemutatni a felhasználónak, miközben nem merítjük ki a RAM eszközünkön a létrehozott nézet minden egyes példányához.
Ha a névjegyzék esetét vesszük, akkor általános elképzelésünk lenne arról, hogyan fog kinézni egy névjegy a listában. Ezt követően létrehoznánk egy sablon elrendezést – ami valójában egy nézet – olyan helyekkel, ahol a névjegylistánk különböző adatai kitöltődnek. A következő egy pszeudo kód, amely elmagyarázza a teljes célt:
//OneContactView <OneContact> <TextView>{{PlaceHolderForName}}</TextView> <TextView>{{PlaceHolderForAddress}}</TextView> <ImageView>{{PlaceHolderForProfilePicture}}</ImageView> <TextView>{{PlaceHolderForPhoneNumber}}</TextView> </OneContact>
Akkor lenne egy ilyen jellegű kapcsolati listánk
<ContactList> </ContactList>
Ha ez a helyzet, akkor a tartalmat keményen kódoltuk, így nem lenne lehetőségünk programozott módon új tartalmat hozzáadni a listához az alkalmazás átírása nélkül. Nekünk szerencsére. Nézet hozzáadását egy nézetcsoporthoz támogatja egy addView(view:View)
módszer.
Még ha ez a helyzet is, a RecyclerView nem ad hozzá gyermeknézeteket.
Használati esetünkben hosszú névjegyzékünk lenne. A listán szereplő minden névjegyhez létre kell hoznunk a OneContactView-t, és fel kell töltenünk az adatokat a Nézeten belül, hogy megfeleljenek a Névjegyosztály mezőinek. Ezután amint megvan a nézet, hozzá kell adnunk a RecyclerView-hoz a lista megjelenítéséhez.
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)
Van egy sor névjegyünk OneContactView néven. Réseket tartalmaz a Contact osztály tartalmának átvételéhez és megjelenítéséhez. A RecyclerView-ban Views-t kell hozzáadnunk, hogy segítsen nekünk az újrahasznosítási képességében.
A RecyclerView valójában nem teszi lehetővé nézet hozzáadását, de lehetővé teszi egy ViewHolder hozzáadását. Tehát ebben a forgatókönyvben két olyan dolog van, amelyeket össze akarunk kapcsolni, de nem egyeznek. Itt jön be az adapterünk. A RecyclerView a miénkhez hasonló adaptert biztosít számunkra AmericanToBritishChargerAdapter()
Ez lehetővé tette számunkra, hogy átalakítsuk az AmericanChargerünket, amely a BritishDevice-ünkkel használhatatlan volt, valami használhatóvá, ami hasonló a valóságban használt hálózati adapterhez.
Ebben a forgatókönyvben az illesztő átveszi a névjegyek tömbjét és a nézetünket, és onnan létrehozza a ViewHoldereket, amelyeket a RecyclerView hajlandó elfogadni.
A RecyclerView olyan felületet biztosít, amelyet kiterjeszthetünk az Adapterünk létrehozásához a RecyclerView.Adapter osztályon keresztül. Ezen az adapteren belül létrehozható a ViewHolder osztály, amellyel a RecyclerView dolgozni szeretne. Tehát ugyanaz a helyzet, mint korábban, de egy plusz dologgal, az Adapterrel.
Névjegyek tömbje áll rendelkezésünkre, egy névjegy megjelenítésére szolgáló OneContactView nézet. A RecyclerView olyan nézetek listája, amelyek újrahasznosítási szolgáltatásokat nyújtanak, de csak a ViewHoldereket hajlandók felvenni.
De ebben a forgatókönyvben most már van RecyclerView.Adapter osztályunk, amelyben van egy módszer a ViewHolderek létrehozására.
fun createViewHolder(@NonNull parent: ViewGroup, viewType: Int): ViewHolder
A RecyclerView.ViewHolder egy absztrakt osztály, amely a nézetünket argumentumként veszi, és ViewHolderré alakítja.
A burkolómintát használja, amelyet az osztályok képességeinek bővítésére használnak.
2. alapvetés – Burkolatminta
Egy egyszerű példán keresztül bemutatjuk, hogyan lehet az állatokat beszélni.
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
A fenti példában két állatunk van. Ha véletlenül, szerettünk volna egy módszert is hozzáadni a beszéd elkészítéséhez, de a könyvtári szerző nem volt jókedvű, mégis találtunk rá módot. Szükségünk lenne egy csomagolóanyagra az Animal osztályunkba. Ezt úgy tennénk meg, hogy az Animal-t építjük osztályunkba
class SpeechPoweredAnimalByWrapper(var myAnimal:Animal){ fun sound(){ myAnimal.sound() } speak(){ println("Hello, my name is ${myAnimal.name}") } }
Most átadhatunk egy állatpéldányt a SpeechPoweredAnimalByWrappernek. A sound() metódus meghívása rajta a pass in animal sound() metódust hívná meg. Van egy további speak() metódusunk is, amely új funkcionalitásnak számít, amelyet a bevitt állatokhoz adunk. A következőképpen használhatjuk:
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"
Ezzel a mintával osztályokat vehetünk fel és funkcionalitást adhatunk hozzá. Csak egy osztálypéldányt és a burkolóosztályunk által meghatározott új metódusokat kell átadnunk.
A fenti esetünkben konkrét osztályt használtunk. Ugyanez megvalósítható egy absztrakt osztályban is. Csak módosítanunk kell a SpeechPoweredAnimalByWrapper osztályt absztraktra, és készen is vagyunk. Módosítjuk az osztály nevét valami rövidebbre, hogy könnyebben olvasható legyen.
abstract class SpeechPowered(var myAnimal:Animal){ fun sound(){ myAnimal.sound() } speak(){ println("Hello, my name is ${myAnimal.name}") } }
Ugyanaz, mint korábban, de mást jelentene. Egy normál osztályban ugyanúgy rendelkezhetünk egy osztály példányával, mint a cat1 és a dog1 létrehozásával. Az absztrakt osztályok azonban nem példányosításra, hanem más osztályok kiterjesztésére szolgálnak. Tehát hogyan használnánk az új SpeechPowered(var myAnimal:Animal) absztrakt osztályt. Használhatjuk új osztályok létrehozásával, amelyek kibővítik és elnyerik a funkcionalitását.
Példánkban létrehozunk egy SpeechPoweredAnimal osztályt, amely kiterjeszti az osztályt
class SpeechPoweredAnimal(var myAnimal:Animal):SpeechPowered(myAnimal)
var cat1 = Cat("Tubby") var speakingKitty = SpeechPoweredAnimal(cat1) speakingKitty.speak() //"Hello, my name is Tubby"
Ez a ViewHolderben használt minta. A RecyclerView.ViewHolder osztály egy absztrakt osztály, amely funkcionalitást ad a nézethez, hasonlóan ahhoz, ahogy az állatokhoz a beszédmódszert adtuk. A hozzáadott funkcionalitás az, ami miatt működik a RecyclerView kezelésekor.
Így hozunk létre egy OneContactViewHolder-t a OneContactView-ból
//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)
A RecyclerView rendelkezik egy adapterrel, amely lehetővé teszi, hogy a Névjegyek tömbjét összekapcsoljuk a ContactsView-val a RecyclerView segítségével
Nézet hozzáadása
A ViewGroup nem rajzolja át automatikusan a ViewGroup-ot, hanem egy meghatározott ütemezést követ. Előfordulhat, hogy az eszközön 10 vagy 100 ms-onként újrarajzol, vagy ha egy abszurd számot választunk, mondjuk 1 percet, amikor nézetet adunk egy ViewGrouphoz, akkor 1 perccel később, amikor a ViewGroup „frissül”, látni fogja a változásokat.
RecyclerView.Recycler
Alapmunka #3. Gyorsítótárazás
Az egyik legjobb példa arra, ahol rendszeresen frissítünk, a böngésző. Képzeljük el például, hogy a meglátogatott webhely statikus, és nem dinamikusan küld tartalmat, folyamatosan frissítenünk kell a változásokat.
Ebben a példában képzeljük el, hogy a kérdéses webhely a Twitter. Egy sor statikus tweetet felsorakoztatunk, és az új tweeteket csak úgy láthatjuk, ha a frissítés gombra kattintunk a tartalom visszaállításához.
A teljes képernyő újrafestése nyilvánvalóan költséges dolog. Ha elképzelnénk, ha ez lenne a helyzet, korlátozott sávszélességünk volt a telefonszolgáltatónkkal. A tweetlistánk pedig rengeteg képet és videót tartalmazott, költséges lenne minden frissítésnél újra letölteni az oldal teljes tartalmát.
Szükségünk lenne egy módra a már betöltött tweetek tárolására, és biztosítanunk kell, hogy a következő kérésünk képes legyen kimondani a már meglévő tweetjeit. Ezért nem tölt le újra mindent, és csak az új tweetjeit kapja meg, és azt is ellenőrzi, hogy egy helyileg mentett tweet nincs-e már ott, hogy helyben törölhesse. Amit leírunk, azt gyorsítótárazásnak hívják.
Azokat az információkat, amelyeket a webhelynek a rendelkezésünkre álló tartalomról küldünk, metaadatoknak nevezzük. Tehát valójában nem csak azt mondjuk, hogy „be akarjuk tölteni a webhelyét”, hanem azt, hogy „be akarjuk tölteni az Ön webhelyét, és itt van néhány olyan tartalom, amelyet már elmentettünk az utolsó betöltéskor. Kérjük, használja csak azt küldje el nekünk, ami nincs benne, így nem használunk túl sok sávszélességet, mivel nincs sok erőforrásunk.”
Elrendezési hívások – A tweetlista biztosan őrült
Az elrendezéshívások példája a scrollToPosition
Ez egy gyakori példa, amely jelen van például a csevegőalkalmazásokban. Ha valaki egy csevegési szálban válaszol egy korábbi csevegési buborékra, egyes csevegőalkalmazások tartalmazzák a választ és a csevegési buborékra mutató hivatkozást, amelyre kattintva eljuthat az eredeti üzenet helyére.
Ebben az esetben hívjuk meg ezt a metódust, mielőtt egy LayoutManager-t hozzáadnánk a RecyclerView-hoz, és mielőtt még egy RecyclerView.Adapter-t kapnánk, a scrollToPosition(n:Int) egyszerűen figyelmen kívül marad.
Kommunikáció a RecyclerView komponensek között
Alapmunka #4. Visszahívások
A RecyclerView munkája során sok mozgó alkatrészt tartalmaz. Foglalkoznia kell a LayoutManagerrel, amely megmondja, hogyan szervezzük meg a nézeteket, akár lineárisan, akár rácsban. Egy olyan adapterrel kell megbirkóznia, amely a contactList elemeinket Views OneContactView-vé, majd ViewHolders OneContactViewHolder-vé alakítja, amelyet a RecyclerView a számunkra biztosított módszereken belül hajlandó dolgozni.
A RecyclerView nyersanyaga a nézeteink, például a OneContactView és az adatforrás.
contactList:Array<Contact>
Egy egyszerű forgatókönyvet használtunk kiindulási pontként, hogy megtudjuk, mit próbál elérni a RecyclerView.
Könnyen érthető az az alapeset, amikor 1000 névjegyből álló statikus tömbünk van, amelyet meg akarunk mutatni a felhasználónak.
A RecyclerView gépezet akkor kezd igazán élni, amikor a lista már nem statikus.
A dinamikus listánál arra kell gondolnunk, hogy mi történik a képernyőn megjelenő Nézettel, amikor hozzáadunk egy elemet a listához, vagy eltávolítunk egy elemet a listáról.
RecyclerView.LayoutManager
Eltekintve attól, hogy eldöntjük, hogyan kell nézeteinket lineárisan vagy rácsban elhelyezni. A LayoutManager sokat dolgozik a motorháztető alatt, hogy segítsen az újrahasznosítónak tudni, mikor kell újrahasznosítani.
Felelős a képernyőn jelenleg látható nézetek nyomon követéséért, és ezen információk továbbításáért az újrahasznosítási mechanizmus felé. Amikor a felhasználó lefelé görget, az elrendezéskezelő feladata, hogy tájékoztassa az Újrahasznosítási rendszert azokról a Nézetekről, amelyek felül élnek, hogy újra felhasználhatók legyenek ahelyett, hogy ott maradnának és memóriát fogyasztanának, vagy ahelyett, hogy megsemmisítenék és létre kellene hozniuk őket. újakat.
Ez azt jelenti, hogy a LayoutManagernek nyomon kell követnie, hol van a felhasználó, miközben görgeti a listánkat. Ezt úgy teszi, hogy van egy listája azokról a pozíciókról, amelyek indexalapnak számítanak, azaz az első tétel 0-ról indul, és a listánkban szereplő tételek számának megfelelően növekszik.
Ha 10 elemet meg tudunk nézni a mondjuk 100-as listánkon, akkor az elején a LayoutManager tudatában van annak, hogy a 0-s nézet fókuszban van egészen a View-9-ig. Lapozás közben a LayoutManager képes kiszámítani a kikerülő nézeteket. a fókusz.
A LayoutManager fel tudja adni ezeket a nézeteket az újrahasznosítási mechanizmusnak, hogy újra felhasználhatók legyenek (új adatok köthetők hozzájuk, pl. egy nézet névjegyadatai eltávolíthatók, és a következő szegmensből származó új névjegyadatok helyettesíthetik a helyőrzőket).
Ez egy boldog eset lenne, ha a listánk statikus, de a RecyclerView használatának egyik leggyakoribb felhasználási esete a dinamikus listák használata, ahol az adatok egy online végpontból vagy akár egy érzékelőből származhatnak. Nemcsak adatok kerülnek hozzáadásra, hanem a listánkon lévő adatok is időnként eltávolításra vagy frissítésre kerülnek.
Adataink dinamikus állapota nagyon megnehezítheti a LayoutManager okoskodását. Emiatt a LayoutManager saját listát vezet az elemekről és pozíciókról, amely elkülönül az Újrahasznosítás összetevő által használt listától. Ez biztosítja, hogy megfelelően végezze el az elrendezési feladatát.
Ugyanakkor a RecyclerView LayoutManager nem akarja hamisan bemutatni a birtokában lévő adatokat. A helyes működés érdekében a LayoutManager adott időközönként (60 ms) szinkronizál a RecyclerView.Adapterrel, megosztva a Listaelemeinkkel kapcsolatos információkat. azaz hozzáadott, frissített, eltávolított, egyik pozícióból a másikba áthelyezett elemek). A LayoutManager, miután megkapta ezt az információt, átszervezi a képernyő tartalmát, hogy megfeleljen a változtatásoknak, amikor szükséges.
A RecylerView-val foglalkozó számos alapvető művelet a RecyclerView.LayoutManager és a RecyclerView.Adapter közötti kommunikáció körül forog, amely az esetenként statikus vagy néha dinamikus adatok listáját tárolja.
Sőt, a RecyclerView olyan módszereket mutat be, amelyek segítségével figyelhetünk olyan eseményekre, mint például az onBindViewHolder, amikor a RecyclerView.Adapter tartalmat köt a listánkból, például egy névjegyet egy ViewHolderhez, így az most hozzászokik az információk képernyőn való megjelenítéséhez.
Egy másik az onCreateViewHolder, amely megmondja, hogy mikor jelenik meg a RecyclerView. Az illesztő olyan szokásos nézetet vesz fel, mint a OneContactView, és egy ViewHolder-elemmé alakítja át, amellyel a RecyclerView együttműködhet. ViewHolderünkből a RecyclerView általi használatra. onViewDetached
Az újrahasznosítást lehetővé tevő alapvető mechanizmusokon kívül. A RecyclerView módokat kínál a viselkedés testreszabására az újrahasznosítás befolyásolása nélkül.
A nézetek újrafelhasználása megnehezíti a statikus nézetekkel megszokott általános tevékenységeket, például az onClick eseményekre való reagálást.
Mint tudjuk, hogy a RecyclerView.LayoutManager
amely bemutatja a Nézeteket a felhasználónak, egy pillanatra eltérhet az elemek listája a RecyclerView.Adapter
amely tartalmazza azt a listát, amelyet egy adatbázisban tároltunk vagy egy forrásból streameltünk. Ha az OnClick eseményeket közvetlenül a Views szolgáltatásba helyezi, az váratlan viselkedéshez vezethet, például rossz névjegy törléséhez vagy módosításához.
Gradle
Ha a RecyclerView-t szeretnénk használni, akkor függőségként hozzá kell adnunk a build .gradle fájlunkhoz.
A következő példában az „androidx.recyclerview:recyclerview:1.1.0” megvalósítást használtuk, amely a cikk szerint a legújabb verzió.
Miután hozzáadtuk a függőséget a mi Gradle fájlt, akkor a rendszer rákérdez Android Stúdió a Syncfelvázolja a változásokat,
Így a miénk Gradle fájl úgy fog kinézni, mint miután hozzáadott egy RecyclerView-t egy üres projekthez, csak az alapértelmezett értékekkel.
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" }
Jelenleg csak egy elrendezési fájlunk van. Egy egyszerű példával kezdjük, ahol a RecyclerView segítségével megjelenítjük a gyümölcsnevek listáját a képernyőn.
Tételek listája
A MainActivity fájlhoz navigálunk, és létrehozunk egy tömböt gyümölcsnevekkel, közvetlenül a beállítás során generált onCreate() metódus előtt.
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) } }
A következő célunk az lesz, hogy ezt a listát egy RecyclerView segítségével megjelenítsük a képernyőn.
Ehhez navigálunk az elrendezési könyvtárba, amely tartalmazza az elrendezéseinket, és létrehozunk egy nézetet, amely egy gyümölcs megjelenítéséért lesz felelős.
A listánk minden eleméhez használandó elrendezés
<?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>
A fenti TextView-ban hozzáadtunk egy azonosítómezőt, amely a nézet azonosítására szolgál.
Alapértelmezés szerint nem jön létre. Szövegnézetünkben a fruitName azonosítója megegyezik az adatokkal, amelyek hozzá lesznek kötve.
A RecyclerView hozzáadása a fő elrendezéshez
Ugyanebben a tevékenységben található a main_layout.xml elrendezési fájl, amely alapértelmezés szerint létrejött számunkra.
Ha egy üres projektet választunk. Ez generált egy XML tartalmaz egy ConstraintLayout, és belül egy TextView lesz „Hello” szöveggel.
Az összes tartalmat töröljük, és az elrendezés csak a RecyclerView-t tartalmazza, az alábbiak szerint:
<?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" />
Hozzáadtunk egy id attribútumot is a RecyclerView-hoz, amellyel hivatkozni fogunk rá a kódunkban.
android:id="@+id/fruitRecyclerView"
Ezután visszatérünk a MainActivity fájlunkhoz. Az általunk létrehozott azonosítók segítségével hivatkozhatunk majd a most létrehozott nézetekre.
Kezdjük a RecyclerView hivatkozásával a findViewById() metódus használatával Android. Ezt az onCreate() metódusunkban fogjuk megtenni.
Az onCreate() metódusunk a következőképpen fog kinézni.
override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContentView(R.layout.activity_main) var myFruitRecyclerView: RecyclerView = findViewById(R.id.fruitRecyclerView) }
Hozzon létre egy ViewHolder-t
Ezután létrehozunk egy RecyclerView.ViewHolder-t, amely a nézetünk felvételéért és ViewHolderré alakításáért felelős, amelyet a RecyclerView a tételeink megjelenítésére használ.
Ezt közvetlenül a mókás onCreate() metódusunk után tesszük meg.
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) }
Hozzon létre egy RecyclerViewAdaptert
Ezután létrehozunk egy FruitArrayAdapter osztályt, amely kiterjeszti a RecyclerView.Adapter osztályt.
Az általunk létrehozott FruitArrayAdapter a következőkért lesz felelős.
Gyümölcsneveket vesz át a gyümölcstömbből. Létrehoz egy ViewHolder-t a one_fruit_view.xml nézetünkkel, majd a gyümölcsöt egy ViewHolderhez köti, és dinamikusan köti a tartalmat az általunk létrehozott one_fruit_view.xml nézethez.
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 A Studio piros csíkokat fog hozzáadni a FruitArrayAdapterünkhöz, jelezve, hogy megvalósítanunk kell egy olyan módszert, amellyel a RecyclerView tömbünket egy ViewHolderhez csatlakoztathatja, amelyet a RecyclerView használhat.
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) { } }
Kezdjük a generált kód legegyszerűbb bitjével, a getItemCount() metódussal. Tudjuk, hogyan kaphatjuk meg a tömbünk elemeinek számát az array.length metódus meghívásával.
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) { } }
Ezután megvalósítjuk a metódus felülbírálása fun onCreateViewHolder.
Ez az a hely, ahol a RecyclerView arra kér minket, hogy segítsünk neki egy FruitHolder létrehozásában.
Ha visszaemlékezünk, így nézett ki a FruitViewHolder osztályunk:
class FruitViewHolder(fruitView: View): RecyclerView.ViewHolder(fruitView)
Ehhez a fruitView-nk szükséges, amelyet xml-fájlként hoztunk létre one_fruit_view.xml
A következőképpen hozhatunk létre hivatkozást erre az xml-re, és konvertálhatjuk nézetté.
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) { } }
A fennmaradó bit a felülírás
fun onBindViewHolder(holder: FruitViewHolder, position: Int)
A RecyclerView.Adapter egy pozíció egész számmal kérdez rá, amivel lekérünk egy elemet a listánkból. Tartót is biztosít számunkra, hogy a fruitArray-ből kapott elemet a nézettartóban lévő nézethez köthessük.
A ViewHoderben tárolt nézet a ViewHolder.itemView mezőn keresztül érhető el. Miután megkaptuk a nézetet, a korábban létrehozott id fruitName segítségével állíthatjuk be a tartalmat.
override fun onBindViewHolder(holder: FruitViewHolder, position: Int) { var ourFruitTextView = holder.itemView.findViewById<TextView>(R.id.fruitName) var aFruitName = fruitArray.get(position) ourFruitTextView.setText(aFruitName) }
Ezzel a FruitArrayAdapterünk elkészült, és a következőképpen néz ki.
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) } }
Végül készen állunk a RecyclerView fennmaradó darabjainak bekötésére. Melyek létrehoznak egy LayoutManager-t, amely megmondja a RecyclerView-nak, hogyan jelenítse meg a lista tartalmát. A LinearLayoutManager használatával lineárisan, vagy a GridLayoutManager vagy a StaggeredGridLayoutManager segítségével rácsban kell-e megjeleníteni.
Elrendezéskezelő létrehozása
Visszatérünk az onCreate funkcióba, és hozzáadjuk a LayoutManagert
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 }
Csatlakoztassa adapterünket az elemekhez, és állítsa be a RecyclerView-ba
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 }
Létrehoztunk egy fruitListAdapter példányt is, és beadtuk a gyümölcsnevek tömbjét.
És lényegében mindannyian készen vagyunk.
A teljes MainActivity.kt fájl a következőképpen néz ki.
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) } } }