Android RecyclerView: cos'è, impara con semplici esempi

In cosa consiste RecyclerView Android?

. RiciclatoreVedi è un widget che è la versione più flessibile e avanzata di GridView e ListView. È un contenitore per la visualizzazione di set di dati di grandi dimensioni che possono essere fatti scorrere in modo efficiente mantenendo un numero limitato di visualizzazioni. È possibile utilizzare il widget RecyclerView quando si hanno raccolte di dati i cui elementi cambiano in fase di esecuzione in base a un evento di rete o all'azione dell'utente.

Visualizzazioni

. Android la piattaforma utilizza le classi View e ViewGroup per disegnare elementi sullo schermo. Queste classi sono astratte e vengono estese a diverse implementazioni per adattarsi a un caso d'uso. TextView, ad esempio, ha il semplice scopo di visualizzare contenuto testuale sullo schermo. EditText si estende dalla stessa classe View e aggiunge più funzionalità per consentire all'utente di inserire dati.

È possibile creare le nostre visualizzazioni personalizzate per poter ottenere una maggiore flessibilità durante lo sviluppo delle interfacce utente. La classe View fornisce metodi che possiamo sovrascrivere per disegnare sullo schermo e un mezzo per passare parametri come larghezza, altezza e i nostri attributi personalizzati che vorremmo aggiungere alla nostra View per farlo comportare come vorremmo.

Visualizza gruppi

La classe ViewGroup è una sorta di View ma, a differenza della semplice classe View la cui responsabilità è semplicemente la visualizzazione, ViewGroup ci dà la possibilità di inserire più visualizzazioni in un'unica visualizzazione, a cui possiamo fare riferimento nel suo insieme. In questo caso, la vista creata al livello superiore a cui aggiungiamo altre viste semplici (possiamo anche aggiungere viewGroup) è chiamata "genitore" e le viste aggiunte all'interno sono "figlie".

Possiamo immaginare una vista come un array e un ViewGroup come un array di array. Dato che un Array di Array è esso stesso un Array, possiamo vedere come un ViewGroup possa essere trattato come una 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

Il ViewGroup ci permette anche di definire come sono organizzati i bambini all'interno della vista, ad esempio, sono disposti verticalmente o orizzontalmente. Possiamo avere regole diverse per l'interazione all'interno della View. Ad esempio, i TextView successivi dovrebbero avere una distanza di 12 dp mentre gli ImageView seguiti da TextView dovrebbero avere una distanza di 5 dp.

Questo sarebbe il caso se sviluppassimo il nostro ViewGroup da zero. Per facilitare queste configurazioni, Android fornisce una classe chiamata LayoutParams, che possiamo utilizzare per inserire queste configurazioni.

Android documentazione fornisce alcuni parametri predefiniti che implementeremo durante la configurazione del nostro ViewGroup. Alcuni parametri comuni sono quelli che riguardano larghezza, altezza e margine. Per impostazione predefinita, queste configurazioni hanno la struttura android:layout_height per altezza, ad esempio android:layout_width. A questo proposito, quando crei il tuo ViewGroup, puoi creare ulteriormente LayoutParams specifici per il modo in cui desideri che il tuo ViewGroup si comporti.

Android viene fornito con visualizzazioni e ViewGroup predefiniti che possiamo utilizzare per eseguire molte delle attività comuni di cui abbiamo bisogno. Un esempio che abbiamo menzionato è TextView. Questa è una visualizzazione semplice che include aspetti configurabili come altezza, larghezza, textSize e simili. Abbiamo un ImageView per visualizzare immagini e EditText come avevamo menzionato, tra molti altri. Android ha anche ViewGroup personalizzati a cui possiamo aggiungere le nostre visualizzazioni e ottenere il comportamento previsto.

Layout lineare

LinearLayout ci consente di aggiungere elementi View al suo interno. LinearLayout è un attributo di orientamento che determina come verrà disposto sullo schermo. Ha anche LinearLayout.LayoutParams che dettano le regole per le viste all'interno, ad esempio, l'attributo android:center_horizontal centrerebbe le viste lungo l'asse orizzontale mentre `android:center_vertical centrerebbe il contenuto della vista lungo l'asse verticale.

Ecco alcune immagini per comprendere la centratura. Considereremmo questo come un semplice TextView all'interno di uno spazio di 200 x 200 px, centrando gli attributi lo farebbe comportare come segue.

androide:centro_orizzontale

Contenuto centrato orizzontalmente
Contenuto centrato orizzontalmente

androide:centro_verticale

Contenuto centrato verticalmente
contenuto centrato verticalmente

androide:centro

Contenuto centrato
Contenuti centrati

Componenti principali di RecyclerView

Componenti principali di RecyclerView
Componenti principali di RecyclerView

Di seguito sono riportati i componenti importanti di RecyclerView:

RecyclerView.Adapter

Base n. 1 – Modello adattatore

Un adattatore è un dispositivo che trasforma gli attributi di un sistema o di un dispositivo in quelli di un dispositivo o sistema altrimenti incompatibile. Alcuni modificano gli attributi del segnale o della potenza, mentre altri semplicemente adattano la forma fisica di un connettore a un altro.

Un semplice esempio che troviamo nella vita reale per spiegare un adattatore è quando dobbiamo connettere insieme i dispositivi, ma hanno porte di connessione che non corrispondono tra loro. Questo potrebbe essere il caso quando visiti un paese diverso in cui vengono utilizzati diversi tipi di prese. Se porti con te il caricabatterie del telefono o del laptop, sarebbe impossibile collegarlo alle prese di corrente. Tuttavia, non ti arrenderesti ma otterresti semplicemente un adattatore che si intrometterebbe tra la presa di corrente e il caricabatterie e consentirebbe la ricarica.

Questo è il caso della programmazione quando vogliamo connettere insieme due strutture dati per svolgere l'attività, ma le loro porte predefinite non hanno un modo per comunicare tra loro.

Utilizzeremo il semplice esempio di un dispositivo e di un caricabatterie. Avremo due istanze di caricabatterie. Uno americano e uno britannico

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

Creeremo quindi due dispositivi

class AmericanDevice() 
class BritishDevice()

Ad esempio, possiamo quindi creare alcune istanze dei dispositivi con cui suonare.

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

Introdurremo quindi il concetto di ricarica per entrambi i dispositivi aggiungendo un metodo nei dispositivi chiamato charge() .

Il metodo accetta come input il rispettivo caricabatterie ed effettua la ricarica in base ad esso.

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

In questo caso, in base alla nostra analogia, per un motivo o per l'altro avremmo bisogno di utilizzare un caricabatterie britannico quando utilizziamo un dispositivo americano o viceversa.

Nel mondo della programmazione, questo di solito avviene quando si mescolano insieme librerie che offrono la stessa funzionalità (nel nostro contesto, la nostra funzionalità condivisa è a pagamento). Dovremmo trovare un modo per abilitarlo.

Se seguiamo l'analogia, dovremo andare in un negozio di elettronica e acquistare un adattatore che ci permetterà di caricare i dispositivi americani forniti con i caricatori britannici. Dal punto di vista della programmazione, saremo noi a produrre l'adattatore.

Realizzeremo un adattatore per uno che corrisponda esattamente al modello di cui avremmo bisogno per creare l'altro. Lo implementeremo come classe come segue. Non deve necessariamente essere una classe e potrebbe essere una funzione che evidenzia ciò che generalmente fa il modello dell'adattatore. Utilizzeremo una classe in base alla maggior parte dell'utilizzo 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
    }
}

Nel mondo della programmazione, la differenza nelle prese è analoga alla differenza nei metodi utilizzati per la ricarica. I caricabatterie con metodi diversi renderebbero impossibile l'utilizzo dei caricabatterie.

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

Provare a chiamare il metodo charge() in myBritishDevice con americanChargerIFound non funzionerebbe poiché AmericanDevice accetta solo un AmericanCharger

Quindi è impossibile farlo

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

In questo scenario l'adattatore che abbiamo creato

AmericanToBritishChargerAdapter ora può tornare utile. Possiamo utilizzare il metodo returnNewCharger() per creare un nuovo BritishCharger, che possiamo utilizzare per caricare. Tutto ciò di cui abbiamo bisogno è creare un'istanza del nostro adattatore e alimentarla con l'AmericanCharger che abbiamo, e creerà un BritishCharger che possiamo usare

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

Quando si ha a che fare con un ViewGroup, al suo interno vengono inserite le visualizzazioni. E proprio LayoutManager avrebbe il compito di descrivere come sono disposte le Views al suo interno.

A scopo di confronto, quando si lavora con Linearlayout ViewGroup, il caso d'uso che desideriamo è la possibilità di posizionare gli elementi verticalmente o orizzontalmente. Questo può essere facilmente implementato aggiungendo un attributo di orientamento, che ci dice come verrà posizionato il layout lineare sullo schermo. Possiamo farlo utilizzando android:orientation=VERTICAL|HORIZONTAL attributo.

Abbiamo anche un altro ViewGroup chiamato GridLayout, il suo caso d'uso è quando vorremmo posizionare Views in una struttura Grid rettangolare. Ciò potrebbe essere dovuto a ragioni quali la facilità di utilizzo dei dati che presentiamo all'utente dell'app. In base alla progettazione, GridLayout consente configurazioni per aiutarti a raggiungere questo obiettivo avendo configurazioni in cui possiamo definire le dimensioni della griglia, ad esempio possiamo avere una griglia 4×4, una griglia 3 x 2.

RecyclerView.ViewHolder

ViewHolder è una classe astratta che estendiamo anche da RecyclerView. ViewHolder ci fornisce metodi comuni per aiutarci a fare riferimento a una vista che abbiamo inserito in RecyclerView anche dopo che il macchinario di riciclaggio in RecyclerView ha modificato vari riferimenti di cui non siamo a conoscenza.

Elenchi di grandi dimensioni

Le RecyclerView vengono utilizzate quando vogliamo presentare all'utente un insieme di visualizzazioni molto ampio, senza esaurire le nostre RAM sul nostro dispositivo per ogni singola istanza della vista creata.

Se dovessimo prendere il caso di una lista di contatti, avremmo un'idea generale di come apparirebbe un contatto nella lista. Ciò che faremmo quindi è creare un layout modello - che in realtà è una vista - con slot in cui verranno riempiti vari dati dal nostro elenco di contatti. Di seguito è riportato uno pseudo codice che spiega l'intero scopo:

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

Avremmo quindi una ContactList di questa natura

 <ContactList>
</ContactList>

Se fosse così, codificassimo i contenuti, non avremmo un modo programmatico per aggiungere nuovi contenuti all'elenco senza riscrivere l'app. Fortunatamente per noi. L'aggiunta di una vista a un ViewGroup è supportata da un addView(view:View) metodo.

Anche se è così, non è così che RecyclerView ottiene le visualizzazioni figlie aggiunte.

Nel nostro caso d'uso, avremmo un lungo elenco di contatti. Per ogni contatto nell'elenco, dovremmo creare OneContactView e popolare i dati all'interno della View in modo che corrispondano ai campi nella nostra classe Contact. Quindi, una volta ottenuta la vista, dovremmo aggiungerla a RecyclerView per mostrare l'elenco.

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)

Abbiamo una serie di contatti chiamata OneContactView. Contiene slot per prendere contenuti dalla classe Contact e visualizzarli. In RecyclerView dobbiamo aggiungere Views in modo che possa aiutarci con la sua capacità di riciclaggio.

RecyclerView in realtà non ci consente di aggiungere una vista ma ci consente di aggiungere un ViewHolder. Quindi, in questo scenario, abbiamo due elementi che vogliamo connettere ma che non corrispondono. È qui che entra in gioco il nostro adattatore. RecyclerView ci fornisce un adattatore molto simile al nostro AmericanToBritishChargerAdapter() da prima, questo ci ha permesso di convertire il nostro caricabatterie americano che era inutilizzabile con il nostro dispositivo britannico in qualcosa di utilizzabile, simile all'alimentatore nella vita reale.

In questo scenario, l'adattatore prenderebbe la nostra serie di contatti e la nostra vista e da lì genererebbe ViewHolder che RecyclerView è disposto ad accettare.

RecyclerView fornisce un'interfaccia che possiamo estendere per creare il nostro adattatore tramite la classe RecyclerView.Adapter. All'interno di questo adattatore c'è un modo per creare la classe ViewHolder con cui RecyclerView vuole lavorare. Quindi, quello che abbiamo è la stessa situazione di prima, ma con una cosa in più, ovvero l'adattatore.

Abbiamo una serie di contatti, una vista per visualizzare un contatto OneContactView. Un RecyclerView è un elenco di visualizzazioni che forniscono servizi di riciclaggio ma sono disposti ad assumere solo ViewHolder

Ma in questo scenario, ora abbiamo la classe RecyclerView.Adapter, che contiene un metodo per creare ViewHolder al suo interno.

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

RecyclerView.ViewHolder è una classe astratta che prende la nostra View come argomento e la converte in ViewHolder.

Utilizza il modello wrapper utilizzato per estendere le capacità delle classi.

Fondamenti n. 2 – Modello di involucro

Utilizzeremo un semplice esempio per dimostrare come possiamo far parlare gli animali.

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

Nell'esempio sopra, abbiamo due animali. Se per caso volessimo aggiungere un metodo per far parlare, ma l'autore della libreria non fosse divertente, potremmo comunque trovare un modo. Ciò di cui abbiamo bisogno sarebbe un involucro per la nostra classe Animale. Lo faremmo prendendo l'Animale come costruttore per la nostra classe

 class SpeechPoweredAnimalByWrapper(var myAnimal:Animal){

    fun sound(){
        myAnimal.sound()
    }

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

Ora possiamo passare un'istanza animale a SpeechPoweredAnimalByWrapper. Chiamare il metodo sound() su di esso chiamerebbe il metodo passato nel suono animale(). Abbiamo anche un metodo speak() aggiuntivo, che conta come nuova funzionalità che aggiungiamo agli animali passati. Possiamo usarlo come segue:

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"

Usando questo modello, possiamo prendere lezioni e aggiungere funzionalità. Tutto ciò di cui avremmo bisogno è passare un'istanza di classe e nuovi metodi definiti dalla nostra classe di confezionamento.

Nel nostro caso sopra, abbiamo utilizzato una classe concreta. È anche possibile implementare lo stesso in una classe Abstract. Dovremmo solo aggiungere la modifica della classe SpeechPoweredAnimalByWrapper in abstract e il gioco è fatto. Modificheremo il nome della classe in qualcosa di più breve per renderlo più leggibile.

abstract class SpeechPowered(var myAnimal:Animal){

    fun sound(){
        myAnimal.sound()
    }

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

E' uguale a prima, ma significherebbe qualcos'altro. In una classe normale, possiamo avere un'istanza di una classe nello stesso modo in cui abbiamo creato cat1 e dog1. Le classi astratte, tuttavia, non sono pensate per essere istanziate ma sono pensate per estendere altre classi. Quindi, come utilizzeremmo la nuova classe astratta SpeechPowered(var myAnimal:Animal). Possiamo usarlo creando nuove classi che lo estenderanno e, a loro volta, ne otterranno le funzionalità.

Nel nostro esempio creeremo una classe SpeechPoweredAnimal che estende la classe

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

Questo è lo stesso modello utilizzato in ViewHolder. La classe RecyclerView.ViewHolder è una classe astratta che aggiunge funzionalità alla View, proprio come abbiamo aggiunto il metodo speak agli animali. La funzionalità aggiunta è ciò che lo fa funzionare quando si ha a che fare con RecyclerView.

Ecco come creeremo un OneContactViewHolder da 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 ha un adattatore che ci consente di connettere il nostro array di contatti a CONTACTSView con RecyclerView

Aggiunta di una vista

Il ViewGroup non ridisegna automaticamente il ViewGroup ma segue una pianificazione particolare. Potrebbe succedere che il tuo dispositivo venga ridisegnato ogni 10 ms o 100 ms, o se scegliamo un numero assurdo diciamo 1 minuto quando aggiungiamo una vista a un ViewGroup vedrai le modifiche 1 minuto dopo quando il ViewGroup "si aggiorna".

RecyclerView.Recycler

Fondamenti n.3. Memorizzazione nella cache

Uno dei migliori esempi di dove eseguiamo regolarmente gli aggiornamenti è nel browser. Immaginiamo, ad esempio, che il sito che stiamo visitando sia statico e non invii contenuti in modo dinamico, dovremmo continuare ad aggiornare per vedere i cambiamenti.

Per questo esempio, immaginiamo che il sito in questione sia Twitter. Avremmo elencato una serie di tweet statici e l'unico modo per vedere i nuovi tweet sarebbe fare clic sul pulsante di aggiornamento per recuperare il contenuto.

Ridipingere l'intero schermo è ovviamente una cosa costosa. Se dovessimo immaginare che fosse così, avremmo avuto una larghezza di banda limitata con il nostro operatore telefonico. E la nostra lista dei tweet conteneva molte immagini e video, sarebbe costoso scaricare nuovamente tutti i contenuti della pagina ad ogni aggiornamento.

Avremmo bisogno di un modo per archiviare i Tweet già caricati e garantire che la nostra prossima richiesta abbia la capacità di pronunciare i Tweet che già possiede. Pertanto, non scarica di nuovo tutto e riceve solo i nuovi Tweet che ha e controlla anche se qualche Tweet che era stato salvato localmente non è più presente in modo da poterlo eliminare localmente. Ciò che stiamo descrivendo si chiama Caching.

Le informazioni che inviamo al sito Web sui contenuti di cui disponiamo si chiamano metadati. Quindi, in realtà, non stiamo semplicemente dicendo "vogliamo caricare il tuo sito", diciamo "vogliamo caricare il tuo sito, ed ecco alcuni dei contenuti che avevamo già salvato dall'ultima volta che lo abbiamo caricato, per favore usali per inviaci solo ciò che non c'è, quindi non utilizziamo molta larghezza di banda poiché non disponiamo di molte risorse."

Chiamate di layout: la Tweet List deve essere pazzesca

Un esempio di chiamata al layout è scrollToPosition

Questo è un esempio comune presente in cose come le app di chat. Se qualcuno in un thread di chat risponde a un fumetto della chat precedente, alcune app di chat includono la risposta e un collegamento al fumetto della chat, che facendo clic ti porta al punto in cui si trovava il messaggio originale.

Nel caso, chiamiamo questo metodo prima di aggiungere un LayoutManager al nostro RecyclerView e prima di avere un RecyclerView.Adapter, scrollToPosition(n:Int) viene semplicemente ignorato.

Comunicazione tra i componenti RecyclerView

Fondamenti n.4. Richiamate

RecyclerView, nello svolgere il suo lavoro, ha molte parti in movimento. Ha a che fare con il LayoutManager che ci dice come organizzare le Viste, sia Linearmente che in una Griglia. Ha a che fare con un adattatore che fa il lavoro di convertire i nostri elementi contactList in Views OneContactView e poi in ViewHolders OneContactViewHolder che RecyclerView è disposto a lavorare nei metodi che ci fornisce.

La materia prima per RecyclerView sono le nostre visualizzazioni, ad esempio OneContactView e l'origine dati.

contactList:Array<Contact>

Abbiamo utilizzato uno scenario semplice come punto di partenza per avere un'idea di ciò che RecyclerView sta cercando di ottenere.

Un caso base di quando avremmo un array statico di 1000 contatti che vogliamo mostrare all'utente è facile da capire.

Il meccanismo di RecyclerView inizia davvero a prendere vita quando l'elenco non è più statico.

Con un elenco dinamico, dobbiamo pensare a cosa succede alla vista sullo schermo quando aggiungiamo un elemento all'elenco o rimuoviamo un elemento dall'elenco.

RecyclerView.LayoutManager

Oltre a decidere come disporre le nostre opinioni in modo lineare o in una griglia. Il LayoutManager fa molto lavoro dietro le quinte per aiutare il Riciclatore a sapere quando eseguire il Riciclo.

È responsabile di tenere traccia delle visualizzazioni attualmente visibili sullo schermo e di comunicare queste informazioni al meccanismo di riciclo. Man mano che l'utente scorre verso il basso, il Layout manager ha il compito di informare il sistema di Riciclo delle Viste che vanno fuori fuoco in alto in modo che possano essere riutilizzate invece che rimanere lì e consumare memoria o invece di distruggerle e dover creare nuovi.

Ciò significa che LayoutManager deve tenere traccia di dove si trova l'utente mentre scorre il nostro elenco. Lo fa avendo un elenco di posizioni che sono base dell'indice, ovvero il primo elemento deve iniziare da 0 e aumentare fino a corrispondere al numero di elementi nel nostro elenco.

Se riusciamo a visualizzare 10 elementi nel nostro elenco, diciamo 100, all'inizio, il LayoutManager è consapevole di avere a fuoco view-0 fino a View-9 Mentre scorriamo il LayoutManager è in grado di calcolare le visualizzazioni che escono di messa a fuoco.

Il LayoutManager è in grado di rilasciare queste viste al meccanismo di riciclo in modo che possano essere riutilizzate (nuovi dati possono essere associati ad esse, ad esempio i dati di contatto di una vista possono essere rimossi e i nuovi dati di contatto dal segmento successivo possono sostituire i segnaposto).

Questo sarebbe un caso felice se l'elenco che abbiamo fosse statico, ma uno dei casi d'uso più comuni di utilizzo di RecyclerView è con elenchi dinamici in cui i dati possono provenire da un endpoint online o anche forse da un sensore. Non solo vengono aggiunti dati, ma a volte i dati presenti nel nostro Elenco vengono rimossi o aggiornati.

Lo stato dinamico dei nostri dati può rendere molto difficile ragionare sul LayoutManager. Per questo motivo, il LayoutManager mantiene un proprio elenco degli elementi e delle posizioni, separato dall'elenco utilizzato dal componente Recycling. Ciò garantisce che svolga correttamente il suo lavoro di layout.

Allo stesso tempo, il LayoutManager di RecyclerView non vuole travisare i dati in suo possesso. Per funzionare correttamente, LayoutManager si sincronizza con RecyclerView.Adapter a determinati intervalli (60 ms) condividendo informazioni sugli elementi della nostra lista. ovvero elementi aggiunti, aggiornati, rimossi, spostati da una posizione all'altra). Il LayoutManager, dopo aver ricevuto queste informazioni, riorganizza i contenuti sullo schermo per corrispondere alle modifiche quando necessario.

Molte delle operazioni principali che riguardano RecylerView ruotano attorno alla comunicazione tra RecyclerView.LayoutManager e RecyclerView.Adapter che memorizza i nostri elenchi di dati a volte statici o talvolta dinamici.

Inoltre, RecyclerView ci presenta metodi che possiamo utilizzare per ascoltare eventi come onBindViewHolder quando il nostro RecyclerView.Adapter associa il contenuto del nostro elenco, ad esempio un contatto a un ViewHolder in modo che ora venga utilizzato per visualizzare le informazioni sullo schermo.

Un altro è onCreateViewHolder, che ci dice quando l'adattatore RecyclerView. L'adattatore prende una vista normale come OneContactView e la converte in un elemento ViewHolder con cui RecyclerView può funzionare. Dal nostro ViewHolder per l'utilizzo da parte di RecyclerView. onViewDetached

A parte i meccanismi fondamentali che consentono il riciclaggio. RecyclerView fornisce modalità per personalizzare il comportamento senza influire sul riciclaggio.

Il riutilizzo delle visualizzazioni rende difficile eseguire operazioni comuni a cui siamo abituati con le visualizzazioni statiche, ad esempio reagire agli eventi onClick.

Poiché siamo consapevoli che il RecyclerView.LayoutManager che presenta le visualizzazioni all'utente potrebbe per un momento avere un elenco di elementi diverso da quello RecyclerView.Adapter che contiene l'elenco che abbiamo archiviato in un database o trasmesso in streaming da una fonte. L'inserimento degli eventi OnClick direttamente nelle visualizzazioni può portare a comportamenti imprevisti, come l'eliminazione o la modifica del contatto sbagliato.

Gradle

Se vogliamo utilizzare RecyclerView, dobbiamo aggiungerlo come dipendenza nel nostro file build .gradle.

Nell'esempio seguente abbiamo utilizzato l'implementazione "androidx.recyclerview:recyclerview:1.1.0" che è la versione più recente secondo questo articolo.

Dopo aver aggiunto la dipendenza a our Gradle file, ci verrà richiesto da Android Studio a Synccronizza i cambiamenti,

Ecco come il nostro Gradle apparirà come il file dopo aver aggiunto un RecyclerView in un progetto vuoto con solo le impostazioni predefinite.

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

Al momento abbiamo un solo file di layout. Inizieremo con un semplice esempio in cui utilizzeremo RecyclerView per mostrare un elenco di nomi di frutta sullo schermo.

Elenco degli articoli

Andremo al nostro file MainActivity e creeremo un array con i nomi dei frutti all'interno subito prima del metodo onCreate() che è stato generato durante la configurazione.

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

Il nostro prossimo obiettivo sarà presentare questo elenco sullo schermo utilizzando RecyclerView.

Per fare ciò, navigheremo nella directory layout che contiene i nostri layout e creeremo una vista che sarà responsabile di mostrare un frutto.

Layout da utilizzare per ogni elemento del nostro elenco

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

Nel TextView sopra, abbiamo aggiunto un campo id che verrà utilizzato per identificare una vista.

Non viene generato per impostazione predefinita. Abbiamo nel nostro TextView l'id fruitName per abbinare i dati, che saranno associati ad esso.

Aggiunta di RecyclerView al layout principale

Nella stessa attività è presente il file di layout main_layout.xml che è stato generato per noi per impostazione predefinita.

Se scegliamo un progetto vuoto. Avrà generato un file XML contenente un ConstraintLayout e all'interno ci sarà un TextView con il testo "Hello".

Elimineremo tutti i contenuti e il layout conterrà solo RecyclerView come di seguito:

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

Abbiamo anche aggiunto un attributo id per RecyclerView che utilizzeremo per farvi riferimento nel nostro codice.

 android:id="@+id/fruitRecyclerView"

Torneremo quindi al nostro file MainActivity. Utilizzando gli ID che abbiamo creato, saremo in grado di fare riferimento alle View che abbiamo appena creato.

Inizieremo facendo riferimento a RecyclerView utilizzando il metodo findViewById() fornito da Android. Lo faremo nel nostro metodo onCreate().

Il nostro metodo onCreate() apparirà come segue.

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

Crea un ViewHolder

Successivamente, creeremo un RecyclerView.ViewHolder che è responsabile di prendere la nostra vista e convertirla in un ViewHolder, che RecyclerView utilizza per visualizzare i nostri articoli.

Lo faremo subito dopo il nostro divertente metodo 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)
    
}

Creare un RecyclerViewAdapter

Successivamente, creeremo una classe FruitArrayAdapter che estende la classe RecyclerView.Adapter.

Il FruitArrayAdapter che creeremo sarà responsabile di quanto segue.

Prenderà i nomi dei frutti dall'array dei frutti. Creerà un ViewHolder utilizzando la nostra vista one_fruit_view.xml. Quindi legherà il frutto a un ViewHolder e legherà dinamicamente il contenuto alla vista che abbiamo creato 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 aggiungerà scarabocchi rossi sul nostro FruitArrayAdapter, dicendoci che dobbiamo implementare un metodo che RecyclerView può utilizzare per connettere il nostro array a un ViewHolder che RecyclerView può utilizzare.

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

Inizieremo con la parte più semplice del codice generato: il metodo getItemCount(). Sappiamo come ottenere il numero di elementi nel nostro array chiamando il metodo 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) {
          
        }
    }

Quindi implementeremo il metodo override fun onCreateViewHolder.

È qui che RecyclerView ci chiede di aiutarlo a costruire un FruitHolder per esso.

Se ricordiamo, ecco come appariva la nostra classe FruitViewHolder:

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

Richiede il nostro fruitView che abbiamo creato come file xml one_fruit_view.xml

Possiamo essere in grado di creare un riferimento a questo xml e convertirlo in una vista come segue.

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

        }
    }

Il bit rimanente è l'override

fun onBindViewHolder(holder: FruitViewHolder, position: Int)

RecyclerView.Adapter richiede un numero intero di posizione, che utilizzeremo per recuperare un elemento dal nostro elenco. Ci fornisce anche un titolare in modo che possiamo associare l'elemento che otteniamo da fruitArray alla vista che è contenuta all'interno del titolare della vista.

La vista contenuta all'interno di un ViewHoder è accessibile tramite il campo ViewHolder.itemView. Una volta ottenuta la vista, possiamo utilizzare l'id fruitName che abbiamo creato in precedenza per impostare il contenuto.

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

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

            var aFruitName = fruitArray.get(position)

            ourFruitTextView.setText(aFruitName)
        }

Con ciò il nostro FruitArrayAdapter è completo e appare come segue.

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

Infine, siamo pronti per collegare i pezzi rimanenti del nostro RecyclerView. Che stanno creando un LayoutManager, che indicherà a RecyclerView come visualizzare il contenuto dell'elenco. Se mostrare in modo lineare utilizzando LinearLayoutManager o in una griglia utilizzando GridLayoutManager o StaggeredGridLayoutManager.

Crea Gestore layout

Torneremo all'interno della nostra funzione onCreate e aggiungeremo 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
        
    }

Aggancia il nostro adattatore agli elementi e impostalo su 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
    }

Abbiamo anche creato un'istanza di fruitListAdapter e le abbiamo fornito l'array di nomi di frutta.

E sostanzialmente, abbiamo finito.

Il file MainActivity.kt completo ha il seguente aspetto.

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

Scarica il progetto