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.

  • ???? Che cosa รจ: RecyclerView รจ un successore flessibile di ListView e GridView per la visualizzazione di grandi insiemi di dati.
  • ๐Ÿงฑ Viste e gruppi di viste: Android Disegna l'interfaccia utilizzando le classi View e ViewGroup, con LayoutParams che controlla le dimensioni e la posizione.
  • ๐Ÿ”„ Adattatore: Il pattern Adapter converte un array di dati in ViewHolder che la RecyclerView puรฒ accettare.
  • ๐Ÿ“ LayoutManager: Il gestore del layout dispone gli elementi in modo lineare o a griglia e segnala quando le viste possono essere riutilizzate.
  • โ™ป๏ธ Riciclaggio: Le viste fuori schermo vengono riutilizzate anzichรฉ ricreate, il che consente di risparmiare memoria e di mantenere uno scorrimento fluido.
  • ๏ธ Passaggi di costruzione: Aggiungere il Gradle dipendenza, crea un layout di elemento, un ViewHolder, un adattatore e un LayoutManager.

Android RiciclatoreVedi

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

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

Scarica il progetto

DOMANDE FREQUENTI

RecyclerView รจ il successore piรน flessibile ed efficiente di ListView. Impone il pattern ViewHolder, supporta layout lineari, a griglia e sfalsati tramite gestori di layout e aggiunge animazioni integrate per gli elementi, funzionalitร  che ListView non offre di default.

GridView visualizza gli elementi solo in una griglia fissa. RecyclerView puรฒ riprodurre una griglia utilizzando GridLayoutManager, ma supporta anche layout lineari e sfalsati, il riciclo delle viste e le animazioni, risultando quindi molto piรน versatile di GridView.

Questo esempio di RecyclerView รจ scritto in Kotlin, il linguaggio ufficialmente raccomandato da Google per Android sviluppo. Le stesse API di RecyclerView, adapter e layout manager funzionano anche in Java.

Aggiungi un listener di clic all'interno del ViewHolder o all'evento onBindViewHolder, quindi esponilo tramite una callback dell'interfaccia all'activity o al fragment. L'utilizzo della posizione del ViewHolder evita di associare i clic all'elemento riciclato sbagliato.

DiffUtil รจ una classe di utilitร  che calcola la differenza tra due liste e invia all'adattatore il set minimo di aggiornamenti. Rende efficienti le modifiche dinamiche alle liste e anima automaticamente aggiunte, rimozioni e spostamenti.

Jetpack Compose utilizza LazyColumn e LazyRow al posto di RecyclerView per le nuove schermate. Tuttavia, RecyclerView rimane ampiamente utilizzato nei progetti esistenti basati su View e puรฒ essere integrato in Compose tramite API di interoperabilitร .

Sรฌ. Gli assistenti basati sull'IA, come GitHub Copilot e gli strumenti basati su GPT, possono generare automaticamente un adattatore RecyclerView, un ViewHolder e il codice boilerplate per onBindViewHolder a partire da un breve prompt. Gli sviluppatori dovrebbero comunque rivedere il codice generato per verificarne la correttezza e le prestazioni.

apprendimento automatico Android Le app utilizzano RecyclerView per visualizzare output dinamici come raccomandazioni, classifiche di ricerca o classificazioni di immagini. L'adattatore associa i risultati del modello agli elementi dell'elenco, consentendo lo scorrimento fluido di set di dati di grandi dimensioni e in continua evoluzione.

Riassumi questo post con: