Android RecyclerView: Mitä on, opi yksinkertaisilla esimerkeillä

Missä RecyclerView on Android?

- RecyclerView on widget, joka on joustavampi ja edistyneempi versio GridView- ja ListView-ohjelmista. Se on kontti suurten tietojoukkojen näyttämiseen, joita voidaan vierittää tehokkaasti ylläpitämällä rajoitettu määrä näkymiä. Voit käyttää RecyclerView-widgetiä, kun sinulla on tietokokoelmia, joiden elementit muuttuvat ajon aikana verkkotapahtuman tai käyttäjän toiminnan mukaan.

Näyttökerrat

- Android alusta käyttää View- ja ViewGroup-luokkia kohteiden piirtämiseen näytölle. Nämä luokat ovat abstrakteja ja niitä on laajennettu erilaisiin toteutuksiin käyttötapauksen mukaan. Esimerkiksi TextView-sovelluksella on yksinkertainen tarkoitus näyttää tekstisisältöä näytöllä. EditText ulottuu samasta View-luokasta ja lisää toimintoja, jotta käyttäjä voi syöttää tietoja.

On mahdollista luoda omia mukautettuja näkymiä, jotta voimme saavuttaa enemmän joustavuutta käyttöliittymien kehittämisessä. View-luokka tarjoaa menetelmiä, joita voimme ohittaa piirtämään näytölle, ja tavan siirtää parametreja, kuten leveys, korkeus ja omat mukautetut attribuuttimme, jotka haluamme lisätä näkymäämme saadakseen sen toimimaan haluamallamme tavalla.

NäytäRyhmät

ViewGroup-luokka on eräänlainen View, mutta toisin kuin yksinkertainen View-luokka, jonka tehtävänä on pelkkä näyttäminen, ViewGroup antaa meille mahdollisuuden laittaa useita näkymiä yhteen näkymään, johon voimme viitata kokonaisuutena. Tässä tapauksessa ylätasolla luotua näkymää, johon lisäämme muita yksinkertaisia ​​näkymiä (voimme myös lisätä näkymäryhmiä), kutsutaan "vanhemmiksi", ja sisälle lisätyt näkymät ovat "lapsia".

Voimme kuvata näkymän taulukkona ja näkymäryhmän taulukkona. Ottaen huomioon, että Array of Array on itse matriisi, voimme nähdä, kuinka ViewGroupia voidaan käsitellä näkymänä.

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

ViewGroupin avulla voimme myös määritellä, miten lapset järjestetään näkymän sisällä, esimerkiksi asetetaan pysty- tai vaakasuoraan. Meillä voi olla erilaiset säännöt vuorovaikutukselle näkymän sisällä. Esimerkiksi toisiaan seuraavien TextView-kuvien etäisyyden tulisi olla 12 dp, kun taas TextView:n seuraavien ImageView-näkymien etäisyyden tulisi olla 5 dp.

Näin olisi, jos kehittäisimme omaa ViewGroupia tyhjästä. Helpottaaksesi näitä määrityksiä, Android tarjoaa luokan nimeltä LayoutParams, jota voimme käyttää näiden kokoonpanojen syöttämiseen.

Android dokumentointi tarjoaa joitain oletusparametreja, joita ottaisimme käyttöön määrittäessämme omaa ViewGroupia. Joitakin yleisiä parametreja ovat leveys, korkeus ja marginaali. Oletuksena näillä määrityksillä on rakenne android:layout_height korkeutta varten, esimerkiksi android:layout_width. Kun luot ViewGroupin, voit lisäksi luoda LayoutParams-parametreja sen mukaan, miten haluat ViewGroupin käyttäytyvän.

Android mukana tulee oletusnäkymät ja ViewGroups, joita voimme käyttää useiden yleisten tehtävien suorittamiseen. Yksi mainitsemamme esimerkki on TextView. Tämä on yksinkertainen näkymä, jossa on konfiguroitavia ominaisuuksia, kuten korkeus, leveys, tekstikoko ja vastaavat. Meillä on ImageView kuvien ja EditTextin näyttämiseen, kuten olimme maininneet muun muassa. Android on myös mukautettuja ViewGroups-ryhmiä, joihin voimme lisätä näkymämme ja saada odotetun toiminnan.

Lineaarinen asettelu

LinearLayoutin avulla voimme lisätä siihen Näytä-kohteita. LinearLayout on suuntamäärite, joka määrää, kuinka se asetetaan näytölle. Siinä on myös LinearLayout.LayoutParams, jotka sanelevat sisäisten näkymien säännöt. Esimerkiksi attribuutti android:center_horizontal keskittäisi näkymät vaaka-akselille, kun taas `android:center_vertical keskittäisi näkymän sisällön pystyakselille.

Tässä on joitain kuvia keskittämisen ymmärtämiseksi. Ottaisimme tämän yksinkertaiseksi TextView-näkymäksi 200 x 200 pikselin tilassa, attribuuttien keskittäminen saisi sen toimimaan seuraavasti.

android:center_horizontal

Vaakasuoraan keskitetty sisältö
Vaakasuoraan keskitetty sisältö

android:center_vertical

pystysuoraan keskitetty sisältö
pystysuoraan keskitetty sisältö

android:center

Keskitetty sisältö
Keskitetty sisältö

RecyclerView:n ydinkomponentit

RecyclerView:n ydinkomponentit
RecyclerView:n ydinkomponentit

Seuraavat ovat RecyclerView:n tärkeät osat:

RecyclerView.Adapter

Pohjatyö nro 1 – sovitinkuvio

Sovitin on laite, joka muuttaa järjestelmän tai laitteen attribuutit muuten yhteensopimattoman laitteen tai järjestelmän määritteiksi. Jotkut niistä muokkaavat signaalin tai tehon ominaisuuksia, kun taas toiset yksinkertaisesti mukauttavat liittimen fyysistä muotoa toiseen.

Yksinkertainen esimerkki, jonka löydämme tosielämästä selittämään sovittimen, on se, kun meidän on kytkettävä laitteet yhteen, mutta niissä on liitäntäportit, jotka eivät vastaa toisiaan. Näin voi käydä, kun vierailet toisessa maassa, jossa he käyttävät erilaisia ​​pistorasioita. Jos kannat puhelimen tai kannettavan tietokoneen laturia, sen kytkeminen pistorasiaan on mahdotonta. Et kuitenkaan antaisi periksi, vaan hankit vain sovittimen, joka tulee virtalähteen ja laturin väliin ja mahdollistaa latauksen.

Näin on ohjelmoinnissa, kun halutaan yhdistää kaksi tietorakennetta tehtävän suorittamiseksi, mutta niiden oletusportit eivät pysty kommunikoimaan keskenään.

Käytämme yksinkertaista esimerkkiä laitteesta ja laturista. Meillä on kaksi laturia. Amerikkalainen ja brittiläinen

class AmericanCharger() {
    var chargingPower = 10
}
class BritishCharger(){
    var charginPower = 5
}

Luomme sitten kaksi laitetta

class AmericanDevice() 
class BritishDevice()

Esimerkkinä voimme sitten luoda laitteista joitain esiintymiä pelaamista varten.

var myAmericanPhone = new AmericanDevice()
var myBritishPhone = new BritishDevice()

Esittelemme sitten molempien laitteiden latauksen käsitteen lisäämällä laitteisiin menetelmän nimeltä charge() .

Menetelmä ottaa sisääntulona sen vastaavan laturin ja suorittaa latauksen sen perusteella.

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

Tässä tapauksessa analogiamme perusteella joutuisimme syystä tai toisesta käyttämään BritishChargeria käytettäessä AmericanDevicea tai päinvastoin.

Ohjelmointimaailmassa tämä on yleensä silloin, kun sekoitetaan yhteen kirjastoja, jotka tarjoavat saman toiminnallisuuden (meidän kontekstissamme jaettu toiminnallisuus on veloitus). Meidän on löydettävä keino tämän mahdollistamiseksi.

Jos noudatamme analogiaa, meidän on mentävä elektroniikkaliikkeeseen ja ostettava sovitin, jonka avulla voimme ladata AmericanDevicesia BritishChargers-latureilla. Ohjelmoinnin näkökulmasta me olemme ne, jotka ovat sovittimen valmistaja.

Teemme yhdelle sovittimen, joka vastaa täsmälleen sitä mallia, jota tarvitsemme toisen luomiseen. Toteutamme sen luokkana seuraavasti. Sen ei välttämättä tarvitse olla luokka, ja se voi olla funktio, joka korostaa sovittimen kuvion yleensä. Käytämme luokkaa, joka sopii useimpiin käyttötarkoituksiin 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
    }
}

Ohjelmointimaailmassa pistorasian ero on samanlainen kuin sisällä olevien lataustapojen ero. Eri menetelmiä käyttävät laturit tekisivät mahdottomaksi laturien käytön.

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

Yritetään kutsua charge()-menetelmää myBritishDevicessa americanChargerIFoundilla ei toimisi, koska AmericanDevice hyväksyy vain AmericanChargerin

Joten tämä on mahdotonta tehdä

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

Tässä skenaariossa luomamme sovitin

AmericanToBritishChargerAdapter voi nyt olla hyödyllinen. Voimme käyttää returnNewCharger()-menetelmää uuden BritishChargerin luomiseen, jota voimme käyttää lataamiseen. Meidän tarvitsee vain luoda sovittimemme esiintymä ja syöttää se AmericanChargerilla, joka meillä on, niin se luo BritishChargerin, jota voimme käyttää.

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

Käsitellessämme ViewGroupia meillä olisi näkymät sen sisään. LayoutManagerin tehtävänä on kuvata, kuinka näkymät on sijoitettu sisällä.

Vertailun vuoksi Linearlayout ViewGroupin kanssa työskennellessämme haluamamme käyttötapaus on mahdollisuus sijoittaa kohteet joko pysty- tai vaakasuoraan. Tämä on helppo toteuttaa lisäämällä orientaatioattribuutti, joka kertoo kuinka lineaarinen asettelu sijoitetaan näytölle. Voimme tehdä tämän käyttämällä android:orientation=VERTICAL|HORIZONTAL määrite.

Meillä on myös toinen ViewGroup nimeltään GridLayout, sen käyttötapaus on, kun haluamme sijoittaa näkymät suorakaiteen muotoiseen Grid-rakenteeseen. Tämä voi johtua esimerkiksi siitä, että sovelluksen käyttäjälle esittämämme tiedot ovat helppokäyttöisiä. Suunnittelultaan GridLayout mahdollistaa konfiguraatiot, jotka auttavat sinua saavuttamaan tämän tavoitteen, koska meillä on konfiguraatioita, joissa voimme määritellä ruudukon mitat, esimerkiksi meillä voi olla 4 × 4 ruudukko, 3 × 2 ruudukko.

RecyclerView.ViewHolder

ViewHolder on abstrakti luokka, jonka laajennamme myös RecyclerView'sta. ViewHolder tarjoaa meille yleisiä menetelmiä, joiden avulla voimme viitata näkymään, jonka olemme sijoittaneet RecyclerView'hun, vaikka RecyclerView'n kierrätyskoneisto on muuttanut useita viitteitä, joista emme tiedä.

Suuret listat

RecyclerViewia käytetään, kun haluamme esittää käyttäjälle todella suuren joukon näkymiä, mutta emme kuitenkaan kuluta RAM laitteellamme jokaiselle luodun näkymän esiintymälle.

Jos ottaisimme yhteystietoluettelon, meillä olisi yleinen käsitys siitä, miltä yksi yhteyshenkilö näyttäisi luettelossa. Sitten tekisimme malliasettelun – joka on itse asiassa näkymä – jossa on paikkoja, joihin yhteystietoluettelomme tiedot täyttyvät. Seuraava on pseudokoodi, joka selittää koko tarkoituksen:

//OneContactView
<OneContact>
<TextView>{{PlaceHolderForName}}</TextView>
<TextView>{{PlaceHolderForAddress}}</TextView>
<ImageView>{{PlaceHolderForProfilePicture}}</ImageView>
<TextView>{{PlaceHolderForPhoneNumber}}</TextView>
</OneContact>

Sitten meillä olisi tällainen yhteystietolista

 <ContactList>
</ContactList>

Jos näin on, koodasimme sisällön, meillä ei olisi ohjelmallista tapaa lisätä uutta sisältöä luetteloon kirjoittamatta sovellusta uudelleen. Meidän onneksi. Näkymän lisäämistä ViewGroupiin tukee addView(view:View) menetelmällä.

Vaikka näin olisikin, RecyclerView ei lisää lasten näkymiä siihen.

Meidän käyttötapauksessa meillä olisi pitkä yhteyshenkilöluettelo. Jokaiselle luettelossa olevalle yhteyshenkilölle meidän on luotava OneContactView ja täytettävä tiedot näkymän sisällä vastaamaan Yhteystietoluokkamme kenttiä. Sitten kun meillä on näkymä, meidän on lisättävä se RecyclerView'hun luettelon näyttämiseksi.

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)

Meillä on joukko yhteystietoja nimeltä OneContactView. Se sisältää paikkoja Yhteystiedot-luokan sisällön ottamiseksi ja näyttämiseksi. RecyclerView'ssa meidän on lisättävä siihen Views, jotta se voi auttaa meitä kierrätyskyvyssä.

RecyclerView ei todellakaan salli näkymän lisäämistä, mutta sen avulla voimme lisätä ViewHolderin. Joten tässä skenaariossa meillä on kaksi asiaa, jotka haluamme yhdistää, mutta jotka eivät täsmää. Tässä adapterimme tulee käyttöön. RecyclerView tarjoaa meille paljon samanlaisen sovittimen AmericanToBritishChargerAdapter() aiemmasta, minkä ansiosta pystyimme muuttamaan AmericanChargerimme, joka ei ollut käyttökelvollinen BritishDevicemme kanssa, käyttökelpoiseksi, joka muistuttaa virtalähdettä tosielämässä.

Tässä skenaariossa sovitin ottaisi yhteystietojemme ja näkymämme ja luo sieltä ViewHolders, jotka RecyclerView on valmis hyväksymään.

RecyclerView tarjoaa käyttöliittymän, jota voimme laajentaa luodaksemme sovittimemme RecyclerView.Adapter-luokan kautta. Tämän sovittimen sisällä on tapa luoda ViewHolder-luokka, jonka kanssa RecyclerView haluaa työskennellä. Meillä on siis sama tilanne kuin ennenkin, mutta yhden ylimääräisen asian kanssa, se on sovitin.

Meillä on joukko yhteystietoja, näkymä yhden yhteystiedon näyttämiseen OneContactView. RecyclerView on luettelo näkymistä, jotka tarjoavat kierrätyspalveluita, mutta jotka ovat valmiita ottamaan vastaan ​​vain ViewHolders

Mutta tässä skenaariossa meillä on nyt RecyclerView.Adapter-luokka, jonka sisällä on menetelmä ViewHolderien luomiseen.

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

RecyclerView.ViewHolder on abstrakti luokka, joka ottaa näkymämme argumenttina ja muuntaa sen ViewHolderiksi.

Se käyttää käärekuviota, jota käytetään luokkien kykyjen laajentamiseen.

Pohjatyö #2 – käärekuvio

Käytämme yksinkertaista esimerkkiä osoittaaksemme, kuinka voimme saada eläimet puhumaan.

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

Yllä olevassa esimerkissä meillä on kaksi eläintä. Jos sattumalta, halusimme lisätä menetelmän puhua, mutta kirjaston kirjoittaja ei ollut hauska, voisimme silti löytää tavan. Tarvitsemme kääreen Animal-luokkaamme. Tekisimme tämän ottamalla Animalin luokallemme rakentajaksi

 class SpeechPoweredAnimalByWrapper(var myAnimal:Animal){

    fun sound(){
        myAnimal.sound()
    }

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

Nyt voimme välittää eläininstanssin SpeechPoweredAnimalByWrapperille. Sound()-metodin kutsuminen siihen kutsuisi passin in animal sound() -menetelmää. Meillä on myös ylimääräinen speak()-menetelmä, joka lasketaan uudeksi toiminnalliseksi, jonka lisäämme sisään siirrettyihin eläimiin. Voimme käyttää sitä seuraavasti:

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"

Tämän mallin avulla voimme ottaa luokkia ja lisätä toimintoja. Tarvitsemme vain luokan ilmentymän ja kääreluokkamme määrittelemät uudet menetelmät.

Yllä olevassa tapauksessamme käytimme betoniluokkaa. Sama on mahdollista toteuttaa myös abstraktissa luokassa. Meidän pitäisi vain lisätä SpeechPoweredAnimalByWrapper-luokka abstraktiksi, ja se on valmis. Muutamme luokan nimen lyhyemmäksi, jotta se olisi helpompi lukea.

abstract class SpeechPowered(var myAnimal:Animal){

    fun sound(){
        myAnimal.sound()
    }

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

Se on sama kuin ennen, mutta se tarkoittaisi jotain muuta. Normaalissa luokassa meillä voi olla luokan esiintymä samalla tavalla kuin loimme kissa1 ja koira1. Abstrakteja luokkia ei kuitenkaan ole tarkoitettu instantoitaviksi, vaan niiden tarkoituksena on laajentaa muita luokkia. Joten miten käyttäisimme uutta SpeechPowered(var myAnimal:Animal) abstraktia luokkaa. Voimme käyttää sitä luomalla uusia luokkia, jotka laajentavat sitä ja puolestaan ​​saavat sen toimivuuden.

Esimerkissämme luomme luokan SpeechPoweredAnimal, joka laajentaa luokkaa

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

Tämä on sama kuvio, jota käytetään ViewHolderissa. Luokka RecyclerView.ViewHolder on abstrakti luokka, joka lisää View-näkymään toimintoja, aivan kuten olemme lisänneet puhumismenetelmän eläimille. Lisätty toiminnallisuus saa sen toimimaan RecyclerView'n käsittelyssä.

Näin luomme OneContactViewHolderin OneContactView-sovelluksesta

//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:ssa on sovitin, jonka avulla voimme yhdistää yhteystietomatriisi ContactsView:hen RecyclerView:n avulla.

Näkymän lisääminen

ViewGroup ei piirrä ViewGroupia automaattisesti uudelleen, vaan noudattaa tiettyä aikataulua. Laitteessasi voi olla niin, että se piirtää uudelleen 10 ms tai 100 ms välein, tai jos valitsemme absurdin luvun, esimerkiksi 1 minuutin, kun lisäämme näkymän ViewGroupiin, näet muutokset minuutin kuluttua, kun ViewGroup "päivittyy".

RecyclerView.Recycler

Pohjatyö nro 3. Välimuisti

Yksi parhaista esimerkeistä siitä, missä teemme säännöllisesti päivityksiä, on selain. Oletetaan esimerkiksi, että vierailemamme sivusto on staattinen eikä lähetä sisältöä dynaamisesti. Meidän on päivitettävä päivitystä nähdäksemme muutokset.

Tässä esimerkissä oletetaan, että kyseessä oleva sivusto on Twitter. Meillä olisi luettelossa staattisia twiittejä, ja ainoa tapa nähdä uusia twiittejä on napsauttaa päivityspainiketta sisällön hakemiseksi.

Koko näytön uudelleenmaalaaminen on tietysti kallista. Jos kuvittelemme, että näin olisi, meillä oli rajoitettu kaistanleveys puhelinpalveluntarjoajamme kanssa. Ja twiittilistallamme oli paljon kuvia ja videoita, olisi kallista ladata koko sivun sisältö uudelleen jokaisen päivityksen yhteydessä.

Tarvitsemme tavan tallentaa jo ladatut twiitit ja varmistaa, että seuraava pyyntömme pystyy sanomaan jo olemassa olevat twiitit. Siksi se ei lataa kaikkea uudelleen, vaan saa vain uudet twiitit, jotka sillä on, ja myös tarkistaa, ettei jokin paikallisesti tallennettu twiitti ole enää siellä, jotta se voi poistaa sen paikallisesti. Se, mitä kuvaamme, on nimeltään välimuisti.

Tietoa, jonka lähetämme verkkosivustolle sisällöstämme, kutsutaan metatiedoksi. Joten todellisessa mielessä emme vain sano "haluamme ladata sivustosi", sanomme "haluamme ladata sivustosi, ja tässä on joitain sisältöjä, jotka olimme jo tallentaneet edellisen latauskerran jälkeen. Käytä sitä Lähetä meille vain se, mitä siellä ei ole, joten emme käytä paljon kaistanleveyttä, koska meillä ei ole paljon resursseja."

Ulkoasukutsut – Tweet-luettelon täytyy olla hullu

Esimerkki asettelukutsuista on scrollToPosition

Tämä on yleinen esimerkki, jota esiintyy esimerkiksi chat-sovelluksissa. Jos joku keskusteluketjussa vastaa aikaisempaan chat-kuplaan, jotkin chat-sovellukset sisältävät vastauksen ja linkin chat-kuplaan, jota napsauttamalla siirryt alkuperäisen viestisi sijaintiin.

Siinä tapauksessa kutsumme tätä menetelmää ennen kuin LayoutManager on lisätty RecyclerView-näkymään ja ennen kuin meillä on RecyclerView.Adapter, scrollToPosition(n:Int) jätetään huomiotta.

Viestintä RecyclerView-komponenttien välillä

Pohjatyö nro 4. Takaisinsoittoja

RecyclerView'ssa on työssään paljon liikkuvia osia. Sen on käsiteltävä LayoutManageria, joka kertoo meille, kuinka näkymät järjestetään joko lineaarisesti tai ruudukossa. Sen on toimittava sovittimen kanssa, joka muuntaa kohteemme contactList Views OneContactView -näkymäksi ja sitten ViewHolders OneContactViewHolder -sovellukseksi, jota RecyclerView on valmis työskentelemään meille tarjoamillaan menetelmillä.

RecyclerView:n raaka-aineena ovat Views-näkymämme, esim. OneContactView ja tietolähde.

contactList:Array<Contact>

Olemme käyttäneet yksinkertaista skenaariota lähtökohtana saadaksemme käsityksen siitä, mitä RecyclerView yrittää saavuttaa.

Perustapaus, jossa meillä olisi 1000 kontaktin staattinen joukko, jonka haluamme näyttää käyttäjälle, on helppo ymmärtää.

RecyclerView-koneisto alkaa todella elää, kun luettelo ei ole enää staattinen.

Dynaamisen luettelon avulla meidän on mietittävä, mitä tapahtuu näytöllä näkyvälle näkymälle, kun lisäämme kohteen luetteloon tai poistamme kohteen luettelosta.

RecyclerView.LayoutManager

Sen lisäksi, että päätetään, kuinka näkemyksemme asetetaan joko lineaarisesti tai ruudukossa. LayoutManager tekee paljon työtä konepellin alla auttaakseen kierrättäjää tietämään, milloin kierrätys tulee tehdä.

Se vastaa näytöllä tällä hetkellä näkyvien näkymien seuraamisesta ja näiden tietojen välittämisestä kierrätysmekanismille. Kun käyttäjä rullaa alaspäin, asettelupäällikkö on vastuussa siitä, että se ilmoittaa kierrätysjärjestelmälle näkymistä, jotka menevät epätarkkoihin ylhäällä, jotta niitä voidaan käyttää uudelleen sen sijaan, että ne jäävät sinne ja kuluttaisivat muistia tai tuhoaisivat ne ja joutuisivat luomaan. uudet.

Tämä tarkoittaa, että LayoutManagerin on seurattava, missä käyttäjä on, kun hän vierittää luetteloamme. Se tekee tämän luomalla luettelon sijoituksista, jotka ovat indeksipohjaisia, eli ensimmäinen kohta on aloitettava nollasta ja kasvatettava vastaamaan luettelossamme olevien kohteiden määrää.

Jos voimme tarkastella 10 kohdetta esimerkiksi 100:n luettelossamme, LayoutManager tietää alussa, että sillä on tarkentunut näkymä-0 aina näkymä-9:ään asti. Kun vieriämme, LayoutManager pystyy laskemaan ulos tulevat näkymät. keskittymistä.

LayoutManager voi vapauttaa nämä näkymät Kierrätysmekanismille, jotta niitä voidaan käyttää uudelleen (niihin voidaan sitoa uusia tietoja, esim. näkymän yhteystiedot voidaan poistaa ja uudet yhteystiedot seuraavasta segmentistä voivat korvata paikkamerkit).

Tämä olisi onnellinen tapaus, jos meillä oleva luettelo on staattinen, mutta yksi yleisimmistä RecyclerView-käyttötapauksista on dynaamiset luettelot, joissa tiedot voivat tulla online-päätepisteestä tai jopa kenties anturista. Tietoja ei vain lisätä, vaan myös luettelomme tietoja joskus poistetaan tai päivitetään.

Tietojemme dynaaminen tila voi vaikeuttaa LayoutManagerin perustelemista. Tästä syystä LayoutManager ylläpitää omaa luetteloa kohteista ja paikoista, joka on erillinen Kierrätyskomponentin käyttämästä luettelosta. Tämä varmistaa, että se tekee asettelutyönsä oikein.

Samaan aikaan RecyclerView'n LayoutManager ei halua antaa vääriä tietoja sillä on. Toimiakseen oikein LayoutManager synkronoituu RecyclerView.Adapterin kanssa tietyin väliajoin (60 ms) jakaa tietoja luettelokohteistamme. eli kohteet lisätty, päivitetty, poistettu, siirretty paikasta toiseen). Saatuaan nämä tiedot LayoutManager järjestää uudelleen näytön sisällön vastaamaan muutoksia tarvittaessa.

Monet RecylerView'ta käsittelevistä ydintoiminnoista kiertävät RecyclerView.LayoutManagerin ja RecyclerView.Adapterin välistä viestintää, joka tallentaa luettelomme joskus staattisista tai joskus dynaamisista tiedoista.

Lisäksi RecyclerView esittelee meille menetelmiä, joilla voimme kuunnella tapahtumia, kuten onBindViewHolder, kun RecyclerView.Adaptermme sitoo sisältöä luettelostamme, esim. yhteyshenkilön ViewHolderiin, jotta se nyt tottuu näyttämään tiedot näytöllä.

Toinen on onCreateViewHolder, joka kertoo meille, milloin RecyclerView.Sovitin ottaa tavallisen näkymän, kuten OneContactView, ja muuntaa sen ViewHolder-kohteeksi, jonka kanssa RecyclerView voi toimia. ViewHolderistamme RecyclerView'n käyttöön. onViewDetached

Kierrätyksen mahdollistavien ydinmekanismien lisäksi. RecyclerView tarjoaa tapoja mukauttaa käyttäytymistä vaikuttamatta kierrätykseen.

Näkymien uudelleenkäyttö vaikeuttaa yleisten asioiden tekemistä, joita olemme tottuneet tekemään staattisilla näkymillä, kuten reagoimista onClick-tapahtumiin.

Kuten tiedämme, että RecyclerView.LayoutManager joka esittelee näkymät käyttäjälle, saattaa hetkeksi olla eri luettelo kohteista kuin RecyclerView.Adapter joka sisältää luettelon, jonka olemme tallentaneet tietokantaan tai suoratoistona lähteestä. OnClick-tapahtumien lisääminen suoraan Viewsiin voi johtaa odottamattomaan toimintaan, kuten väärän yhteystiedon poistamiseen tai vaihtamiseen.

Gradle

Jos haluamme käyttää RecyclerView'ta, meidän on lisättävä se riippuvuutena build .gradle -tiedostoomme.

Seuraavassa esimerkissä olemme käyttäneet toteutusta "androidx.recyclerview:recyclerview:1.1.0", joka on tämän artikkelin uusin versio.

Lisättyään riippuvuuden meidän Gradle tiedosto, saamme kehotteen Android Studioon Synchronisoida muutokset,

Näin on meidän Gradle tiedosto näyttää siltä, ​​kun RecyclerView on lisätty tyhjään projektiin vain oletusarvoilla.

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

Meillä on tällä hetkellä vain yksi asettelutiedosto. Aloitamme yksinkertaisella esimerkillä, jossa käytämme RecyclerView'ta näyttääksemme luettelon hedelmien nimistä näytöllä.

Luettelo tuotteista

Siirrymme MainActivity-tiedostoomme ja luomme taulukon, jossa on hedelmien nimet juuri ennen asennuksen aikana luotua onCreate()-menetelmää.

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

Seuraava tavoitteemme on esittää tämä luettelo näytöllä RecyclerView'n avulla.

Tätä varten siirrymme asetteluhakemistoon, joka sisältää asettelumme, ja luomme näkymän, joka vastaa yhden hedelmän näyttämisestä.

Asettelu, jota käytetään luettelossamme jokaiselle kohteelle

<?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>

Yllä olevaan TextView-näkymään olemme lisänneet id-kentän, jota käytetään näkymän tunnistamiseen.

Sitä ei luoda oletuksena. Meillä on TextView id fruitName vastaamaan tietoja, jotka sidotaan siihen.

RecyclerView'n lisääminen pääasetteluun

Samassa toiminnassa on main_layout.xml-asettelutiedosto, joka on luotu meille oletuksena.

Jos valitsimme tyhjän projektin. Se on luonut XML sisältää ConstraintLayout ja sen sisällä on TextView, jossa on "Hei"-teksti.

Poistamme kaiken sisällön ja asettelussa on vain RecyclerView, kuten alla:

<?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" />

Olemme myös lisänneet RecyclerView:lle id-attribuutin, jota käytämme viittaamaan siihen koodissamme.

 android:id="@+id/fruitRecyclerView"

Siirrymme sitten takaisin MainActivity-tiedostoomme. Käyttämällä luomiamme tunnuksia voimme viitata juuri luomiimme näkymiin.

Aloitamme viittaamalla RecyclerView'n tarjoamaan findViewById()-menetelmään Android. Teemme tämän onCreate()-menetelmällämme.

OnCreate()-menetelmämme näyttää seuraavalta.

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)
        
        var myFruitRecyclerView: RecyclerView = findViewById(R.id.fruitRecyclerView)
    }

Luo ViewHolder

Seuraavaksi luomme RecyclerView.ViewHolderin, joka vastaa näkymämme ottamisesta ja sen muuntamisesta ViewHolderiksi, jota RecyclerView käyttää tuotteidemme näyttämiseen.

Teemme tämän heti hauskan onCreate()-menetelmämme jälkeen.

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

Luo RecyclerViewAdapter

Seuraavaksi luomme FruitArrayAdapter-luokan, joka laajentaa RecyclerView.Adapter-luokkaa.

Luomamme FruitArrayAdapter on vastuussa seuraavista toimista.

Se ottaa hedelmien nimet hedelmäjoukosta. Se luo ViewHolderin käyttämällä näkymäämme one_fruit_view.xml Se sitoo sitten hedelmät ViewHolderiin ja sitoo sisällön dynaamisesti luomaan one_fruit_view.xml-näkymään.

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 lisää punaisia ​​värejä FruitArrayAdapteriimme, mikä kertoo meille, että meidän on otettava käyttöön menetelmä, jolla RecyclerView voi yhdistää taulukomme ViewHolderiin, jota RecyclerView voi käyttää.

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

Aloitamme luodun koodin helpoimmasta bitistä getItemCount()-menetelmä. Tiedämme kuinka saada taulukossa olevien kohteiden määrä kutsumalla array.length-menetelmää.

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

Sitten otamme käyttöön menetelmän ohitus hauskan CreateViewHolderissa.

Tässä RecyclerView pyytää meitä auttamaan sitä rakentamaan sille FruitHolderin.

Jos muistamme, tältä FruitViewHolder-luokkamme näytti:

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

Se vaatii hedelmänäkymämme, jonka loimme xml-tiedostona one_fruit_view.xml

Voimme luoda viittauksen tähän xml-tiedostoon ja muuntaa sen näkymäksi seuraavasti.

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

        }
    }

Jäljellä oleva bitti on ohitus

fun onBindViewHolder(holder: FruitViewHolder, position: Int)

RecyclerView.Adapter kysyy sijaintikokonaisluvulla, jota käytämme kohteen hakemiseen luettelostamme. Se tarjoaa meille myös pidikkeen, jotta voimme sitoa hedelmätaulukosta saamamme kohteen näkymäpitimen sisällä olevaan näkymään.

ViewHoderin sisällä olevaan näkymään pääsee ViewHolder.itemView-kentän kautta. Kun saamme näkymän, voimme käyttää aiemmin luomaamme id fruitName sisällön asettamiseen.

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

            var ourFruitTextView = holder.itemView.findViewById<TextView>(R.id.fruitName)

            var aFruitName = fruitArray.get(position)

            ourFruitTextView.setText(aFruitName)
        }

FruitArrayAdapterimme on valmis ja näyttää seuraavalta.

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

Lopuksi olemme valmiita yhdistämään jäljellä olevat RecyclerView-osat. Jotka luovat LayoutManagerin, joka kertoo RecyclerViewille, kuinka luettelon sisältö näytetään. Näytetäänkö lineaarisesti LinearLayoutManagerin avulla tai ruudukossa GridLayoutManagerin tai StaggeredGridLayoutManagerin avulla.

Luo Layout Manager

Palaamme onCreate-toimintoomme ja lisäämme LayoutManagerin

 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
        
    }

Kiinnitä sovitin esineisiin ja aseta se RecyclerView'lle

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
    }

Olemme myös luoneet fruitListAdapter-esiintymän ja syöttäneet sille joukon hedelmien nimiä.

Ja periaatteessa olemme kaikki valmiita.

Täydellinen MainActivity.kt-tiedosto näyttää seuraavalta.

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

Lataa projekti