Android RecyclerView: Mitä on, opi yksinkertaisilla esimerkeillä
⚡ Älykäs yhteenveto
RecyclerView on edistynyt Android widget, joka näyttää suuria, vieritettäviä tietojoukkoja tehokkaasti kierrättämällä rajoitetun määrän näkymiä. Se käyttää sovitinta, asettelun hallintaohjelmaa ja näkymänpitäjää sitoakseen ajonaikana muuttuvat tietokokoelmat.

Missä RecyclerView on Android?
RecyclerView on GridView'n ja ListView'n joustavampi ja edistyneempi versio. Se on säilö 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 suorituksen 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 abstract ja niitä laajennetaan eri toteutuksiin käyttötapauksen mukaan. Esimerkiksi TextView'n yksinkertainen tarkoitus on näyttää tekstisisältöä näytöllä. EditText ulottuu samasta View-luokasta ja lisää toimintoja, joiden avulla käyttäjä voi syöttää tietoja.
On mahdollista luoda omia mukautettuja näkymiä, mikä lisää joustavuutta kehitysvaiheessa.ping käyttöliittymiä. View-luokka tarjoaa metodeja, joita voimme korvata piirtääksemme näytölle, ja keinon välittää parametreja, kuten leveys, korkeus ja omia mukautettuja attribuutteja, joita haluamme lisätä View-luokkaamme, jotta se toimisi haluamallamme tavalla.
NäytäRyhmät
ViewGroup-luokka on eräänlainen View-luokka, mutta toisin kuin yksinkertainen View-luokka, jonka tehtävänä on vain näyttää, ViewGroup antaa meille mahdollisuuden yhdistää useita näkymiä yhteen näkymään, johon voimme viitata kokonaisuutena. Tässä tapauksessa ylimmällä tasolla luotua View'ta, johon lisäämme muita yksinkertaisia näkymiä, kutsutaan "päänäkymäksi", ja sen sisälle lisättyjä näkymiä kutsutaan "alinäkymäksi".
Voimme kuvitella näkymän (View) taulukkona (Array) ja näkymäryhmän (ViewGroup) taulukoiden joukosta (Array) koostuvana taulukkona. Koska taulukoiden joukosta koostuva taulukko on itsekin taulukko, voimme nähdä, kuinka näkymäryhmää (ViewGroup) voidaan käsitellä näkymän (View) tavoin.
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-ryhmän avulla voimme myös määrittää, miten alinäkymän elementit on järjestetty näkymän sisällä, esimerkiksi ovatko ne pystysuunnassa vai vaakasuunnassa. Voimme käyttää erilaisia sääntöjä näkymän sisäiselle vuorovaikutukselle. Esimerkiksi toisiaan seuraavien TextView-elementtien etäisyyden tulisi olla 12 dp, kun taas TextView-elementin ja sitä seuraavan ImageView-elementin etäisyyden tulisi olla 5 dp.
Näin olisi, jos olisimme kehittyneetping oma ViewGroup alusta alkaen. Näiden määritysten helpottamiseksi Android tarjoaa luokan nimeltä LayoutParams, jota voimme käyttää näiden kokoonpanojen syöttämiseen.
Android dokumentointi tarjoaa joitakin oletusparametreja, joita käyttäisimme oman ViewGroup-arvomme määrittämisessä. Joitakin yleisiä parametreja ovat leveys, korkeus ja reunus. Oletusarvoisesti näillä kokoonpanoilla on rakenne android:layout_height korkeudelle ja android:layout_width leveydelle. Tässä suhteessa, kun luot ViewGroup-arvosi, voit luoda LayoutParams-arvoja, jotka ovat erityisiä ViewGroup-arvojesi toiminnan mukaan.
Android sisältää oletusarvoiset näkymät ja näkymäryhmät, joita voimme käyttää monien yleisten tehtävien suorittamiseen. Yksi mainitsemamme esimerkki on TextView. Se on yksinkertainen näkymä, jossa on konfiguroitavia ominaisuuksia, kuten korkeus, leveys ja tekstin koko. Meillä on myös ImageView kuvien näyttämiseen ja, kuten mainitsimme, EditText, monien muiden ohella. Android on myös mukautettuja ViewGroups-ryhmiä, joihin voimme lisätä näkymämme ja saada odotetun toiminnan.
Lineaarinen asettelu
LinearLayout-asetukselle voidaan lisätä View-kohteita. LinearLayout-asetuksella on orientation-attribuutti, joka määrää, miten se asetetaan näytöllä. Siinä on myös LinearLayout.LayoutParams-attribuutti, joka sanelee säännöt sen sisällä oleville näkymille. 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 kuvia keskittämisen ymmärtämiseksi. Oletamme tämän olevan yksinkertainen TextView 200 x 200 pikselin kokoisessa tilassa; keskitysattribuutit saisivat sen toimimaan seuraavasti.
android:center_horizontal
Vaakasuoraan keskitetty sisältö
android:center_vertical
Pystysuunnassa keskitetty sisältö
android:center
Keskitetty sisältö
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 yhden järjestelmän tai laitteen ominaisuudet muuten yhteensopimattoman laitteen tai järjestelmän ominaisuuksiksi. Jotkut sovittimet muokkaavat signaali- tai teho-ominaisuuksia, kun taas toiset yksinkertaisesti mukauttavat yhden liittimen fyysisen muodon toiseen.
Yksinkertainen esimerkki sovittimesta on tilanne, jossa laitteita on yhdistettävä toisiinsa, mutta niiden liitäntäportit eivät sovi yhteen. Näin voi olla esimerkiksi silloin, kun vierailet toisessa maassa, jossa käytetään erilaisia pistorasioita. Jos kannat mukanasi puhelimen tai kannettavan tietokoneen laturia, sitä on mahdotonta kytkeä pistorasiaan. Et kuitenkaan luovuta, vaan hankit sovittimen, joka asetetaan pistorasian ja laturin väliin ja mahdollistaa latauksen.
Näin on ohjelmoinnissa, kun haluamme yhdistää kaksi tietorakennetta toisiinsa 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 chargingPower = 5 }
Tämän jälkeen luomme kaksi laitetta.
class AmericanDevice() class BritishDevice()
Esimerkkinä voimme sitten luoda laitteista joitain esiintymiä pelaamista varten.
var myAmericanPhone = new AmericanDevice() var myBritishPhone = new BritishDevice()
Seuraavaksi esittelemme molempien laitteiden latauksen käsitteen lisäämällä laitteisiin metodin nimeltä charge(). Metodi ottaa vastaan oman laturin tiedot 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 tarvitsisimme jostain syystä brittiläisen laturin käyttäessämme amerikkalaista laturia, 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 seuraamme analogiaa, meidän on mentävä elektroniikkaliikkeeseen ja ostettava sovitin, jonka avulla voimme ladata amerikkalaisia laitteita, kunhan meillä on brittiläisiä latureita. Ohjelmoinnin näkökulmasta olemme sovittimen valmistaja.
Teemme sovittimen, joka vastaa täsmälleen samaa mallia, jota tarvitsisimme toisen luomiseen. Toteutamme sen luokkana seuraavasti. Sen ei välttämättä tarvitse olla luokka, ja se voi olla funktio, joka korostaa sovittimen yleistä toimintaa. Käytämme luokkaa, koska se vastaa useimpia käyttötarkoituksia 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 } }
Ohjelmoinnin maailmassa pistorasioiden ero on analoginen latausmenetelmien erojen kanssa. Latureiden erilaiset latausmenetelmät tekisivät niiden käytön mahdottomaksi.
var myBritishDevice = new BritishDevice() var americanChargerIFound = new AmericanCharger()
charge()-metodin kutsuminen myBritishDevice-laitteessa americanChargerIFound-metodilla ei toimi, koska AmericanDevice hyväksyy vain AmericanCharger-latauslaitteen. Joten tätä on mahdotonta tehdä:
var myBritishDevice = new BritishDevice() var americanChargerIFound = new AmericanCharger() myBritishDevice.charge(americanChargerIFound)
Tässä skenaariossa luomastamme sovittimesta, AmericanToBritishChargerAdapterista, voi nyt olla hyötyä. Voimme käyttää returnNewCharger()-metodia luodaksemme uuden BritishChargerin, jota voimme käyttää lataamiseen. Meidän tarvitsee vain luoda sovittimestamme instanssi ja syöttää siihen AmericanCharger-lataussignaali, jolloin 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(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
Kun käsitellään ViewGroupia (näkymäryhmää), sen sisällä on näkymiä (views). LayoutManagerin (asettelunhallinnan) tehtävänä on kuvata, miten näkymät (views) on aseteltu ryhmän sisällä.
Vertailun vuoksi, kun työskentelemme LinearLayout ViewGroupin kanssa, haluamme mahdollisuuden sijoittaa kohteet joko pystysuunnassa tai vaakasuunnassa. Tämä on helppo toteuttaa lisäämällä orientation-attribuutti, joka kertoo meille, miten LinearLayout 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ä GridLayout. Sen käyttötapaus on, kun haluamme sijoittaa näkymiä suorakaiteen muotoiseen ruudukkorakenteeseen. Tämä voi olla tarpeen esimerkiksi siksi, että sovelluksen käyttäjälle esittämämme datan käyttö olisi helppoa. GridLayout on suunniteltu mahdollistamaan konfiguraatioita, jotka auttavat saavuttamaan tämän tavoitteen määrittämällä ruudukon mitat; meillä voi esimerkiksi olla 4×4-ruudukko tai 3×2-ruudukko.
RecyclerView.ViewHolder
ViewHolder on vatsalihastract-luokka, jota laajennamme myös RecyclerView'sta. ViewHolder tarjoaa meille yleisiä metodeja, joiden avulla voimme viitata RecyclerView'hun sijoittamaamme näkymään, vaikka RecyclerView'n kierrätysmekanismi olisi muuttanut useita viittauksia, joista emme tiedä.
Suuret listat
RecyclerViews-kohteita käytetään, kun haluamme esitellä käyttäjälle todella suuren joukon Views-kohteita, mutta samalla ei tyhjentää RAM laitteellamme jokaiselle luodun näkymän esiintymälle.
Jos otamme esimerkiksi yhteystietoluettelon, meillä olisi yleinen käsitys siitä, miltä yksi yhteystieto luettelossa näyttäisi. Tämän jälkeen loisimme mallipohjan, joka on itse asiassa näkymä, jossa on paikkoja, joihin yhteystietoluettelomme tiedot täytetään. Seuraava on pseudokoodi, joka selittää koko tarkoituksen:
//OneContactView
<OneContact>
<TextView>{{PlaceHolderForName}}</TextView>
<TextView>{{PlaceHolderForAddress}}</TextView>
<ImageView>{{PlaceHolderForProfilePicture}}</ImageView>
<TextView>{{PlaceHolderForPhoneNumber}}</TextView>
</OneContact>
Meillä olisi sitten tämänkaltainen yhteystietoluettelo:
<ContactList> </ContactList>
Jos koodaisimme sisällön kovakoodauksella, meillä ei olisi ohjelmallista tapaa lisätä uutta sisältöä listaan ilman sovelluksen uudelleenkirjoittamista. Onneksi meille View-ryhmän lisäämistä tukee addView(view:View) menetelmä. Silti se ei ole tapa, jolla RecyclerView lisää lapsinäkymiä siihen.
Käyttötapauksessamme meillä olisi pitkä luettelo yhteystiedoista. Jokaista luettelossa olevaa yhteystietoa varten meidän pitäisi luoda OneContactView ja täyttää View-näkymän tiedot vastaamaan Contact-luokkamme kenttiä. Kun näkymä on valmis, meidän pitäisi lisätä se RecyclerView-näkymään 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 yhteystietotaulukko. OneContactView sisältää paikkoja, joihin voi ottaa Contact-luokan sisällön ja näyttää sen. RecyclerView'ssa meidän on lisättävä siihen Views-objekteja, jotta se voi auttaa meitä kierrätyskyvyssään.
RecyclerView ei oikeastaan salli näkymän lisäämistä, mutta se mahdollistaa ViewHolderin lisäämisen. Tässä skenaariossa meillä on kaksi osaa, jotka haluamme yhdistää, mutta jotka eivät sovi yhteen. Tässä kohtaa sovitin tulee mukaan kuvaan. RecyclerView tarjoaa meille sovittimen, aivan kuten AmericanToBritishChargerAdapter() aiemmasta, mikä mahdollisti sen, että pystyimme muuttamaan AmericanCharger-laturimme, jota ei voinut käyttää BritishDevice-laitteemme kanssa, käyttökelpoiseksi, käytännössä virtalähteen kaltaiseksi.
Tässä skenaariossa sovitin ottaisi yhteystietotaulukomme ja View-näkymämme ja luoisi niistä ViewHolder-objektit, jotka RecyclerView on valmis hyväksymään.
RecyclerView tarjoaa rajapinnan, jota voimme laajentaa luomaan oman sovittimen RecyclerView.Adapter-luokan avulla. Tämän sovittimen sisällä on tapa luoda ViewHolder-luokka, jonka kanssa RecyclerView haluaa työskennellä. Tilanne on siis sama kuin aiemmin, mutta yhdellä lisäjutulla, sovittimella.
Meillä on yhteystietotaulukko, näkymä yhden yhteystiedon näyttämiseksi (OneContactView) ja RecyclerView, joka on luettelo näkymistä, jotka tarjoavat kierrätyspalveluita, mutta on halukas ottamaan vastaan vain ViewHolder-objekteja. Tässä skenaariossa meillä on nyt RecyclerView.Adapter-luokka, jonka sisällä on metodi ViewHolder-objektien luomiseksi.
fun createViewHolder(@NonNull parent: ViewGroup, viewType: Int): ViewHolder
RecyclerView.ViewHolder on vatsalihas.tract-luokka, joka ottaa View-arvomme argumenttina ja muuntaa sen ViewHolder-objektiksi. Se käyttää käärekuviota, jota käytetään luokkien ominaisuuksien 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 haluaisimme lisätä metodin, joka saa ne puhumaan, mutta kirjaston tekijä ei olisi hauska, voisimme silti löytää keinon. Tarvitsisimme kääreen Animal-luokallemme. Tekisimme tämän ottamalla Animalin luokan rakentajaksi.
class SpeechPoweredAnimalByWrapper(var myAnimal:Animal){ fun sound(){ myAnimal.sound() } fun speak(){ println("Hello, my name is ${myAnimal.name}") } }
Nyt voimme välittää eläininstanssin SpeechPoweredAnimalByWrapperille. Sen sound()-metodin kutsuminen kutsuisi jo annettua animal sound()-metodia. Meillä on myös ylimääräinen speak()-metodi, joka lasketaan uudeksi toiminnallisuudeksi, jonka lisäämme annettuihin 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ä niihin toiminnallisuutta. Tarvitsemme vain luokkainstanssin ja uudet wrappimme määrittelemät metodit.ping luokka.
Yllä olevassa tapauksessa käytimme betoniluokkaa. Saman voi toteuttaa myös abs-funktiossatract-luokka. Meidän pitäisi muuttaa SpeechPoweredAnimalByWrapper-luokka muotoon abstract, ja olemme valmiita. Muutamme luokan nimen lyhyemmäksi, jotta se olisi luettavampi.
abstract class SpeechPowered(var myAnimal:Animal){ fun sound(){ myAnimal.sound() } fun speak(){ println("Hello, my name is ${myAnimal.name}") } }
Se on sama kuin ennen, mutta se merkitsisi jotain muuta. Normaalissa luokassa meillä voi olla luokan instanssi samalla tavalla kuin loimme cat1:n ja dog1:n. Abstract-luokkia ei kuitenkaan ole tarkoitettu instanssien luomiseen, vaan niiden tarkoituksena on laajentaa muita luokkia. Joten miten käyttäisimme uutta SpeechPowered(var myAnimal:Animal) abs -funktiota?tract-luokka? Voimme käyttää sitä luomalla uusia luokkia, jotka laajentavat sitä ja saavat samalla sen toiminnallisuuden.
Esimerkissämme luomme SpeechPoweredAnimal-luokan, 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 kaava, jota käytetään ViewHolderissa. RecyclerView.ViewHolder-luokka on abs-luokka.tract-luokka, joka lisää View'hun toimintoja, aivan kuten lisäsimme Animals-metodille speak-metodin. Lisätty toiminnallisuus tekee siitä toimivan RecyclerView'n kanssa.
Näin loisimme OneContactViewHolder-objektin OneContactView'sta:
//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ää yhteystietomatriisin ContactsView'hun RecyclerView'n avulla.
Näkymän lisääminen
ViewGroup ei piirrä uudelleen automaattisesti, vaan noudattaa tiettyä aikataulua. Laitteellasi se voi piirtää uudelleen 10 ms:n tai 100 ms:n välein; tai jos valitsemme järjettömän luvun, esimerkiksi minuutin, kun lisäämme näkymän ViewGroupiin, näet muutokset minuutin kuluttua, kun ViewGroup "päivittyy".
RecyclerView.Recycler
Pohjatyö #3 – Välimuisti
Yksi parhaista esimerkeistä säännöllisistä päivityksistä on selain. Kuvitellaan esimerkiksi, että vierailemamme sivusto on staattinen eikä lähetä sisältöä dynaamisesti; meidän pitäisi päivittää sivua jatkuvasti nähdäksemme muutokset.
Kuvitellaanpa tässä esimerkissä, että kyseinen sivusto on Twitter. Meillä olisi listattu joukko staattisia twiittejä, ja ainoa tapa nähdä uudet twiitit olisi napsauttaa päivityspainiketta sisällön hakemiseksi uudelleen.
Koko näytön uudelleenmaalaaminen on tietenkin kallista. Kuvittele, että meillä olisi rajoitettu kaistanleveys puhelinoperaattorimme kanssa ja twiittilistassamme olisi paljon kuvia ja videoita; sivun koko sisällön lataaminen uudelleen jokaisella päivityksellä olisi kallista.
Tarvitsisimme tavan tallentaa jo ladatut twiitit ja varmistaa, että seuraava pyyntömme pystyy kertomaan, mitkä twiitit sillä jo on. Näin ollen se ei lataa kaikkea uudelleen, vaan hakee vain uudet twiitit ja tarkistaa myös, onko jokin paikallisesti tallennettu twiitti poistunut, jotta se voi poistaa sen paikallisesti. Tätä kutsutaan välimuistiksi.
Tietoja, joita lähetämme verkkosivustolle sisällöstämme, kutsutaan metatiedoksi. Emme siis käytännössä sano vain "haluamme ladata sivustosi", vaan "haluamme ladata sivustosi, ja tässä on osa sisällöstä, jonka olemme jo tallentaneet viimeisimmästä latauskerrasta lähtien. Käytä sitä vain sellaisen sisällön lähettämiseen, jota siellä ei ole, jotta emme käytä paljon kaistanleveyttä".
Ulkoasukutsut – Tweet-luettelon täytyy olla hullu
Esimerkki asettelukutsuista on scrollToPosition. Tämä on yleinen esimerkki esimerkiksi keskustelusovelluksissa. Jos joku keskusteluketjussa vastaa aiempaan keskustelukuplaan, jotkut keskustelusovellukset lisäävät vastauksen ja linkin keskustelukuplaan, jota napsauttamalla pääset takaisin alkuperäiseen viestiisi.
Siinä tapauksessa, että kutsumme tätä metodia ennen kuin LayoutManager on lisätty RecyclerView-objektiin ja ennen kuin meillä on RecyclerView.Adapter-objekti, scrollToPosition(n:Int) yksinkertaisesti jätetään huomiotta.
Viestintä RecyclerView-komponenttien välillä
Pohjatyö #4 – Takaisinkutsut
RecyclerView'lla on työssään paljon liikkuvia osia. Sen on käsiteltävä LayoutManageria, joka kertoo meille, miten näkymät järjestetään joko lineaarisesti tai ruudukkoon. Sen on käsiteltävä sovitinta, joka muuntaa alkiot (contactList) näkymiksi (OneContactView) ja sitten näkymänpitäjiksi (OneContactViewHolder), joiden kanssa RecyclerView on valmis työskentelemään tarjoamiensa metodien avulla.
RecyclerView'n raaka-aineena toimivat View'mme, esim. OneContactView, ja datalähde.
contactList:Array<Contact>
Olemme käyttäneet lähtökohtana yksinkertaista skenaariota saadaksemme käsityksen siitä, mitä RecyclerView pyrkii saavuttamaan. Perustapaus, jossa meillä on staattinen 1000 yhteystiedon taulukko, jonka haluamme näyttää käyttäjälle, on helppo ymmärtää. RecyclerView'n koneisto alkaa todella toimia, kun lista ei ole enää staattinen. Dynaamisen listan kanssa meidän on mietittävä, mitä näytöllä olevalle näkymälle tapahtuu, kun lisäämme kohteen listaan tai poistamme kohteen listasta.
RecyclerView.LayoutManager
Sen lisäksi, että LayoutManager päättää, miten näkymät asetetaan, joko lineaarisesti tai ruudukkona, se tekee paljon työtä konepellin alla.ping Kierrättäjä tietää, milloin kierrätys on tehtävä.
Se on vastuussa keestäping track näytöllä kulloinkin näkyvistä näkymistä ja välittää nämä tiedot kierrätysmekanismille. Kun käyttäjä vierittää alaspäin, LayoutManager vastaa kierrätysjärjestelmän tiedottamisesta ylhäällä tarkentuvista näkymistä, jotta niitä voidaan käyttää uudelleen sen sijaan, että ne jäisivät sinne ja kuluttaisivat muistia tai tuhoutuisivat vain luodakseen uusia.
Tämä tarkoittaa, että LayoutManagerin on pidettävä track siitä, missä käyttäjä on, kun hän vierittää listaamme. Se tekee tämän käyttämällä indeksipohjaista listaa sijainneista, eli ensimmäinen kohde alkaa nollasta ja kasvaa vastaamaan listan kohteiden määrää.
Jos voimme tarkastella 10 kohdetta listallamme, jossa on esimerkiksi 100 kohdetta, LayoutManager tietää alussa, että sillä on tarkennuspisteenä View-0 ja View-9. Vierittäessämme LayoutManager pystyy laskemaan näkymät, jotka menevät epätarkasti.
LayoutManager pystyy vapauttamaan nämä näkymät kierrätysmekanismiin, jotta niitä voidaan käyttää uudelleen (niihin voidaan sitoa uutta dataa; esim. näkymän yhteystiedot voidaan poistaa ja seuraavan segmentin uudet yhteystiedot voivat korvata paikkamerkit).
Tämä olisi hyvä tapaus, jos listamme on staattinen, mutta yksi yleisimmistä RecyclerView'n käyttötapauksista on dynaamiset listat, joissa data voi tulla online-päätepisteestä tai jopa anturista. Dataa ei ainoastaan lisätä, vaan listallamme olevaa dataa voidaan joskus poistaa tai päivittää.
Datamme dynaaminen tila voi tehdä LayoutManagerista päättelyn erittäin vaikeaksi. Tästä syystä LayoutManager ylläpitää omaa listaansa kohteista ja positioista erillään kierrätyskomponentin käyttämästä listasta. Tämä varmistaa, että se suorittaa asettelutehtävänsä oikein.
Samaan aikaan RecyclerView'n LayoutManager ei halua vääristellä hallussaan olevaa dataa. Jotta LayoutManager toimisi oikein, se synkronoituu RecyclerView.Adapterin kanssa tietyin väliajoin (60 ms) ja jakaa tietoa listakohteistamme, eli lisätyistä, päivitetyistä, poistetuista tai paikasta toiseen siirretyistä kohteista. Saatuaan nämä tiedot LayoutManager järjestää näytön sisällön uudelleen vastaamaan muutoksia tarvittaessa.
Monet RecyclerView'n ydintoiminnoista liittyvät RecyclerView.LayoutManager- ja RecyclerView.Adapter-tiedostojen väliseen kommunikointiin. Adapter tallentaa luettelomme joskus staattisista tai joskus dynaamisista tiedoista.
Lisäksi RecyclerView tarjoaa meille metodeja, joita voimme käyttää tapahtumien kuuntelemiseen, kuten onBindViewHolder, kun RecyclerView.Adapter sitoo listamme sisällön (esim. yhteystiedon) ViewHolderiin, jotta sitä käytetään tietojen näyttämiseen näytöllä.
Toinen on onCreateViewHolder, joka kertoo meille, milloin RecyclerView.Adapter ottaa tavallisen View-näkymän, kuten OneContactView-näkymän, ja muuntaa sen ViewHolder-kohteeksi, jonka kanssa RecyclerView voi työskennellä.
Kierrätyksen mahdollistavien ydinmekanismien lisäksi RecyclerView tarjoaa tapoja mukauttaa toimintaa vaikuttamatta kierrätykseen. Näkymien uudelleenkäyttö vaikeuttaa yleisiä asioita, joihin olemme tottuneet staattisilla näkymillä, kuten reagointia onClick-tapahtumiin.
Kuten tiedämme, RecyclerView.LayoutManager joka esittelee näkymät käyttäjälle, saattaa hetkeksi olla eri luettelo kohteista kuin RecyclerView.Adapter , jonka luettelo on tallennettuna tietokantaan tai suoratoistettuna lähteestä. OnClick-tapahtumien lisääminen suoraan Views-objektiin voi johtaa odottamattomaan toimintaan, kuten väärän yhteystiedon poistamiseen.
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 mukaan uusin versio.
Lisättyään riippuvuuden meidän Gradle tiedosto, saamme kehotteen Android Studio synkronoidaksesi muutokset. Näin meidän Gradle tiedosto huolehtii RecyclerView-näkymän lisäämisestä tyhjään projektiin käyttäen vain oletusarvoja.
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 hedelmien nimien luettelon näytöllä.
Luettelo tuotteista
Siirrymme MainActivity-tiedostoomme ja luomme hedelmien nimiä sisältävän taulukon juuri ennen asennuksen aikana luotua onCreate()-metodia.
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ä lista ruudulla RecyclerView'n avulla. Tätä varten siirrymme asettelujamme sisältävään layout-hakemistoon 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'hun olemme lisänneet id-kentän, jota käytetään näkymän tunnistamiseen. Sitä ei luoda oletusarvoisesti. Olemme antaneet TextView'lle id-fruitName-tunnuksen, joka vastaa siihen sidottavaa dataa.
RecyclerView'n lisääminen pääasetteluun
Samassa aktiviteetissa on main_layout.xml-asettelutiedosto, joka luotiin meille oletuksena. Jos valitsisimme tyhjän projektin, se olisi luonut XML sisältää ConstraintLayout-näkymän ja sen sisällä on TextView-näkymä, jossa on teksti ”Hello”. Poistamme kaiken sisällön ja annamme asettelun sisältää vain RecyclerView-näkymän seuraavasti:
<?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-objektille id-attribuutin, jota käytämme siihen viittaamiseen koodissamme.
android:id="@+id/fruitRecyclerView"
Palaamme sitten MainActivity-tiedostoomme. Luomiemme tunnisteiden avulla voimme viitata juuri luomiimme näkymiin. Aloitamme viittaamalla RecyclerView-näkymään käyttämällä findViewById()-metodia, jonka tarjoaa AndroidTeemme tämän onCreate()-metodillamme. OnCreate()-metodimme 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.ViewHolder-metodin, joka muuntaa View-objektimme ViewHolder-objektiksi, jota RecyclerView käyttää objektiemme näyttämiseen. Teemme tämän heti onCreate()-metodin 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 vastaa seuraavista asioista: se ottaa hedelmien nimet hedelmätaulukosta, luo ViewHolder-objektin käyttämällä one_fruit_view.xml-näkymäämme, sitoo sitten hedelmän ViewHolder-objektiin ja sitoo sisällön dynaamisesti luomaamme View-objektiin.
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 viivoja FruitArrayAdapter-objektiimme, mikä kertoo, että meidän on toteutettava metodit, joita RecyclerView voi käyttää taulukon yhdistämiseen ViewHolder-objektiin.
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 osasta, getItemCount()-metodista. Tiedämme, miten taulukon alkioiden lukumäärä saadaan kutsumalla taulukon koko -ominaisuutta.
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 toteutamme onCreateViewHolder-metodin. Tässä RecyclerView pyytää meitä auttamaan FruitViewHolder-luokituksen luomisessa. Jos muistamme, FruitViewHolder-luokkamme näytti tältä:
class FruitViewHolder(fruitView: View): RecyclerView.ViewHolder(fruitView)
Se vaatii fruitView-näkymän, jonka loimme XML-tiedostona one_fruit_view.xml. Voimme luoda viittauksen tähän XML-tiedostoon ja muuntaa sen View-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äljelle jäävä bitti on onBindViewHolder-ohituksen arvo.
fun onBindViewHolder(holder: FruitViewHolder, position: Int)
RecyclerView.Adapter pyytää position kokonaislukua, jota käytämme hakemaan alkion listaltamme. Se antaa meille myös pidikkeen, jotta voimme sitoa fruitArray-luokasta saamamme alkion näkymäpidikkeen sisällä olevaan näkymään (View). ViewHolder-elementin sisällä olevaan näkymään pääsee käsiksi ViewHolder.itemView-kentän kautta. Kun olemme saaneet näkymän, voimme käyttää aiemmin luomaamme fruitName-tunnusta 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) }
Tämän jälkeen FruitArrayAdapter 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 kytkemään RecyclerView'n jäljellä olevat osat. Ne luovat LayoutManagerin, joka kertoo RecyclerView'lle, miten listan sisältö näytetään: lineaarisesti LinearLayoutManagerilla vai ruudukossa GridLayoutManagerilla tai StaggeredGridLayoutManagerilla.
Luo Layout Manager
Palaamme takaisin onCreate-funktioomme 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-instanssin ja syöttäneet siihen hedelmien nimitaulukon. Ja periaatteessa kaikki on valmista. Koko 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) } } }




