Android RecyclerView: Co je, učte se pomocí jednoduchých příkladů
⚡ Chytré shrnutí
RecyclerView je pokročilý Android Widget, který efektivně zobrazuje velké, posouvatelné datové sady recyklací omezeného počtu zobrazení. Pro navázání datových kolekcí, které se mění za běhu, se spoléhá na adaptér, správce rozvržení a držitele zobrazení.

V čem je RecyclerView Android?
Jedno RecyclerView je flexibilnější a pokročilejší verze GridView a ListView. Jedná se o kontejner pro zobrazení velkých datových sad, který lze efektivně posouvat díky omezenému počtu zobrazení. Widget RecyclerView můžete použít, pokud máte datové kolekce, jejichž prvky se mění za běhu v závislosti na síťové události nebo akci uživatele.
Zobrazení
Jedno Android Platforma používá třídy View a ViewGroup k vykreslování položek na obrazovce. Tyto třídy jsou abstract a jsou rozšířeny na různé implementace, aby vyhovovaly případu použití. Například TextView má jednoduchý účel zobrazení textového obsahu na obrazovce. EditText se rozšiřuje ze stejné třídy View a přidává další funkce, které uživateli umožňují zadávat data.
Je možné si vytvořit vlastní zobrazení pro dosažení větší flexibility při vývoji.ping uživatelská rozhraní. Třída View poskytuje metody, které můžeme přepsat pro kreslení na obrazovce, a prostředky pro předávání parametrů, jako je šířka, výška a naše vlastní atributy, které bychom chtěli přidat do našeho View, aby se choval podle našich představ.
Zobrazit skupiny
Třída ViewGroup je druhem třídy View, ale na rozdíl od jednoduché třídy View, jejíž úkolem je pouze zobrazování, nám ViewGroup umožňuje umístit více pohledů do jednoho pohledu, na který se můžeme odkazovat jako na celek. V tomto případě se pohled vytvořený na nejvyšší úrovni, ke kterému přidáváme další jednoduché pohledy, nazývá „rodič“ a pohledy, které jsou přidávány uvnitř, se nazývají „děti“.
Můžeme si představit View jako pole a ViewGroup jako pole polí. Vzhledem k tomu, že pole polí je samo o sobě polem, můžeme vidět, jak lze ViewGroup zacházet jako s 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 nám také umožňuje definovat, jak jsou podřízené prvky uspořádány uvnitř zobrazení, například zda jsou rozloženy svisle nebo vodorovně. Uvnitř zobrazení můžeme mít různá pravidla pro interakci. Například textové zobrazení následující po sobě by mělo mít vzdálenost 12 dp, zatímco obrázek následovaný textovým zobrazením by měl mít vzdálenost 5 dp.
Tak by to bylo, kdybychom se vyvíjeliping vlastní ViewGroup od nuly. Abychom tyto konfigurace zjednodušili, Android poskytuje třídu nazvanou LayoutParams, kterou můžeme použít k zadávání těchto konfigurací.
Jedno Android dokumentace poskytuje některé výchozí parametry, které bychom implementovali při konfiguraci naší vlastní skupiny zobrazení (ViewGroup). Některé běžné parametry se týkají šířky, výšky a okraje (margin). Ve výchozím nastavení mají tyto konfigurace strukturu android:layout_height pro výšku a android:layout_width pro šířku. V tomto ohledu můžete při vytváření skupiny zobrazení (ViewGroup) dále vytvořit parametry LayoutParams specifické pro to, jak se má vaše skupina zobrazení (ViewGroup) chovat.
Android dodává se s výchozími zobrazeními a skupinami zobrazení, které můžeme použít k provádění mnoha běžných úkolů, které potřebujeme. Jedním z příkladů, které jsme zmínili, je TextView. Jedná se o jednoduchý pohled, který má konfigurovatelné aspekty, jako je výška, šířka a velikost textu. Máme také ImageView pro zobrazení obrázků a, jak jsme již zmínili, EditText, mimo jiné. Android má také vlastní ViewGroups, do kterých můžeme přidat naše zobrazení a získat očekávané chování.
Lineární rozvržení
LinearLayout nám umožňuje přidávat do něj položky zobrazení (View). LinearLayout má atribut orientace, který určuje, jak bude rozložen na obrazovce. Má také atributy LinearLayout.LayoutParams, které určují pravidla pro zobrazení uvnitř. Například atribut android:center_horizontal by vycentroval zobrazení podél vodorovné osy, zatímco android:center_vertical by vycentroval obsah zobrazení podél svislé osy.
Zde je několik obrázků, které vám pomohou pochopit centrování. Představujeme si, že se jedná o jednoduchý TextView uvnitř prostoru 200 pixelů x 200 pixelů; atributy centrování by způsobily, že se bude chovat následovně.
android:center_horizontal
Horizontálně centrovaný obsah
android:center_vertical
Svisle vycentrovaný obsah
android:centrum
Vycentrovaný obsah
Základní součásti RecyclerView
Základní součásti RecyclerView
Níže jsou uvedeny důležité součásti RecyclerView:
RecyclerView.Adapter
Základ #1 – Vzor adaptéru
Adaptér je zařízení, které transformuje atributy jednoho systému nebo zařízení na atributy jinak nekompatibilního zařízení nebo systému. Některé adaptéry upravují atributy signálu nebo napájení, zatímco jiné jednoduše přizpůsobují fyzický tvar jednoho konektoru druhému.
Jednoduchý příklad z reálného života, který vysvětluje použití adaptéru, je situace, kdy potřebujeme propojit zařízení, ale jejich připojovací porty se vzájemně neshodují. To může nastat, když navštívíte jinou zemi, která používá různé druhy zásuvek. Pokud s sebou nesete nabíječku na telefon nebo notebook, nebude možné ji připojit k elektrickým zásuvkám. Nevzdáte to však a jednoduše si pořídíte adaptér, který se vloží mezi zásuvku a nabíječku a umožní nabíjení.
To je případ v programování, kdy chceme propojit dvě datové struktury dohromady, abychom splnili nějaký úkol, ale jejich výchozí porty nemají způsob, jak spolu komunikovat.
Použijeme jednoduchý příklad zařízení a nabíječky. Budeme mít dva případy nabíječek, americký a britský.
class AmericanCharger() { var chargingPower = 10 } class BritishCharger(){ var chargingPower = 5 }
Poté vytvoříme dvě zařízení.
class AmericanDevice() class BritishDevice()
Jako příklad pak můžeme vytvořit nějaké instance zařízení pro hraní.
var myAmericanPhone = new AmericanDevice() var myBritishPhone = new BritishDevice()
Poté si představíme koncept nabíjení pro obě zařízení přidáním metody s názvem charge() do zařízení. Metoda přijme příslušnou nabíječku a na jejím základě provede nabíjení.
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 } }
V tomto případě, na základě naší analogie, bychom z nějakého důvodu potřebovali použít britskou nabíječku BritishCharger, když používáme americké zařízení AmericanDevice, nebo naopak.
Ve světě programování je to obvykle při míchání knihoven, které nabízejí stejnou funkcionalitu (v našem kontextu je naše sdílená funkce zpoplatněna). Potřebovali bychom najít způsob, jak to umožnit.
Pokud budeme postupovat podle analogie, budeme muset jít do obchodu s elektronikou a koupit adaptér, který nám umožní nabíjet zařízení AmericanDevices s danou značkou BritishChargers. Z programátorského hlediska budeme výrobcem adaptéru.
Vytvoříme adaptér, který přesně odpovídá vzoru, který bychom potřebovali pro vytvoření druhého. Implementujeme ho jako třídu následovně. Nemusí to být nutně třída, může to být funkce, která zdůrazňuje, co vzor adaptéru obecně dělá. Použijeme třídu, protože odpovídá většině použití 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 } }
Ve světě programování je rozdíl v zásuvkách analogický s rozdílem v metodách používaných pro nabíjení. Nabíječky s různými metodami by znemožňovaly jejich použití.
var myBritishDevice = new BritishDevice() var americanChargerIFound = new AmericanCharger()
Volání metody charge() na myBritishDevice s americanChargerIFound by nefungovalo, protože AmericanDevice akceptuje pouze AmericanCharger. Takže to není možné:
var myBritishDevice = new BritishDevice() var americanChargerIFound = new AmericanCharger() myBritishDevice.charge(americanChargerIFound)
V tomto scénáři se nám nyní může hodit adaptér, který jsme vytvořili, AmericanToBritishChargerAdapter. Můžeme použít metodu returnNewCharger() k vytvoření nové nabíječky BritishCharger, kterou můžeme použít k nabíjení. Stačí vytvořit instanci našeho adaptéru a předat jí existující nabíječku AmericanCharger, čímž se vytvoří nabíječka BritishCharger, kterou můžeme použít.
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
Při práci se skupinou ViewGroup máme uvnitř ní umístěny prvky View. Správce rozvržení (LayoutManager) má za úkol popsat, jak jsou prvky View uvnitř rozloženy.
Pro srovnání, při práci s ViewGroup typu LinearLayout chceme možnost umístit položky svisle nebo vodorovně. To lze snadno implementovat přidáním atributu orientace, který nám říká, jak bude LinearLayout umístěn na obrazovce. Toho lze dosáhnout pomocí android:orientation=VERTICAL|HORIZONTAL atribut.
Máme také další skupinu ViewGroup s názvem GridLayout. Její použití je, když chceme umístit zobrazení (Views) do obdélníkové mřížkové struktury. To může být například z důvodů, jako je snazší přístup k datům, která prezentujeme uživateli aplikace. GridLayout je navržen tak, aby umožňoval konfigurace, které vám pomohou dosáhnout tohoto cíle definováním rozměrů mřížky; můžeme mít například mřížku 4×4 nebo 3×2.
RecyclerView.ViewHolder
ViewHolder je abstracTřída t, kterou také rozšiřujeme z RecyclerView. ViewHolder nám poskytuje běžné metody, které nám pomáhají odkazovat na View, který jsme umístili na RecyclerView, a to i poté, co recyklační mechanismus v RecyclerView změnil různé reference, o kterých nevíme.
Velké seznamy
RecyclerViews se používají, když chceme uživateli prezentovat opravdu velkou sadu Views, aniž bychom vyčerpali všechny možnosti. RAM na našem zařízení pro každou vytvořenou instanci pohledu.
Pokud si vezmeme příklad seznamu kontaktů, máme obecnou představu o tom, jak by jeden kontakt v seznamu vypadal. Pak bychom vytvořili šablonu rozvržení, což je ve skutečnosti zobrazení (View), se sloty, kam se budou vkládat různá data z našeho seznamu kontaktů. Následuje pseudokód, který vysvětluje celý účel:
//OneContactView
<OneContact>
<TextView>{{PlaceHolderForName}}</TextView>
<TextView>{{PlaceHolderForAddress}}</TextView>
<ImageView>{{PlaceHolderForProfilePicture}}</ImageView>
<TextView>{{PlaceHolderForPhoneNumber}}</TextView>
</OneContact>
Pak bychom měli seznam kontaktů tohoto typu:
<ContactList> </ContactList>
Pokud bychom obsah napevno kódovali, neměli bychom programově přidávat nový obsah do seznamu bez nutnosti přepisovat aplikaci. Naštěstí pro nás je přidání View do ViewGroup podporováno… addView(view:View) metoda. Přesto se nejedná o způsob, jakým se do RecyclerView přidávají podřízené pohledy.
V našem případě bychom měli dlouhý seznam kontaktů. Pro každý kontakt v seznamu bychom museli vytvořit OneContactView a naplnit data uvnitř View tak, aby odpovídala polím v naší třídě Contact. Jakmile budeme mít zobrazení, museli bychom ho přidat do RecyclerView, abychom seznam zobrazili.
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)
Máme pole kontaktů. OneContactView obsahuje sloty pro převzetí obsahu z třídy Contact a jeho zobrazení. V RecyclerView musíme přidat Views, aby nám pomohl s jeho recyklační schopností.
RecyclerView nám ve skutečnosti neumožňuje přidat pohled, ale umožňuje nám přidat ViewHolder. V tomto scénáři tedy máme dva kusy, které chceme propojit, ale které se neshodují. A zde přichází na řadu náš adaptér. RecyclerView nám poskytuje adaptér, podobně jako náš AmericanToBritishChargerAdapter() z dřívějších dob, které nám umožnily přeměnit naši nabíječku AmericanCharger, která byla s naším zařízením BritishDevice nepoužitelná, na něco použitelného, něco jako napájecí adaptér v reálném životě.
V tomto scénáři by adaptér vzal naše pole kontaktů a náš View a z nich by vygeneroval ViewHoldery, které je RecyclerView ochoten přijmout.
RecyclerView poskytuje rozhraní, které můžeme rozšířit a vytvořit tak náš adaptér pomocí třídy RecyclerView.Adapter. Uvnitř tohoto adaptéru je způsob, jak vytvořit třídu ViewHolder, se kterou chce RecyclerView pracovat. Takže máme stejnou situaci jako předtím, ale s jednou věcí navíc, adaptérem.
Máme pole kontaktů, zobrazení jednoho kontaktu (OneContactView) a RecyclerView, což je seznam zobrazení, které poskytuje recyklační služby, ale je ochotno přijmout pouze ViewHoldery. V tomto scénáři nyní máme třídu RecyclerView.Adapter, která má metodu pro vytváření ViewHolderů uvnitř.
fun createViewHolder(@NonNull parent: ViewGroup, viewType: Int): ViewHolder
RecyclerView.ViewHolder je ABStracTřída t, která bere náš View jako argument a převádí ho na ViewHolder. Používá wrapper vzor, který se používá k rozšíření schopností tříd.
Základ #2 – Vzor obalu
Na jednoduchém příkladu si ukážeme, jak můžeme přimět zvířata mluvit.
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
Ve výše uvedeném příkladu máme dvě zvířata. Pokud bychom náhodou chtěli přidat metodu, která by je přiměla mluvit, ale autor knihovny nebyl zrovna zábavný, stále bychom našli způsob. Potřebovali bychom obalovací kód pro naši třídu Animal. To bychom udělali tak, že bychom třídu Animal přijali jako konstruktor pro naši třídu.
class SpeechPoweredAnimalByWrapper(var myAnimal:Animal){ fun sound(){ myAnimal.sound() } fun speak(){ println("Hello, my name is ${myAnimal.name}") } }
Nyní můžeme předat instanci zvířete do SpeechPoweredAnimalByWrapper. Volání metody sound() na něm by zavolalo předanou metodu sound() zvířete. Máme také další metodu speak(), která se počítá jako nová funkcionalita, kterou přidáváme k předaným zvířatům. Můžeme ji použít následovně:
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"
Pomocí tohoto vzoru můžeme přidávat třídy a přidávat funkce. Stačí předat instanci třídy a nové metody definované naším wrapem.ping třída.
Ve výše uvedeném případě jsme použili třídu concrete. Je také možné ji implementovat v ABS.tractřída t. Museli bychom změnit třídu SpeechPoweredAnimalByWrapper na abstract a máme hotovo. Změníme název třídy na kratší, aby byl lépe čitelný.
abstract class SpeechPowered(var myAnimal:Animal){ fun sound(){ myAnimal.sound() } fun speak(){ println("Hello, my name is ${myAnimal.name}") } }
Je to stejné jako předtím, ale znamenalo by to něco jiného. V normální třídě můžeme mít instanci třídy stejným způsobem, jako jsme vytvořili cat1 a dog1. Abs.tracTřídy t však nejsou určeny k vytváření instancí, ale k rozšiřování jiných tříd. Jak bychom tedy použili novou funkci SpeechPowered(var myAnimal:Animal) abstracTřída t? Můžeme ji použít vytvořením nových tříd, které ji rozšíří a tím získají její funkcionalitu.
V našem příkladu vytvoříme třídu SpeechPoweredAnimal, která tuto třídu rozšiřuje.
class SpeechPoweredAnimal(var myAnimal:Animal):SpeechPowered(myAnimal)
var cat1 = Cat("Tubby") var speakingKitty = SpeechPoweredAnimal(cat1) speakingKitty.speak() //"Hello, my name is Tubby"
Toto je stejný vzor použitý v ViewHolderu. Třída RecyclerView.ViewHolder je třída ABS.tract třída, která přidává funkcionalitu do View, podobně jako jsme přidali metodu speak zvířatům. Tato přidaná funkcionalita je to, co ji činí funkční při práci s RecyclerView.
Takto bychom vytvořili OneContactViewHolder z 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 má adaptér, který nám umožňuje propojit naše pole kontaktů s ContactsView pomocí RecyclerView.
Přidání pohledu
Skupina zobrazení se nepřekresluje automaticky, ale podle určitého plánu. Na vašem zařízení se může stát, že se překresluje každých 10 ms nebo 100 ms; nebo pokud zvolíme absurdní číslo, řekněme 1 minutu, když přidáme zobrazení do skupiny zobrazení, změny se zobrazí o 1 minutu později, když se skupina zobrazení „obnoví“.
RecyclerView.Recycler
Základ č. 3 – Ukládání do mezipaměti
Jedním z nejlepších příkladů, kde pravidelně provádíme aktualizace, je prohlížeč. Představme si například, že web, který navštěvujeme, je statický a neodesílá obsah dynamicky; abychom viděli změny, museli bychom stránku neustále obnovovat.
Pro tento příklad si představme, že daným webem je Twitter. Měli bychom uvedenou sérii statických tweetů a jediný způsob, jak bychom viděli nové tweety, by bylo kliknutí na tlačítko Obnovit pro obnovení obsahu.
Překreslení celé obrazovky je samozřejmě nákladná záležitost. Představte si, že bychom měli omezenou šířku pásma u našeho telefonního operátora a náš seznam tweetů by obsahoval spoustu obrázků a videí; bylo by nákladné znovu stahovat veškerý obsah stránky při každém obnovení.
Potřebovali bychom způsob, jak ukládat již načtené tweety a zajistit, aby náš další požadavek dokázal zjistit, které tweety již obsahuje. Proto se znovu nestahuje vše, ale pouze se stahují nové tweety a také se kontroluje, zda se nějaký lokálně uložený tweet již nenachází, aby se mohl lokálně smazat. To, co popisujeme, se nazývá ukládání do mezipaměti.
Informace, které o obsahu, který máme, posíláme na webové stránky, se nazývají metadata. Takže v pravém slova smyslu neříkáme jen „chceme načíst váš web“, ale říkáme „chceme načíst váš web a zde je část obsahu, který jsme již uložili z posledního načítání; použijte ho prosím k zasílání pouze toho, co tam není, abychom nespotřebovali mnoho šířky pásma.“
Layout Calls – Seznam tweetů musí být šílený
Příkladem volání rozvržení je scrollToPosition. Toto je běžný příklad, který se vyskytuje například v chatovacích aplikacích. Pokud někdo v chatovacím vlákně odpoví na bublinu chatu z dřívější doby, některé chatovací aplikace přidají odpověď a odkaz na bublinu chatu, který vás po kliknutí přesměruje na místo, kde byla vaše původní zpráva.
V případě, že tuto metodu zavoláme předtím, než přidáme LayoutManager do našeho RecyclerView a předtím, než máme RecyclerView.Adapter, je scrollToPosition(n:Int) jednoduše ignorována.
Komunikace mezi komponentami RecyclerView
Základní práce č. 4 – Zpětná volání
RecyclerView má při své práci spoustu pohyblivých částí. Musí spolupracovat s LayoutManagerem, který nám říká, jak uspořádat Viewy, buď lineárně, nebo v mřížce. Musí spolupracovat s adaptérem, který převádí naše položky (contactList) na Viewy (OneContactView) a poté na ViewHoldery (OneContactViewHolder), se kterými je RecyclerView ochoten pracovat pomocí metod, které poskytuje.
Surovinou pro RecyclerView jsou naše Views, např. OneContactView, a zdroj dat.
contactList:Array<Contact>
Jako výchozí bod jsme použili jednoduchý scénář, abychom získali představu o tom, čeho se RecyclerView snaží dosáhnout. Základní případ, kdy máme statické pole 1000 kontaktů, které chceme uživateli zobrazit, je snadno pochopitelný. Mechanismus RecyclerView se skutečně začne projevovat, když seznam přestane být statický. U dynamického seznamu musíme přemýšlet o tom, co se stane s pohledem na obrazovce, když do seznamu přidáme položku nebo ji ze seznamu odebereme.
RecyclerView.LayoutManager
Kromě rozhodování o tom, jak budou naše pohledy rozloženy, ať už lineárně nebo v mřížce, LayoutManager v Helu vykonává spoustu práce „pod kapotou“.ping Recyklační firma ví, kdy má provést recyklaci.
Je zodpovědný za uchováníping track zobrazení aktuálně viditelných na obrazovce a sdělení těchto informací mechanismu recyklace. Když uživatel posouvá stránku dolů, LayoutManager je zodpovědný za informování systému recyklace o zobrazeních, která se nahoře rozostří, aby je bylo možné znovu použít, místo aby tam zůstala a spotřebovávala paměť nebo byla zničena a následně vytvářela nová.
To znamená, že LayoutManager musí udržovat track, kde se uživatel nachází při posouvání našeho seznamu. Toho dosahuje pomocí seznamu pozic založeného na indexu, tj. první položka začíná od 0 a její počet se zvyšuje podle počtu položek v našem seznamu.
Pokud si můžeme zobrazit 10 položek v našem seznamu, řekněme 100, LayoutManager si na začátku uvědomí, že má ve fokusu View-0 až View-9. Při posouvání je LayoutManager schopen vypočítat pohledy, které se rozostří.
Správce rozvržení (LayoutManager) je schopen tyto pohledy uvolnit do mechanismu recyklace, aby je bylo možné znovu použít (lze k nim navázat nová data; např. kontaktní data pohledu lze odstranit a zástupné symboly mohou nahradit nová kontaktní data z dalšího segmentu).
To by byl vhodný případ, pokud by náš seznam byl statický, ale jedním z nejběžnějších případů použití RecyclerView jsou dynamické seznamy, kde data mohou pocházet z online koncového bodu nebo dokonce ze senzoru. Nejenže jsou data přidávána, ale data v našem seznamu jsou někdy odstraňována nebo aktualizována.
Dynamický stav našich dat může velmi ztěžovat uvažování o LayoutManageru. Z tohoto důvodu si LayoutManager udržuje vlastní seznam položek a pozic, oddělený od seznamu, který používá komponenta recyklace. To zajišťuje, že jeho úloha rozvržení probíhá správně.
Zároveň LayoutManager v RecyclerView nechce zkreslovat data, která má k dispozici. Aby LayoutManager fungoval správně, synchronizuje se s RecyclerView.Adapter v daných intervalech (60 ms) a sdílí informace o položkách našeho seznamu, tj. o položkách přidaných, aktualizovaných, odebraných nebo přesunutých z jedné pozice na druhou. LayoutManager po obdržení těchto informací v případě potřeby reorganizuje obsah na obrazovce tak, aby odpovídal změnám.
Mnoho základních operací, které se zabývají RecyclerView, se točí kolem komunikace mezi RecyclerView.LayoutManager a RecyclerView.Adapter, které ukládají naše seznamy někdy statických nebo někdy dynamických dat.
Kromě toho nám RecyclerView nabízí metody, které můžeme použít k naslouchání událostem, například onBindViewHolder, když náš RecyclerView.Adapter váže obsah z našeho seznamu (např. kontakt) k ViewHolderu, aby se informace zobrazovaly na obrazovce.
Další je onCreateViewHolder, který nám říká, kdy RecyclerView.Adapter vezme regulární View, jako je OneContactView, a převede ho na položku ViewHolder, se kterou může RecyclerView pracovat.
Kromě základních mechanismů, které umožňují recyklaci, nabízí RecyclerView způsoby, jak přizpůsobit chování bez ovlivnění recyklace. Opětovné použití Viewů ztěžuje běžné věci, na které jsme zvyklí u statických Viewů, jako je například reakce na události onClick.
Jak víme, RecyclerView.LayoutManager který uživateli představuje pohledy, může mít na chvíli jiný seznam položek než RecyclerView.Adapter který má seznam uložený v databázi nebo streamovaný ze zdroje. Vložení událostí OnClick přímo do zobrazení může vést k neočekávanému chování, jako je například smazání nesprávného kontaktu.
Gradle
Pokud chceme použít RecyclerView, musíme ho přidat jako závislost do našeho souboru build.gradle. V následujícím příkladu jsme použili implementaci „androidx.recyclerview:recyclerview:1.1.0“, což je podle tohoto článku nejaktuálnější verze.
Po přidání závislosti do našeho Gradle souboru, budeme vyzváni Android Studio synchronizovat změny. Takto se naše Gradle Soubor se postará o přidání RecyclerView do prázdného projektu pouze s výchozími hodnotami.
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" }
Momentálně máme pouze jeden soubor s rozvržením. Začneme jednoduchým příkladem, kde použijeme RecyclerView k zobrazení seznamu názvů ovoce na obrazovce.
Seznam položek
Přejdeme do našeho souboru MainActivity a vytvoříme pole s názvy ovoce uvnitř, těsně před metodou onCreate(), která byla vygenerována během instalace.
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ším dalším cílem bude prezentovat tento seznam na obrazovce pomocí RecyclerView. Abychom to dosáhli, přejdeme do adresáře s rozvržením, který obsahuje naše rozvržení, a vytvoříme View, který bude zodpovědný za zobrazení jednoho ovoce.
Rozvržení, které se použije pro každou položku v našem seznamu
<?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>
Ve výše uvedeném TextView jsme přidali pole id, které bude použito k identifikaci View. Ve výchozím nastavení se negeneruje. Našemu TextView jsme přiřadili ID fruitName, které odpovídá datům, která k němu budou vázána.
Přidání RecyclerView do hlavního rozvržení
Ve stejné aktivitě je soubor rozvržení main_layout.xml, který nám byl standardně vygenerován. Pokud bychom zvolili prázdný projekt, vygeneroval by se soubor XML obsahující ConstraintLayout a uvnitř bude TextView s textem „Hello“. Smažeme veškerý obsah a rozvržení bude obsahovat pouze RecyclerView, jak je uvedeno níže:
<?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" />
Také jsme pro RecyclerView přidali atribut id, který budeme používat k odkazování na něj v našem kódu.
android:id="@+id/fruitRecyclerView"
Poté se vrátíme zpět do našeho souboru MainActivity. Pomocí vytvořených ID budeme moci odkazovat na právě vytvořené Viewy. Začneme odkazováním na RecyclerView pomocí metody findViewById() poskytované ` AndroidTo provedeme v naší metodě onCreate(). Naše metoda onCreate() bude vypadat následovně.
override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContentView(R.layout.activity_main) var myFruitRecyclerView: RecyclerView = findViewById(R.id.fruitRecyclerView) }
Vytvořte ViewHolder
Dále vytvoříme RecyclerView.ViewHolder, který bude zodpovědný za převod našeho View na ViewHolder, který RecyclerView použije k zobrazení našich položek. Uděláme to hned po naší metodě 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) }
Vytvořte RecyclerViewAdapter
Dále vytvoříme třídu FruitArrayAdapter, která rozšiřuje třídu RecyclerView.Adapter. Vytvořený FruitArrayAdapter bude zodpovědný za následující: převezme názvy ovoce z pole ovoce, vytvoří ViewHolder pomocí našeho zobrazení one_fruit_view.xml, poté naváže ovoce na ViewHolder a dynamicky naváže obsah na zobrazení, které jsme vytvořili.
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 přidá na náš FruitArrayAdapter červené vlnovky, které nám řeknou, že musíme implementovat metody, které může RecyclerView použít k propojení našeho pole s ViewHolderem.
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) { } }
Začneme s nejjednodušší částí vygenerovaného kódu, metodou getItemCount(). Víme, jak získat počet položek v našem poli voláním vlastnosti size pole.
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) { } }
Pak implementujeme metodu onCreateViewHolder. Zde nás RecyclerView požádá o pomoc s konstrukcí třídy FruitViewHolder. Pokud si dobře vzpomínáme, naše třída FruitViewHolder vypadala takto:
class FruitViewHolder(fruitView: View): RecyclerView.ViewHolder(fruitView)
Vyžaduje náš fruitView, který jsme vytvořili jako XML soubor one_fruit_view.xml. Můžeme vytvořit odkaz na tento XML a převést ho na View následujícím způsobem.
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) { } }
Zbývající bit je přepsání onBindViewHolder.
fun onBindViewHolder(holder: FruitViewHolder, position: Int)
RecyclerView.Adapter se ptá s celočíselnou číslicí pozice, kterou používáme k načtení položky z našeho seznamu. Také nám poskytuje holder, abychom mohli navázat položku získanou z fruitArray na View, který je uložen uvnitř holderu view. View uložený uvnitř ViewHolderu je přístupný prostřednictvím pole ViewHolder.itemView. Jakmile získáme view, můžeme použít id fruitName, které jsme vytvořili dříve, k nastavení obsahu.
override fun onBindViewHolder(holder: FruitViewHolder, position: Int) { var ourFruitTextView = holder.itemView.findViewById<TextView>(R.id.fruitName) var aFruitName = fruitArray.get(position) ourFruitTextView.setText(aFruitName) }
Tím je náš FruitArrayAdapter kompletní a vypadá takto.
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) } }
Konečně jsme připraveni propojit zbývající části našeho RecyclerView, které vytvářejí LayoutManager, jenž bude sdělovat RecyclerView, jak má zobrazit obsah seznamu, zda lineárně pomocí LinearLayoutManager nebo v mřížce pomocí GridLayoutManager nebo StaggeredGridLayoutManager.
Vytvořit Správce rozložení
Vrátíme se do naší funkce onCreate a přidáme 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 }
Připojte náš adaptér k položkám a nastavte jej 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 }
Také jsme vytvořili instanci fruitListAdapter a naplnili ji polem názvů ovoce. A v podstatě jsme hotovi. Kompletní soubor MainActivity.kt vypadá následovně.
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) } } }




