Android RecyclerView: cos'è, impara con semplici esempi
⚡ Riepilogo intelligente
RecyclerView è un sistema avanzato Android Widget che visualizza in modo efficiente grandi set di dati scorrevoli riutilizzando un numero limitato di viste. Si basa su un adattatore, un gestore di layout e un contenitore di viste per associare le raccolte di dati che cambiano in fase di esecuzione.

In cosa consiste RecyclerView Android?
Migliori RiciclatoreVedi RecyclerView è una versione più flessibile e avanzata di GridView e ListView. Si tratta di un contenitore per la visualizzazione di grandi insiemi di dati che possono essere scorciati in modo efficiente mantenendo un numero limitato di viste. È possibile utilizzare il widget RecyclerView quando si dispone di raccolte di dati i cui elementi cambiano in fase di esecuzione in base a un evento di rete o a un'azione dell'utente.
Visualizzazioni
Migliori Android La piattaforma utilizza le classi View e ViewGroup per disegnare elementi sullo schermo. Queste classi sono assolutetrace vengono estesi a diverse implementazioni per adattarsi a un caso d'uso. TextView, ad esempio, ha il semplice scopo di visualizzare contenuto testuale sullo schermo. EditText estende la stessa classe View e aggiunge ulteriori funzionalità per consentire all'utente di inserire dati.
È possibile creare le nostre viste personalizzate per ottenere maggiore flessibilità durante lo sviluppoping Interfacce utente. La classe View fornisce metodi che possiamo sovrascrivere per disegnare sullo schermo e un modo per passare parametri come larghezza, altezza e i nostri attributi personalizzati che vorremmo aggiungere alla nostra View per farla comportarsi come desideriamo.
Visualizza gruppi
La classe ViewGroup è un tipo di View, ma a differenza della semplice classe View, la cui responsabilità è semplicemente quella di visualizzare, ViewGroup ci offre la possibilità di inserire più viste in un'unica vista, a cui possiamo fare riferimento come un tutt'uno. In questo caso, la View creata al livello superiore, a cui aggiungiamo altre viste semplici, viene chiamata "parent", mentre le viste aggiunte al suo interno sono chiamate "children".
Possiamo immaginare una View come un Array e un ViewGroup come un Array di Array. Dato che un Array di Array è a sua volta un Array, possiamo capire 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 analogy, 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 gli elementi figli sono organizzati all'interno della vista, ad esempio se sono disposti verticalmente o orizzontalmente. Possiamo avere regole diverse per l'interazione all'interno della vista. Ad esempio, le TextView una accanto all'altra dovrebbero avere una distanza di 12 dp, mentre un'ImageView seguita da una TextView dovrebbe avere una distanza di 5 dp.
Questo sarebbe il caso se fossimo sviluppatiping il nostro ViewGroup da zero. Per semplificare queste configurazioni, Android fornisce una classe chiamata LayoutParams, che possiamo utilizzare per inserire queste configurazioni.
Migliori Android documentazione Fornisce alcuni parametri predefiniti che implementeremmo quando configuriamo il nostro ViewGroup. Alcuni parametri comuni riguardano larghezza, altezza e margine. Per impostazione predefinita, queste configurazioni hanno la struttura android:layout_height per l'altezza e android:layout_width per la larghezza. A questo proposito, quando crei il tuo ViewGroup, puoi creare ulteriormente dei LayoutParam specifici per il modo in cui desideri che il tuo ViewGroup si comporti.
Android Il sistema include View e ViewGroup predefiniti che possiamo utilizzare per svolgere molte delle attività comuni di cui abbiamo bisogno. Un esempio che abbiamo già menzionato è la TextView. Si tratta di una vista semplice che include aspetti configurabili come altezza, larghezza e dimensione del testo. Abbiamo anche una ImageView per visualizzare immagini e un EditText, come abbiamo detto, tra molti altri. Android ha anche ViewGroup personalizzati a cui possiamo aggiungere le nostre visualizzazioni e ottenere il comportamento previsto.
Layout lineare
LinearLayout ci permette di aggiungere elementi View al suo interno. LinearLayout ha un attributo `orientation` che ne determina la disposizione sullo schermo. Dispone inoltre di `LinearLayout.LayoutParams` che definiscono le regole per le viste al suo interno. Ad esempio, l'attributo `android:center_horizontal` centra le viste lungo l'asse orizzontale, mentre `android:center_vertical` centra il contenuto della vista lungo l'asse verticale.
Ecco alcune immagini per aiutare a capire il centraggio. Prendiamo come esempio una semplice TextView all'interno di uno spazio di 200px per 200px; gli attributi di centraggio la farebbero comportare come segue.
androide:centro_orizzontale
Contenuto centrato orizzontalmente
androide:centro_verticale
Contenuto centrato verticalmente
androide:centro
Contenuti centrati
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 le caratteristiche di un sistema o dispositivo in quelle di un dispositivo o sistema altrimenti incompatibile. Alcuni adattatori modificano le caratteristiche del segnale o dell'alimentazione, mentre altri si limitano ad adattare la forma fisica di un connettore a un altro.
Un semplice esempio pratico che illustra l'utilità di un adattatore è quando dobbiamo collegare tra loro dei dispositivi, ma le loro porte di connessione non sono compatibili. Questo potrebbe accadere, ad esempio, quando si visita un Paese che utilizza prese di corrente diverse. Se si porta con sé il caricabatterie del telefono o del portatile, sarebbe impossibile collegarlo alla presa di corrente locale. Tuttavia, non ci si arrenderebbe e si acquisterebbe semplicemente un adattatore da inserire tra la presa e il caricabatterie, consentendo così la ricarica.
Questo è il caso in programmazione quando vogliamo collegare due strutture dati per svolgere un compito, ma le loro porte predefinite non hanno un modo per comunicare tra loro.
Useremo il semplice esempio di un dispositivo e di un caricabatterie. Avremo due esempi di caricabatterie, uno americano e uno britannico.
class AmericanCharger() { var chargingPower = 10 } class BritishCharger(){ var chargingPower = 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 chiamato charge(). Il metodo riceve in input il rispettivo caricabatterie ed effettua la ricarica in base a 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, basandoci sulla nostra analogia, per un motivo o per l'altro avremmo bisogno di usare un caricabatterie britannico quando usiamo 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.
Seguendo l'analogia, dovremo andare in un negozio di elettronica e acquistare un adattatore che ci permetta di caricare dispositivi americani utilizzando caricabatterie britannici. Dal punto di vista della programmazione, saremo noi i produttori dell'adattatore.
Creeremo un adattatore che corrisponda esattamente al modello di cui avremmo bisogno per creare l'altro. Lo implementeremo come una classe come segue. Non deve necessariamente essere una classe e potrebbe essere una funzione che evidenzia cosa fa generalmente il modello dell'adattatore. Usiamo una classe poiché corrisponde alla maggior parte dell'utilizzo su 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 } }
Nel mondo della programmazione, la differenza tra le prese è analoga alla differenza tra i metodi di ricarica. Caricabatterie con metodi diversi renderebbero impossibile l'utilizzo degli stessi.
var myBritishDevice = new BritishDevice() var americanChargerIFound = new AmericanCharger()
Tentare di chiamare il metodo charge() su 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, può tornare utile. Possiamo usare il metodo returnNewCharger() per creare un nuovo BritishCharger, che potremo utilizzare per la ricarica. Tutto ciò che dobbiamo fare è creare un'istanza del nostro adattatore e fornirgli l'AmericanCharger che abbiamo, e questo creerà un BritishCharger che potremo utilizzare.
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
Quando si ha a che fare con un ViewGroup, al suo interno sono posizionate delle View. Il LayoutManager ha il compito di descrivere come le View sono disposte al suo interno.
Per confronto, quando si lavora con un LinearLayout ViewGroup, il caso d'uso che desideriamo è la possibilità di posizionare gli elementi verticalmente o orizzontalmente. Questo si implementa facilmente aggiungendo un attributo di orientamento, che ci dice come il LinearLayout verrà posizionato sullo schermo. Possiamo farlo utilizzando il android:orientation=VERTICAL|HORIZONTAL attributo.
Abbiamo anche un altro ViewGroup chiamato GridLayout. Il suo utilizzo è quando vogliamo disporre le View in una struttura a griglia rettangolare. Questo può essere utile, ad esempio, per rendere i dati presentati all'utente dell'app più facili da consultare. GridLayout, per sua natura, consente di configurare le dimensioni della griglia per raggiungere questo obiettivo; ad esempio, possiamo avere una griglia 4x4 o una griglia 3x2.
RecyclerView.ViewHolder
Il ViewHolder è un assolutotracLa classe t che estendiamo anche da RecyclerView. Il ViewHolder ci fornisce metodi comuni per aiutarci a fare riferimento a una View che abbiamo posizionato su RecyclerView, anche dopo che il meccanismo di riciclo in RecyclerView ha modificato vari riferimenti di cui non siamo a conoscenza.
Elenchi di grandi dimensioni
Le RecyclerView vengono utilizzate quando vogliamo presentare un set davvero ampio di View all'utente, senza esaurire le risorse. RAM sul nostro dispositivo per ogni singola istanza della vista creata.
Se prendiamo come esempio un elenco di contatti, avremmo un'idea generale di come apparirebbe un contatto nell'elenco. Quello che faremmo quindi è creare un layout modello, che in realtà è una Vista, con degli slot in cui verranno inseriti i vari dati del nostro elenco di contatti. Di seguito è riportato uno pseudocodice che spiega l'intero scopo:
//OneContactView
<OneContact>
<TextView>{{PlaceHolderForName}}</TextView>
<TextView>{{PlaceHolderForAddress}}</TextView>
<ImageView>{{PlaceHolderForProfilePicture}}</ImageView>
<TextView>{{PlaceHolderForPhoneNumber}}</TextView>
</OneContact>
Avremmo quindi una lista contatti di questo tipo:
<ContactList> </ContactList>
Se stessimo codificando manualmente i contenuti, non avremmo un modo programmatico per aggiungere nuovi contenuti all'elenco senza riscrivere l'app. Fortunatamente per noi, l'aggiunta di una View a un ViewGroup è supportata da un addView(view:View) metodo. Tuttavia, non è così che alla RecyclerView vengono aggiunte le viste figlie.
Nel nostro caso d'uso, avremmo un lungo elenco di contatti. Per ogni contatto nell'elenco, dovremmo creare una OneContactView e popolare i dati al suo interno in modo che corrispondano ai campi della nostra classe Contact. Una volta creata la vista, dovremmo aggiungerla alla RecyclerView per visualizzare 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 un array di contatti. La classe OneContactView contiene degli slot per prelevare i contenuti dalla classe Contact e visualizzarli. Nella RecyclerView, dobbiamo aggiungere delle View affinché possa aiutarci con la sua funzionalità di riciclo.
Il RecyclerView non ci permette di aggiungere una vista, ma ci consente di aggiungere un ViewHolder. Quindi, in questo scenario, abbiamo due elementi che vogliamo collegare ma che non corrispondono. È qui che entra in gioco il nostro adapter. RecyclerView ci fornisce un adapter, molto simile al nostro AmericanToBritishChargerAdapter() da prima che ci ha permesso di convertire il nostro AmericanCharger, che era inutilizzabile con il nostro BritishDevice, in qualcosa di utilizzabile, simile a un adattatore di corrente nella vita reale.
In questo scenario, l'adattatore prenderebbe il nostro array di contatti e la nostra View, e da lì genererebbe i ViewHolder che la RecyclerView è disposta ad accettare.
La RecyclerView fornisce un'interfaccia che possiamo estendere per creare il nostro adattatore tramite la classe RecyclerView.Adapter. All'interno di questo adattatore è presente un modo per creare la classe ViewHolder con cui la RecyclerView desidera interagire. Quindi, ci troviamo nella stessa situazione di prima, ma con un elemento in più: l'adattatore.
Abbiamo un array di contatti, una vista per visualizzare un singolo contatto (OneContactView) e una RecyclerView, ovvero un elenco di viste che fornisce servizi di riciclo ma accetta solo ViewHolder. In questo scenario, ora abbiamo una classe RecyclerView.Adapter, che contiene un metodo per creare ViewHolder al suo interno.
fun createViewHolder(@NonNull parent: ViewGroup, viewType: Int): ViewHolder
Il RecyclerView.ViewHolder è un assolutotracClasse che accetta la nostra View come argomento e la converte in un ViewHolder. Utilizza il pattern wrapper, impiegato per estendere le funzionalità delle classi.
Fondamenti n. 2 – Modello di involucro
Useremo 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 precedente, abbiamo due animali. Se, per caso, volessimo aggiungere un metodo per farli parlare, ma l'autore della libreria non fosse disponibile, potremmo comunque trovare una soluzione. Ciò di cui avremmo bisogno è un wrapper per la nostra classe Animal. Lo faremmo passando l'oggetto Animal come costruttore della nostra classe.
class SpeechPoweredAnimalByWrapper(var myAnimal:Animal){ fun sound(){ myAnimal.sound() } fun speak(){ println("Hello, my name is ${myAnimal.name}") } }
Ora possiamo passare un'istanza di animale a SpeechPoweredAnimalByWrapper. Chiamando il metodo sound() su di essa, verrà chiamato il metodo sound() dell'animale passato. Abbiamo anche un ulteriore metodo speak(), che rappresenta una 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"
Utilizzando questo schema, possiamo prendere le classi e aggiungere funzionalità. Tutto ciò che ci serve è passare un'istanza della classe e i nuovi metodi definiti dal nostro wrapperping classe.
Nel caso sopra, abbiamo utilizzato una classe concreta. È anche possibile implementare la stessa cosa in un astrattotracclasse t. Dovremmo cambiare la classe SpeechPoweredAnimalByWrapper in assolutatract, e abbiamo finito. Cambieremo il nome della classe con qualcosa di più breve per renderlo più leggibile.
abstract class SpeechPowered(var myAnimal:Animal){ fun sound(){ myAnimal.sound() } fun speak(){ println("Hello, my name is ${myAnimal.name}") } }
È la stessa cosa di prima, ma significherebbe qualcosa di diverso. In una classe normale, possiamo avere un'istanza di una classe nello stesso modo in cui abbiamo creato cat1 e dog1. AbstracLe classi t, tuttavia, non sono destinate a essere istanziate, ma sono destinate a estendere altre classi. Quindi come useremmo il nuovo SpeechPowered(var myAnimal:Animal) abstracClasse t? Possiamo usarla creando nuove classi che la estenderanno e, a loro volta, ne acquisiranno la 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 schema utilizzato nel ViewHolder. La classe RecyclerView.ViewHolder è un assolutotracClasse t che aggiunge funzionalità alla View, proprio come abbiamo aggiunto il metodo speak agli animali. La funzionalità aggiunta è ciò che ne consente il funzionamento quando si interagisce con la RecyclerView.
Ecco come creare un OneContactViewHolder da un 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 dispone di un adattatore che ci permette di collegare il nostro array di contatti a ContactsView tramite RecyclerView.
Aggiunta di una vista
Il ViewGroup non si aggiorna automaticamente, ma segue una pianificazione specifica. Sul tuo dispositivo, l'aggiornamento potrebbe avvenire ogni 10 ms o 100 ms; oppure, se scegliamo un valore assurdo, ad esempio 1 minuto, quando aggiungiamo una View a un ViewGroup, vedrai le modifiche 1 minuto dopo, quando il ViewGroup si "aggiorna".
RecyclerView.Recycler
Opera di base n. 3 – Caching
Uno degli esempi migliori di quando aggiorniamo regolarmente la pagina è il browser. Immaginiamo, ad esempio, che il sito che stiamo visitando sia statico e non invii contenuti dinamicamente; dovremmo aggiornare continuamente la pagina per visualizzare le modifiche.
Per questo esempio, immaginiamo che il sito in questione sia Twitter. Avremmo una serie di tweet statici elencati e l'unico modo per vedere i nuovi tweet sarebbe cliccando sul pulsante di aggiornamento per recuperare il contenuto.
Ridisegnare l'intera schermata è ovviamente un'operazione costosa. Immaginate di avere una larghezza di banda limitata con il vostro operatore telefonico e che la vostra lista di tweet contenga molte immagini e video; sarebbe dispendioso dover scaricare nuovamente tutti i contenuti della pagina a ogni aggiornamento.
Avremmo bisogno di un modo per memorizzare i tweet già caricati e garantire che la nostra richiesta successiva possa specificare quali tweet sono già presenti. In questo modo, non scaricherebbe nuovamente tutto, ma solo i nuovi tweet, verificando anche se qualche tweet salvato localmente non è più presente, in modo da poterlo eliminare. Ciò che stiamo descrivendo si chiama caching.
Le informazioni che inviamo al sito web sui contenuti che abbiamo si chiamano metadati. Quindi, in realtà, non stiamo semplicemente dicendo "vogliamo caricare il tuo sito", ma piuttosto "vogliamo caricare il tuo sito, ed ecco alcuni contenuti che abbiamo già salvato dall'ultimo caricamento; per favore, usali per inviarci solo ciò che non è già presente, in modo da non consumare troppa banda".
Chiamate di layout: la Tweet List deve essere pazzesca
Un esempio di chiamata di layout è `scrollToPosition`. Questo è un esempio comune presente in applicazioni come le chat. Se qualcuno in una conversazione risponde a una bolla di chat precedente, alcune app di chat includono la risposta e un link alla bolla di chat, che cliccandoci sopra ti riporta al punto in cui si trovava il messaggio originale.
Nel caso in cui chiamiamo questo metodo prima di aver aggiunto un LayoutManager alla nostra RecyclerView e prima di avere un RecyclerView.Adapter, scrollToPosition(n:Int) viene semplicemente ignorato.
Comunicazione tra i componenti RecyclerView
Preparazione n. 4 – Richiami
Il RecyclerView, nel suo funzionamento, è composto da molti elementi interconnessi. Deve interagire con il LayoutManager, che ci indica come organizzare le View, in modo lineare o a griglia. Deve inoltre interagire con un adapter che si occupa di convertire i nostri elementi (contactList) in View (OneContactView) e successivamente in ViewHolder (OneContactViewHolder) con cui il RecyclerView può interagire utilizzando i metodi che mette a disposizione.
La materia prima per RecyclerView è costituita dalle nostre View, ad esempio OneContactView, e da un'origine dati.
contactList:Array<Contact>
Abbiamo utilizzato uno scenario semplice come punto di partenza per comprendere meglio lo scopo di RecyclerView. Un caso base, in cui abbiamo un array statico di 1000 contatti da mostrare all'utente, è facile da capire. Il meccanismo di RecyclerView entra davvero in gioco quando l'elenco non è più statico. Con un elenco dinamico, dobbiamo considerare cosa succede alla View sullo schermo quando aggiungiamo o rimuoviamo un elemento dall'elenco.
RecyclerView.LayoutManager
Oltre a decidere come sono disposte le nostre viste, in modo lineare o a griglia, il LayoutManager svolge un sacco di lavoro dietro le quinte per aiutartiping Chi si occupa del riciclaggio sa quando è il momento di effettuare il riciclaggio.
È responsabile della manutenzioneping tracIl LayoutManager gestisce le View attualmente visibili sullo schermo e comunica queste informazioni al meccanismo di riciclo. Quando l'utente scorre verso il basso, il LayoutManager si occupa di informare il sistema di riciclo delle View che perdono il focus nella parte superiore dello schermo, in modo che possano essere riutilizzate, invece di rimanere lì consumando memoria o essere distrutte solo per crearne di nuove.
Ciò significa che il LayoutManager deve mantenere track della posizione dell'utente mentre scorre la nostra lista. Lo fa utilizzando un elenco di posizioni basato su un indice, ovvero il primo elemento parte da 0 e aumenta fino a corrispondere al numero di elementi nella nostra lista.
Se possiamo visualizzare 10 elementi su un elenco di, diciamo, 100, all'inizio il LayoutManager sa di avere a fuoco tutti gli elementi, da View-0 a View-9. Man mano che scorriamo, il LayoutManager è in grado di calcolare quali elementi perdono il focus.
Il LayoutManager è in grado di rilasciare queste viste al meccanismo di riciclo in modo che possano essere riutilizzate (è possibile associarvi nuovi dati; ad esempio, i dati di contatto di una vista possono essere rimossi e i nuovi dati di contatto del segmento successivo possono sostituire i segnaposto).
Questo sarebbe un caso ideale se l'elenco in nostro possesso fosse statico, ma uno degli utilizzi più comuni di una RecyclerView riguarda gli elenchi dinamici, in cui i dati possono provenire da un endpoint online o persino da un sensore. Non solo vengono aggiunti dati, ma a volte i dati presenti nell'elenco vengono rimossi o aggiornati.
La natura dinamica dei nostri dati può rendere molto difficile ragionare sul LayoutManager. Per questo motivo, il LayoutManager mantiene un proprio elenco di elementi e posizioni, separato dall'elenco utilizzato dal componente di riciclo. Ciò garantisce che il layout venga eseguito correttamente.
Allo stesso tempo, il LayoutManager di RecyclerView non vuole rappresentare in modo errato i dati in suo possesso. Per funzionare correttamente, il LayoutManager si sincronizza con RecyclerView.Adapter a intervalli prestabiliti (60 ms), condividendo informazioni sugli elementi della lista, ovvero elementi aggiunti, aggiornati, rimossi o spostati da una posizione all'altra. Il LayoutManager, una volta ricevute queste informazioni, riorganizza il contenuto sullo schermo per adattarlo alle modifiche, quando necessario.
Molte delle operazioni principali che riguardano RecyclerView ruotano attorno alla comunicazione tra RecyclerView.LayoutManager e RecyclerView.Adapter, che memorizza i nostri elenchi di dati, a volte statici, a volte dinamici.
Inoltre, RecyclerView ci offre metodi che possiamo utilizzare per intercettare gli eventi, come ad esempio onBindViewHolder, quando il nostro RecyclerView.Adapter associa il contenuto della nostra lista (ad esempio un contatto) a un ViewHolder in modo che venga utilizzato per visualizzare le informazioni sullo schermo.
Un altro è onCreateViewHolder, che ci indica quando RecyclerView.Adapter prende una View normale come OneContactView e la converte in un elemento ViewHolder con cui RecyclerView può interagire.
Oltre ai meccanismi principali che consentono il riciclo, RecyclerView offre modi per personalizzare il comportamento senza influire sul riciclo stesso. Il riutilizzo delle View rende difficile eseguire operazioni comuni a cui siamo abituati con le View statiche, come ad esempio reagire agli eventi onClick.
Come sappiamo, il RecyclerView.LayoutManager che presenta le visualizzazioni all'utente potrebbe per un momento avere un elenco di elementi diverso da quello RecyclerView.Adapter che ha l'elenco memorizzato in un database o in streaming da una sorgente. Inserire gli eventi OnClick direttamente nelle View può portare a comportamenti imprevisti, come l'eliminazione 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 per sincronizzare i cambiamenti. Ecco come il nostro Gradle Il file apparirà 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 disponiamo di un solo file di layout. Inizieremo con un semplice esempio in cui utilizziamo una RecyclerView per visualizzare un elenco di nomi di frutta sullo schermo.
Elenco degli articoli
Ci sposteremo nel file MainActivity e creeremo un array contenente i nomi dei frutti, subito prima del metodo onCreate() generato durante la fase di 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à quello di presentare questo elenco sullo schermo utilizzando una RecyclerView. Per fare ciò, ci sposteremo nella directory dei layout che contiene i nostri layout e creeremo una View che si occuperà di visualizzare 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>
Nella TextView qui sopra, abbiamo aggiunto un campo ID che verrà utilizzato per identificare una View. Questo campo non viene generato automaticamente. Abbiamo assegnato alla nostra TextView l'ID fruitName per associarlo ai dati che verranno collegati ad essa.
Aggiunta di RecyclerView al layout principale
Nella stessa attività, è presente un file di layout main_layout.xml che è stato generato per noi per impostazione predefinita. Se abbiamo scelto un progetto vuoto, verrà generato un XML Contiene un ConstraintLayout e al suo interno una TextView con il testo "Hello". Elimineremo tutto il contenuto e faremo in modo che il layout contenga solo la RecyclerView, come mostrato 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 inoltre aggiunto un attributo id per la RecyclerView, che useremo per farvi riferimento nel nostro codice.
android:id="@+id/fruitRecyclerView"
Successivamente torneremo 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 alla RecyclerView utilizzando il metodo findViewById() fornito da AndroidLo faremo nel nostro metodo onCreate(). Il nostro metodo onCreate() sarà simile al seguente.
override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContentView(R.layout.activity_main) var myFruitRecyclerView: RecyclerView = findViewById(R.id.fruitRecyclerView) }
Crea un ViewHolder
In seguito, creeremo un RecyclerView.ViewHolder, che si occuperà di convertire la nostra View in un ViewHolder, che la RecyclerView utilizzerà per visualizzare i nostri elementi. Lo faremo subito dopo il nostro 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. La classe FruitArrayAdapter che creeremo si occuperà di quanto segue: prenderà i nomi dei frutti dall'array, creerà un ViewHolder utilizzando la nostra vista one_fruit_view.xml, quindi assocerà i frutti al ViewHolder e assocerà dinamicamente il contenuto alla View che abbiamo creato.
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 Verranno aggiunte delle linee ondulate rosse al nostro FruitArrayAdapter, indicandoci che dobbiamo implementare dei metodi che la RecyclerView possa utilizzare per connettere il nostro array a un ViewHolder.
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, ovvero il metodo getItemCount(). Sappiamo come ottenere il numero di elementi nel nostro array richiamando la proprietà size dell'array.
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) { } }
Successivamente implementeremo il metodo onCreateViewHolder. È qui che RecyclerView ci chiede di aiutarla a costruire un FruitViewHolder. Se ricordate, la nostra classe FruitViewHolder aveva questo aspetto:
class FruitViewHolder(fruitView: View): RecyclerView.ViewHolder(fruitView)
Richiede la nostra fruitView, che abbiamo creato come file XML one_fruit_view.xml. Possiamo creare un riferimento a questo XML e convertirlo in una View 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) { } }
La parte rimanente è l'override di onBindViewHolder.
fun onBindViewHolder(holder: FruitViewHolder, position: Int)
Il componente RecyclerView.Adapter richiede un valore intero per la posizione, che utilizziamo per recuperare un elemento dalla nostra lista. Ci fornisce anche un ViewHolder in modo da poter associare l'elemento ottenuto da fruitArray alla View contenuta al suo interno. La View contenuta in un ViewHolder è accessibile tramite il campo ViewHolder.itemView. Una volta ottenuta la View, possiamo utilizzare l'ID fruitName creato in precedenza per impostarne 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 si presenta 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 a collegare le parti rimanenti del nostro RecyclerView, ovvero la creazione di un LayoutManager che indicherà al RecyclerView come visualizzare il contenuto dell'elenco, se mostrarlo 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 il 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 dei nomi dei frutti. E in pratica, abbiamo finito. Il file MainActivity.kt completo si presenta come segue.
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) } } }




