Android RecyclerView: Co je, učte se pomocí jednoduchých příkladů

V čem je RecyclerView Android?

Jedno RecyclerView je widget, který je flexibilnější a pokročilejší verzí GridView a ListView. Jedná se o kontejner pro zobrazení velkých datových sad, které lze efektivně posouvat při zachování omezeného počtu zobrazení. Widget RecyclerView můžete použít, když máte kolekce dat, jejichž prvky se za běhu mění v závislosti na síťové události nebo akci uživatele.

Zobrazení

Jedno Android platforma používá třídy View a ViewGroup ke kreslení položek na obrazovce. Tyto třídy jsou abstraktní a jsou rozšířeny na různé implementace, aby vyhovovaly případu použití. TextView má například 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é umožňují uživateli zadávat data.

Je možné vytvořit naše vlastní uživatelské pohledy, abychom mohli dosáhnout větší flexibility při vývoji uživatelských rozhraní. Třída View poskytuje metody, které můžeme přepsat, abychom kreslili na obrazovku, a prostředky k předávání parametrů, jako je šířka, výška a naše vlastní atributy, které bychom chtěli přidat do našeho pohledu, aby se choval tak, jak bychom si přáli.

Zobrazit skupiny

Třída ViewGroup je druh View, ale na rozdíl od jednoduché třídy View, jejíž odpovědností je pouhé zobrazování, nám ViewGroup dává možnost umístit více pohledů do jednoho pohledu, na který můžeme odkazovat jako na celek. V tomto případě se pohled, který je vytvořen na nejvyšší úrovni, do kterého přidáváme další jednoduchá zobrazení (můžeme také přidat skupiny viewGroups), nazývá „nadřazený“ a pohledy, které jsou přidány uvnitř, jsou „dětské“.

Zobrazení můžeme zobrazit jako pole a ViewGroup jako pole polí. Vzhledem k tomu, že Array of Arrays je Array samo o sobě, můžeme vidět, jak lze se skupinou 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 anology, we can now group views 
//together and the structure that would hold that would be what we call the ViewGroup

Skupina ViewGroup nám také umožňuje definovat, jak jsou děti uvnitř pohledu uspořádány, například jsou umístěny svisle nebo vodorovně. Uvnitř pohledu můžeme mít různá pravidla pro interakci. Například, TextViews následující za sebou by měly mít vzdálenost 12dp, zatímco ImageViews následované TextView by měly mít vzdálenost 5dp.

To by byl případ, kdybychom vyvíjeli vlastní ViewGroup od nuly. Chcete-li tyto konfigurace usnadnit, Android poskytuje třídu nazvanou LayoutParams, kterou můžeme použít k zadávání těchto konfigurací.

Android dokumentace poskytuje některé výchozí parametry, které bychom implementovali při konfiguraci naší vlastní ViewGroup. Některé běžné parametry jsou ty, které se týkají šířky, výšky a okraje. Ve výchozím nastavení mají tyto konfigurace pro výšku strukturu android:layout_height, například android:layout_width. V tomto ohledu můžete při vytváření skupiny ViewGroup dále vytvářet parametry LayoutParams specifické podle toho, jak si přejete, aby se skupina ViewGroup chovala.

Android přichází 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 příkladem, který jsme zmínili, je TextView. Jedná se o jednoduchý pohled, který přichází s konfigurovatelnými aspekty, jako je výška, šířka, velikost textu a podobně. Máme ImageView pro zobrazení obrázků a EditText, jak jsme zmínili, mezi mnoha dalšími. 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 View. LinearLayout je atribut orientace, který určuje, jak bude rozvrženo na obrazovce. Má také LinearLayout.LayoutParams, které diktují pravidla pro zobrazení uvnitř, například atribut android:center_horizontal by vycentroval pohledy podél vodorovné osy, zatímco `android:center_vertical by vycentroval obsah pohledu podél svislé osy.

Zde je několik obrázků pro pochopení centrování. Považovali bychom to za jednoduchý TextView uvnitř prostoru 200 x 200 pixelů, centrování atributů by se chovalo následovně.

android:center_horizontal

Horizontálně centrovaný obsah
Horizontálně centrovaný obsah

android:center_vertical

vertikálně centrovaný obsah
vertikálně centrovaný obsah

android:centrum

Obsah na střed
Vycentrovaný obsah

Základní součásti RecyclerView

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 systému nebo zařízení na vlastnosti jinak nekompatibilního zařízení nebo systému. Některé z nich upravují vlastnosti signálu nebo napájení, zatímco jiné pouze přizpůsobují fyzickou podobu jednoho konektoru druhému.

Jednoduchý příklad, který najdeme v reálném životě pro vysvětlení adaptéru, je situace, kdy potřebujeme propojit zařízení dohromady, ale mají připojovací porty, které se navzájem neshodují. To může být případ, kdy navštívíte jinou zemi, kde používají různé druhy zásuvek. Pokud s sebou nosíte nabíječku telefonu nebo notebooku, nebylo by možné jej připojit k zásuvkám. Nevzdali byste se však, ale jednoduše si pořídili adaptér, který by se dostal mezi zásuvku a nabíječku a umožnil nabíjení.

To je případ programování, kdy chceme pro splnění úkolu propojit dvě datové struktury dohromady, 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. Jeden americký a jeden britský

class AmericanCharger() {
    var chargingPower = 10
}
class BritishCharger(){
    var charginPower = 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é představíme koncept nabíjení pro obě zařízení přidáním metody do zařízení nazvané charge() .

Metoda bere jako vstup svou příslušnou nabíječku a provádí nabíjení na jejím základě.

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 toho či onoho důvodu potřebovali použít BritishCharger při použití 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 zajít do obchodu s elektronikou a koupit si adaptér, který nám umožní nabíjet AmericanDevices danými British Chargers. Z programátorského hlediska to budeme my, kdo bude výrobcem adaptéru.

Pro jeden vytvoříme Adaptér, který přesně odpovídá vzoru, který bychom potřebovali pro vytvoření druhého. Implementujeme to jako třídu následovně. Nemusí to být nutně třída a může to být funkce, která zvýrazňuje to, co vzor adaptéru obecně dělá. Použijeme třídu, která odpovídá většině použití 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
    }
}

Ve světě programování je rozdíl v zásuvkách analogický rozdílu ve vnitřních metodách nabíjení. Nabíječky mající různé metody by znemožnily použití nabíječek.

 var myBritishDevice = new BritishDevice()
var americanChargerIFound = new AmericanCharger()

Pokus o volání metody charge() v myBritishDevice s americanChargerIFound by nefungoval, protože AmericanDevice přijímá pouze AmericanCharger

Takže to není možné

 var myBritishDevice = new BritishDevice()
var americanChargerIFound = new AmericanCharger()
myBritishDevice.charge(americanChargerIFound)

V tomto scénáři jsme vytvořili adaptér

AmericanToBritishChargerAdapter se nyní může hodit. Pomocí metody returnNewCharger() můžeme vytvořit nový BritishCharger, který můžeme použít k nabíjení. Vše, co potřebujeme, je vytvořit instanci našeho adaptéru a napájet jej pomocí americké nabíječky, kterou máme, a vytvoří britskou nabíječku, 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(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

Při práci se skupinou ViewGroup bychom do ní umístili pohledy. Právě LayoutManager by měl za úkol popsat, jak jsou pohledy rozmístěny uvnitř.

Pro účely srovnání, při práci s Linearlayout ViewGroup, případ použití, který chceme, je možnost umístit položky buď svisle nebo vodorovně. To lze snadno implementovat přidáním atributu orientace, který nám říká, jak bude lineární rozložení umístěno na obrazovce. Můžeme to udělat pomocí android:orientation=VERTICAL|HORIZONTAL atribut.

Máme také další ViewGroup s názvem GridLayout, je to případ použití, kdy bychom chtěli umístit pohledy do obdélníkové struktury mřížky. Mohlo by to být z důvodů, jako je například usnadnění používání dat, která předkládáme uživateli aplikace. Návrh GridLayout umožňuje konfigurace, které vám pomohou dosáhnout tohoto cíle tím, že máme konfigurace, kde můžeme definovat rozměry mřížky, například můžeme mít mřížku 4×4, mřížku 3x2.

RecyclerView.ViewHolder

ViewHolder je abstraktní třída, kterou také rozšiřujeme z RecyclerView. ViewHolder nám poskytuje běžné metody, které nám pomáhají odkazovat na pohled, který jsme umístili do RecyclerView, i když zařízení Recycling v RecyclerView změnilo různé reference, o kterých nevíme.

Velké seznamy

RecyclerViews se používají, když chceme uživateli prezentovat opravdu velkou sadu pohledů a přitom nevyčerpávat naše RAM na našem zařízení pro každou vytvořenou instanci pohledu.

Pokud bychom měli vzít případ seznamu kontaktů, měli bychom obecnou představu o tom, jak by jeden kontakt v seznamu vypadal. Co bychom pak udělali, je vytvořit šablonu rozložení – což je vlastně pohled – se sloty, kam se zaplní různá data z našeho seznamu kontaktů. Následuje pseudo kó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 takový seznam kontaktů

 <ContactList>
</ContactList>

Pokud je tomu tak, obsah jsme napevno kódovali, neměli bychom programový způsob přidávání nového obsahu do seznamu bez přepsání aplikace. Naštěstí pro nás. Přidání pohledu do ViewGroup je podporováno addView(view:View) metoda.

I kdyby tomu tak bylo, není to způsob, jakým RecyclerView do něj přidává dětské pohledy.

V našem případě použití bychom měli dlouhý seznam kontaktů. Pro každý kontakt v seznamu bychom museli vytvořit OneContactView a naplnit data uvnitř zobrazení tak, aby odpovídala polím v naší třídě Contact. Poté, co máme zobrazení, bychom jej museli přidat do RecyclerView, aby se zobrazil seznam.

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 řadu kontaktů s názvem OneContactView. Obsahuje sloty pro převzetí obsahu z třídy Contact a jeho zobrazení. V RecyclerView do něj musíme přidat pohledy, aby nám pomohl s jeho recyklační schopností.

RecyclerView nám ve skutečnosti neumožňuje přidat zobrazení, ale umožňuje nám přidat ViewHolder. V tomto scénáři tedy máme dvě věci, které chceme propojit, ale neshodují se. Zde přichází na řadu náš adaptér. RecyclerView nám poskytuje adaptér podobný našemu AmericanToBritishChargerAdapter() z dříve, což nám umožnilo převést naši americkou nabíječku, která byla nepoužitelná s naším britským zařízením, na něco použitelného, ​​podobného napájecímu adaptéru v reálném životě.

V tomto scénáři by adaptér vzal naše pole kontaktů a naše zobrazení a odtud vygeneroval ViewHolders, které je RecyclerView ochoten přijmout.

RecyclerView poskytuje rozhraní, které můžeme rozšířit o vytvoření našeho Adaptéru prostřednictvím třídy RecyclerView.Adapter. Uvnitř tohoto adaptéru je způsob, jak vytvořit třídu ViewHolder, se kterou chce RecyclerView pracovat. Takže to, co máme, je stejná situace jako předtím, ale s jednou věcí navíc, a to je Adaptér.

Máme řadu kontaktů, zobrazení pro zobrazení jednoho kontaktu OneContactView. RecyclerView je seznam pohledů, které poskytují recyklační služby, ale jsou ochotny převzít pouze ViewHolders

Ale v tomto scénáři nyní máme třídu RecyclerView.Adapter, která má uvnitř metodu pro vytváření ViewHolders.

fun createViewHolder(@NonNull parent: ViewGroup, viewType: Int): ViewHolder

RecyclerView.ViewHolder je abstraktní třída, která bere náš pohled jako argument a převádí jej na ViewHolder.

Využívá vzor obalu, který se používá k rozšíření schopností tříd.

Základ #2 – Vzor obalu

Na jednoduchém příkladu demonstrujeme, jak můžeme zvířata přimět 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. Kdybychom náhodou chtěli přidat metodu, jak přimět mluvit, ale s autorem knihovny nebyla žádná legrace, i tak bychom mohli najít způsob. Potřebujeme obal pro naši třídu zvířat. Udělali bychom to tak, že bychom do naší třídy vzali Zvíře jako konstruktéra

 class SpeechPoweredAnimalByWrapper(var myAnimal:Animal){

    fun sound(){
        myAnimal.sound()
    }

    speak(){
        println("Hello, my name is ${myAnimal.name}")
    }
}

Nyní můžeme předat instanci zvířete SpeechPoweredAnimalByWrapper. Volání metody sound() by zavolalo předávanou metodu animal sound(). Máme také další metodu speak(), která se počítá jako nová funkce, kterou přidáme k předávaný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řebírat třídy a přidávat funkce. Vše, co bychom potřebovali, je předat instanci třídy a nové metody definované naší obalovou třídou.

V našem případě výše jsme použili konkrétní třídu. Je také možné implementovat totéž v abstraktní třídě. Museli bychom přidat změnu třídy SpeechPoweredAnimalByWrapper na abstraktní a máme hotovo. Změníme název třídy na kratší, aby byl čitelnější.

abstract class SpeechPowered(var myAnimal:Animal){

    fun sound(){
        myAnimal.sound()
    }

    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, jakým jsme vytvořili cat1 a dog1. Abstraktní třídy však nejsou určeny k vytváření instancí, ale jsou určeny k rozšíření dalších tříd. Jak bychom tedy použili novou abstraktní třídu SpeechPowered(var myAnimal:Animal). Můžeme jej použít vytvořením nových tříd, které jej rozšíří a naopak získají jeho funkčnost.

V našem příkladu vytvoříme třídu SpeechPoweredAnimal, která třídu rozšíří

class SpeechPoweredAnimal(var myAnimal:Animal):SpeechPowered(myAnimal)
var cat1 =  Cat("Tubby")
var speakingKitty = SpeechPoweredAnimal(cat1)
speakingKitty.speak() //"Hello, my name is Tubby"

Jedná se o stejný vzor použitý v ViewHolder. Třída RecyclerView.ViewHolder je abstraktní třída, která přidává funkcionalitu do View, podobně jako jsme přidali metodu speak ke zvířatům. Díky přidané funkčnosti funguje 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 připojit naše pole Contacts k ContactsView pomocí RecyclerView

Přidání pohledu

ViewGroup nepřekresluje ViewGroup automaticky, ale řídí se konkrétním plánem. Na vašem zařízení se může stát, že se překresluje každých 10 ms nebo 100 ms, nebo pokud vybereme absurdní číslo, řekněme 1 minutu, když přidáme View do ViewGroup, uvidíte změny o 1 minutu později, když se ViewGroup „obnoví“.

RecyclerView.Recycler

Základ #3. Ukládání do mezipaměti

Jedním z nejlepších příkladů toho, kde pravidelně obnovujeme, je prohlížeč. Představme si například, že web, který navštěvujeme, je statický a neposílá obsah dynamicky, museli bychom se neustále obnovovat, abychom viděli změny.

Pro tento příklad si představme, že dotyčný web je twitter. Měli bychom uvedenu řadu statických tweetů a jediný způsob, jak bychom se mohli podívat na nové tweety, by bylo kliknutím na tlačítko pro obnovení pro opětovné načtení obsahu.

Přemalování celé obrazovky je samozřejmě nákladná záležitost. Pokud bychom si měli představit, že by tomu tak bylo, měli jsme u našeho telefonního operátora omezenou šířku pásma. A náš seznam tweetů měl 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 uložit již načtené tweety a zajistit, aby náš další požadavek mohl sdělit tweety, které již má. Proto nestahuje znovu vše a získává pouze nové tweety, které má, a také zkontroluje, zda tam některý tweet, který byl uložen lokálně, již není, aby jej mohl lokálně smazat. To, co popisujeme, se nazývá ukládání do mezipaměti.

Informace, které odesíláme na web o obsahu, který máme, se nazývají metadata. Takže ve skutečném smyslu neříkáme jen „chceme načíst váš web“, říkáme „chceme načíst váš web, a zde je část obsahu, který jsme si uložili z posledního načítání, použijte jej posílejte nám pouze to, co tam není, takže nevyužíváme velkou šířku pásma, protože nemáme mnoho zdrojů.“

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 ve věcech, jako jsou chatovací aplikace. Pokud někdo ve vláknu chatu odpoví na bublinu chatu z dřívějšího období, některé aplikace pro chat obsahují odpověď a odkaz na bublinu chatu, který vás po kliknutí přenese na místo, kde byla vaše původní zpráva.

V tomto případě tuto metodu voláme předtím, než přidáme LayoutManager do našeho RecyclerView a než budeme mít RecyclerView.Adapter, scrollToPosition(n:Int) se jednoduše ignoruje.

Komunikace mezi komponentami RecyclerView

Základ #4. Zpětná volání

RecyclerView má při své práci mnoho pohyblivých částí. Musí se vypořádat s LayoutManager, který nám říká, jak organizovat pohledy, buď lineárně nebo v mřížce. Musí se vypořádat s Adaptérem, který převádí naše položky contactList na Views OneContactView a poté na ViewHolders OneContactViewHolder, který je RecyclerView ochoten pracovat v rámci metod, které nám poskytuje.

Surovým materiálem pro RecyclerView jsou naše pohledy, např. OneContactView a zdroj dat.

contactList:Array<Contact>

Použili jsme jednoduchý scénář jako výchozí bod, abychom získali představu o tom, čeho se RecyclerView snaží dosáhnout.

Základní případ, kdy bychom měli statické pole 1000 kontaktů, které chceme uživateli ukázat, je snadno pochopitelné.

Mašinérie RecyclerView se skutečně začne probouzet, když seznam již není statický.

U dynamického seznamu musíme myslet na to, co se stane s Pohledem na obrazovce, když přidáme položku do seznamu nebo odebereme položku ze seznamu.

RecyclerView.LayoutManager

Kromě rozhodování o tom, jak mají být naše pohledy uspořádány lineárně nebo v mřížce. LayoutManager dělá spoustu práce pod kapotou, protože pomáhá recyklátorovi vědět, kdy má provést recyklaci.

Zodpovídá za sledování zobrazení aktuálně zobrazených na obrazovce a za předávání těchto informací recyklačnímu mechanismu. Když uživatel posouvá dolů, správce rozvržení je zodpovědný za informování systému recyklace o pohledech, které se nahoře rozostřují, takže je lze znovu použít, místo aby tam zůstaly a spotřebovávaly paměť nebo je nezničily a musely je vytvářet. nováčci.

To znamená, že LayoutManager musí při procházení našeho seznamu sledovat, kde se uživatel nachází. Dělá to tak, že má seznam pozic, které jsou základem indexu, tj. první položka začíná od 0 a zvyšuje se tak, aby odpovídala počtu položek v našem seznamu.

Pokud dokážeme zobrazit 10 položek na našem seznamu, řekněme 100, na začátku si LayoutManager uvědomí, že má zaostřený pohled-0 až po Pohled-9 Jak posouváme, LayoutManager dokáže vypočítat pohledy, které se dostanou ven. zaměření.

LayoutManager je schopen uvolnit tyto pohledy do mechanismu Recyklace, aby je bylo možné znovu použít (lze na ně 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 šťastný případ, kdyby byl seznam, který máme, 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 se přidávají data, ale data na našem Seznamu jsou někdy odstraněna nebo aktualizována.

Dynamický stav našich dat může velmi ztížit uvažování o LayoutManager. Z tohoto důvodu LayoutManager udržuje vlastní seznam položek a pozic, který je oddělený od seznamu, který komponenta Recyklace používá. Tím zajistíte, že svou úlohu rozvržení provede správně.

Zároveň LayoutManager RecyclerView nechce zkreslovat data, která má. Aby správně fungoval, LayoutManager se v daných intervalech (60 ms) synchronizuje s RecyclerView.Adapter a sdílí informace o položkách našeho seznamu. tj. položky přidané, aktualizované, odstraněné, přesunuty z jedné pozice na druhou). LayoutManager po obdržení těchto informací reorganizuje obsah na obrazovce tak, aby odpovídal změnám, když je to nutné.

Mnoho základních operací, které se zabývají RecylerView, rotuje kolem komunikace mezi RecyclerView.LayoutManager a RecyclerView.Adapter, která ukládá naše seznamy někdy statických nebo někdy dynamických dat.

Kromě toho nám RecyclerView představuje metody, které můžeme použít k naslouchání událostem, jako je onBindViewHolder, když náš RecyclerView.Adapter váže obsah z našeho seznamu, např. kontakt na ViewHolder, takže si nyní zvykne zobrazovat informace na obrazovce.

Dalším je onCreateViewHolder, který nám říká, kdy RecyclerView. Adaptér vezme běžný pohled jako OneContactView a převede jej na položku ViewHolder, se kterou může RecyclerView pracovat. Z našeho ViewHolder pro použití RecyclerView. onViewDetached

Kromě základních mechanismů, které umožňují recyklaci. RecyclerView poskytuje způsoby, jak přizpůsobit chování bez ovlivnění recyklace.

Opětovné použití pohledů ztěžuje provádění běžných věcí, na které jsme zvyklí se statickými pohledy, jako je reakce na události onClick.

Jak jsme si vědomi, že 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, který máme uložený v databázi nebo streamovaný ze zdroje. Umístění událostí OnClick přímo do zobrazení může vést k neočekávanému chování, jako je smazání nesprávného kontaktu nebo změna.

Gradle

Pokud chceme použít RecyclerView, musíme jej přidat jako závislost v našem souboru build .gradle.

V následujícím příkladu jsme použili implementaci „androidx.recyclerview:recyclerview:1.1.0“, což je nejaktuálnější verze podle tohoto článku.

Po přidání závislosti do našeho Gradle souboru, budeme vyzváni Android Studio do Synchronizovat změny,

Takto je naše Gradle soubor bude vypadat jako po 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"
}

V tuto chvíli máme pouze jeden soubor rozvržení. 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 nastavování.

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.

Za tímto účelem přejdeme do adresáře rozložení, který obsahuje naše rozložení, a vytvoříme pohled, 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>

Do TextView výše jsme přidali pole id, které bude použito k identifikaci pohledu.

Ve výchozím nastavení se negeneruje. Máme naše TextView id fruitName, aby odpovídalo 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ý byl pro nás standardně vygenerován.

Pokud bychom zvolili prázdný projekt. Vygeneruje to XML obsahující ConstraintLayout a uvnitř bude TextView s textem „Ahoj“.

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 přidali atribut id pro RecyclerView, který použijeme k odkazování v našem kódu.

 android:id="@+id/fruitRecyclerView"

Poté se vrátíme zpět do našeho souboru MainActivity. Pomocí id, které jsme vytvořili, budeme moci odkazovat na pohledy, které jsme právě vytvořili.

Začneme odkazem na RecyclerView pomocí metody findViewById(), kterou poskytuje Android. Provedeme to 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ý je zodpovědný za převzetí našeho pohledu a jeho převod na ViewHolder, který RecyclerView používá k zobrazení našich položek.

Uděláme to hned po naší zábavné 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.

Námi vytvořený FruitArrayAdapter bude zodpovědný za následující.

Převezme názvy ovoce z pole ovoce. Vytvoří ViewHolder pomocí našeho pohledu one_fruit_view.xml Poté naváže ovoce na ViewHolder a dynamicky sváže obsah s View, který jsme vytvořili one_fruit_view.xml.

package com.guru99.learnrecycler

import androidx.appcompat.app.AppCompatActivity
import android.os.Bundle
import android.view.View
import androidx.recyclerview.widget.RecyclerView

class MainActivity : AppCompatActivity() {

    var fruitNames:Array<String> = arrayOf<String>("Banana", "Mango", "Passion fruit", "Orange", "Grape")

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)

        var myFruitRecyclerView: RecyclerView = findViewById(R.id.fruitRecyclerView)
    }
    
    class FruitViewHolder(fruitView: View): RecyclerView.ViewHolder(fruitView)
    
    class FruitArrayAdapter(var fruitArray: Array<String>) : RecyclerView.Adapter<FruitViewHolder>()
}

Android Studio přidá na náš FruitArrayAdapter červené vlnovky, které nám sdělují, že musíme implementovat metodu, kterou může RecyclerView použít k připojení našeho pole k ViewHolder, který může RecyclerView používat.

   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 nejjednodušším bitem z vygenerovaného kódu je metoda getItemCount(). Víme, jak získat počet položek v našem poli voláním metody array.length.

class FruitListAdapter(var fruitArray: Array<String>) : RecyclerView.Adapter<FruitViewHolder>() {
        override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): FruitViewHolder {
            
        }

        override fun getItemCount(): Int {
         return fruitArray.size
        }

        override fun onBindViewHolder(holder: FruitViewHolder, position: Int) {
          
        }
    }

Poté implementujeme metodu override fun onCreateViewHolder.

Zde nás RecyclerView žádá, abychom mu pomohli vytvořit FruitHolder.

Pokud si vzpomínáme, takto vypadala naše třída FruitViewHolder:

class FruitViewHolder(fruitView: View): RecyclerView.ViewHolder(fruitView)

Vyžaduje to náš fruitView, který jsme vytvořili jako xml soubor one_fruit_view.xml

Můžeme být schopni vytvořit odkaz na tento xml a převést jej na View následovně.

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řepis

fun onBindViewHolder(holder: FruitViewHolder, position: Int)

RecyclerView.Adapter se zeptá na celé číslo pozice, které použijeme k načtení položky z našeho seznamu. Poskytuje nám také držák, abychom mohli přivázat položku, kterou získáme z fruitArray, k pohledu, který je držen uvnitř držáku pohledu.

Pohled, který je uložen uvnitř ViewHoder, je přístupný přes pole ViewHolder.itemView. Jakmile získáme pohled, 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á následovně.

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)
        }
    }

Nakonec jsme připraveni připojit zbývající části našeho RecyclerView. Které vytvářejí LayoutManager, který řekne RecyclerView, jak zobrazit obsah seznamu. Zda se má zobrazovat lineárně pomocí LinearLayoutManager nebo v mřížce pomocí GridLayoutManager nebo StaggeredGridLayoutManager.

Vytvořit Správce rozložení

Vrátíme se zpět 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ě máme všichni hotovo.

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)
        }
    }
}

Stáhněte si projekt