Android Kotlin Fundamentals 07.1: RecyclerView fundamentals

Questo codelab fa parte del corso Android Kotlin Fundamentals. Per ottenere il massimo valore da questo corso, ti consigliamo di seguire le codelab in sequenza. Tutti i codelab del corso sono elencati nella pagina di destinazione dei codelab Android Kotlin Fundamentals.

Introduzione

Questo codelab ti insegna a utilizzare un RecyclerView per visualizzare elenchi di elementi. Partendo dall'app di monitoraggio del sonno della serie precedente di codelab, imparerai un modo migliore e più versatile per visualizzare i dati, utilizzando un RecyclerView con un'architettura consigliata.

Cosa devi già sapere

Devi avere familiarità con:

  • Creazione di un'interfaccia utente (UI) di base utilizzando un'attività, frammenti e visualizzazioni.
  • Spostarsi tra i fragment e utilizzare safeArgs per passare i dati tra i fragment.
  • Utilizzo di modelli di visualizzazione, fabbriche di modelli di visualizzazione, trasformazioni e LiveData e dei relativi osservatori.
  • Creazione di un database Room, creazione di un DAO e definizione delle entità.
  • Utilizzo di coroutine per attività di database e altre attività di lunga durata.

Obiettivi didattici

  • Come utilizzare un RecyclerView con un Adapter e un ViewHolder per visualizzare un elenco di elementi.

In questo lab proverai a:

  • Modifica l'app TrackMySleepQuality della lezione precedente in modo che utilizzi un RecyclerView per visualizzare i dati sulla qualità del sonno.

In questo codelab, creerai la parte RecyclerView di un'app che monitora la qualità del sonno. L'app utilizza un database Room per archiviare i dati del sonno nel tempo.

L'app di monitoraggio del sonno iniziale ha due schermate, rappresentate da frammenti, come mostrato nella figura seguente.

La prima schermata, mostrata a sinistra, ha pulsanti per avviare e interrompere il monitoraggio. Questa schermata mostra anche tutti i dati sul sonno dell'utente. Il pulsante Cancella elimina definitivamente tutti i dati raccolti dall'app per l'utente. La seconda schermata, mostrata a destra, serve per selezionare una valutazione della qualità del sonno.

Questa app utilizza un'architettura semplificata con un controller UI, ViewModel e LiveData. L'app utilizza anche un database Room per rendere persistenti i dati del sonno.

L'elenco delle notti di sonno visualizzato nella prima schermata è funzionale, ma non bello. L'app utilizza un formattatore complesso per creare stringhe di testo per la visualizzazione del testo e numeri per la qualità. Inoltre, questo design non è scalabile. Dopo aver risolto tutti questi problemi in questo codelab, l'app finale ha la stessa funzionalità e la schermata principale ha questo aspetto:

La visualizzazione di un elenco o di una griglia di dati è una delle attività più comuni dell'interfaccia utente in Android. Gli elenchi variano da semplici a molto complessi. Un elenco di visualizzazioni di testo potrebbe mostrare dati semplici, ad esempio una lista della spesa. Un elenco complesso, ad esempio un elenco annotato di destinazioni per le vacanze, potrebbe mostrare all'utente molti dettagli all'interno di una griglia scorrevole con intestazioni.

Per supportare tutti questi casi d'uso, Android fornisce il widget RecyclerView.

Il vantaggio principale di RecyclerView è che è molto efficiente per gli elenchi di grandi dimensioni:

  • Per impostazione predefinita, RecyclerView elabora o disegna solo gli elementi attualmente visibili sullo schermo. Ad esempio, se il tuo elenco ha mille elementi, ma solo 10 sono visibili, RecyclerView esegue solo il lavoro necessario per disegnare 10 elementi sullo schermo. Quando l'utente scorre, RecyclerView calcola quali nuovi elementi devono essere visualizzati sullo schermo e svolge il lavoro necessario per visualizzarli.
  • Quando un elemento scorre fuori dallo schermo, le visualizzazioni dell'elemento vengono riciclate. Ciò significa che l'elemento è pieno di nuovi contenuti che scorrono sullo schermo. Questo comportamento di RecyclerView consente di risparmiare molto tempo di elaborazione e di scorrere gli elenchi in modo fluido.
  • Quando un elemento cambia, anziché ridisegnare l'intero elenco, RecyclerView può aggiornare solo quell'elemento. Si tratta di un enorme aumento dell'efficienza quando vengono visualizzati elenchi di elementi complessi.

Nella sequenza mostrata di seguito, puoi vedere che una visualizzazione è stata compilata con i dati, ABC. Dopo che la visualizzazione scorre fuori dallo schermo, RecyclerView la riutilizza per i nuovi dati, XYZ.

Il pattern dell'adattatore

Se viaggi spesso in paesi con prese elettriche diverse, probabilmente sai come collegare i tuoi dispositivi alle prese utilizzando un adattatore. L'adattatore consente di convertire un tipo di spina in un altro, ovvero di convertire un'interfaccia in un'altra.

Il pattern dell'adattatore nell'ingegneria del software aiuta un oggetto a funzionare con un'altra API. RecyclerView utilizza un adattatore per trasformare i dati delle app in un formato che RecyclerView può visualizzare, senza modificare il modo in cui l'app archivia ed elabora i dati. Per l'app di monitoraggio del sonno, crei un adattatore che adatta i dati del database Room in un formato che RecyclerView sa come visualizzare, senza modificare ViewModel.

Implementare un RecyclerView

Per visualizzare i dati in un RecyclerView, sono necessarie le seguenti parti:

  • Dati da visualizzare.
  • Un'istanza RecyclerView definita nel file di layout, che funge da contenitore per le visualizzazioni.
  • Un layout per una voce di dati.
    Se tutti gli elementi dell'elenco hanno lo stesso aspetto, puoi utilizzare lo stesso layout per tutti, ma non è obbligatorio. Il layout dell'elemento deve essere creato separatamente dal layout del frammento, in modo che sia possibile creare e compilare con i dati una visualizzazione di un elemento alla volta.
  • Un gestore layout.
    Il gestore layout gestisce l'organizzazione (il layout) dei componenti UI in una visualizzazione.
  • Un view holder.
    Il view holder estende la classe ViewHolder. Contiene le informazioni sulla visualizzazione per mostrare un elemento del layout. I segnaposto aggiungono anche informazioni che RecyclerView utilizza per spostare in modo efficiente le visualizzazioni sullo schermo.
  • Un adattatore.
    L'adattatore connette i dati a RecyclerView. Adatta i dati in modo che possano essere visualizzati in un ViewHolder. Un RecyclerView utilizza l'adattatore per capire come visualizzare i dati sullo schermo.

In questa attività, aggiungerai un RecyclerView al file di layout e configurerai un Adapter per esporre i dati sul sonno al RecyclerView.

Passaggio 1: aggiungi RecyclerView con LayoutManager

In questo passaggio, sostituisci ScrollView con RecyclerView nel file fragment_sleep_tracker.xml.

  1. Scarica l'app RecyclerViewFundamentals-Starter da GitHub.
  2. Crea ed esegui l'app. Nota come i dati vengono visualizzati come testo semplice.
  3. Apri il file di layout fragment_sleep_tracker.xml nella scheda Progettazione di Android Studio.
  4. Nel riquadro Struttura dei componenti, elimina ScrollView. Questa azione elimina anche TextView all'interno di ScrollView.
  5. Nel riquadro Tavolozza, scorri l'elenco dei tipi di componenti a sinistra per trovare Contenitori, quindi selezionalo.
  6. Trascina un RecyclerView dal riquadro Palette al riquadro Struttura dei componenti. Inserisci RecyclerView all'interno di ConstraintLayout.

  1. Se si apre una finestra di dialogo che ti chiede se vuoi aggiungere una dipendenza, fai clic su Ok per consentire ad Android Studio di aggiungere la dipendenza recyclerview al file Gradle. Potrebbero essere necessari alcuni secondi, dopodiché l'app si sincronizza.

  1. Apri il file build.gradle del modulo, scorri fino alla fine e prendi nota della nuova dipendenza, che ha un aspetto simile al codice riportato di seguito:
implementation 'androidx.recyclerview:recyclerview:1.0.0'
  1. Torna a fragment_sleep_tracker.xml.
  2. Nella scheda Testo, cerca il codice RecyclerView mostrato di seguito:
<androidx.recyclerview.widget.RecyclerView
   android:layout_width="match_parent"
   android:layout_height="match_parent" />
  1. Assegna a RecyclerView un id di sleep_list.
android:id="@+id/sleep_list"
  1. Posiziona RecyclerView in modo che occupi la parte rimanente dello schermo all'interno di ConstraintLayout. Per farlo, vincola la parte superiore di RecyclerView al pulsante Avvia, la parte inferiore al pulsante Cancella e ciascun lato al contenitore principale. Imposta la larghezza e l'altezza del layout su 0 dp nell'editor del layout o in XML, utilizzando il seguente codice:
    android:layout_width="0dp"
    android:layout_height="0dp"
    app:layout_constraintBottom_toTopOf="@+id/clear_button"
    app:layout_constraintEnd_toEndOf="parent"
    app:layout_constraintStart_toStartOf="parent"
    app:layout_constraintTop_toBottomOf="@+id/stop_button"
  1. Aggiungi un gestore di layout al file XML RecyclerView. Ogni RecyclerView ha bisogno di un gestore del layout che indichi come posizionare gli elementi nell'elenco. Android fornisce un LinearLayoutManager, che per impostazione predefinita dispone gli elementi in un elenco verticale di righe a larghezza intera.
app:layoutManager="androidx.recyclerview.widget.LinearLayoutManager"
  1. Passa alla scheda Design e nota che i vincoli aggiunti hanno causato l'espansione di RecyclerView per riempire lo spazio disponibile.

Passaggio 2: crea il layout dell'elemento di elenco e il segnaposto della visualizzazione di testo

RecyclerView è solo un contenitore. In questo passaggio, crei il layout e l'infrastruttura per gli elementi da visualizzare all'interno di RecyclerView.

Per ottenere un RecyclerView funzionante il più rapidamente possibile, all'inizio utilizzi un elemento di elenco semplificato che mostra solo la qualità del sonno come numero. Per questo, hai bisogno di un segnaposto della visualizzazione, TextItemViewHolder. Hai anche bisogno di una visualizzazione, un TextView, per i dati. In un passaggio successivo, scoprirai di più sui segnaposto della visualizzazione e su come disporre tutti i dati sul sonno.

  1. Crea un file di layout denominato text_item_view.xml. Non importa cosa utilizzi come elemento principale, perché sostituirai il codice del modello.
  2. In text_item_view.xml, elimina tutto il codice fornito.
  3. Aggiungi un TextView con un riempimento di 16dp all'inizio e alla fine e una dimensione del testo di 24sp. Imposta la larghezza in modo che corrisponda a quella del contenitore principale e l'altezza in modo che contenga i contenuti. Poiché questa visualizzazione viene visualizzata all'interno di RecyclerView, non devi inserirla in un ViewGroup.
<?xml version="1.0" encoding="utf-8"?>
<TextView xmlns:android="http://schemas.android.com/apk/res/android"
    android:textSize="24sp"
    android:paddingStart="16dp"
    android:paddingEnd="16dp"
    android:layout_width="match_parent"       
    android:layout_height="wrap_content" />
  1. Apri Util.kt. Scorri fino alla fine e aggiungi la definizione mostrata di seguito, che crea la classe TextItemViewHolder. Inserisci il codice in fondo al file, dopo l'ultima parentesi graffa chiusa. Il codice va inserito in Util.kt perché questo segnaposto della visualizzazione è temporaneo e lo sostituirai in un secondo momento.
class TextItemViewHolder(val textView: TextView): RecyclerView.ViewHolder(textView)
  1. Se richiesto, importa android.widget.TextView e androidx.recyclerview.widget.RecyclerView.

Passaggio 3: crea SleepNightAdapter

L'attività principale nell'implementazione di un RecyclerView è la creazione dell'adattatore. Hai un semplice segnaposto per la visualizzazione degli articoli e un layout per ogni articolo. Ora puoi creare un adattatore. L'adattatore crea un segnaposto della visualizzazione e lo riempie con i dati da visualizzare per RecyclerView.

  1. Nel pacchetto sleeptracker, crea una nuova classe Kotlin denominata SleepNightAdapter.
  2. Fai in modo che la classe SleepNightAdapter estenda RecyclerView.Adapter. La classe si chiama SleepNightAdapter perché adatta un oggetto SleepNight a qualcosa che RecyclerView può utilizzare. L'adattatore deve sapere quale view holder utilizzare, quindi passa TextItemViewHolder. Importa i componenti necessari quando richiesto, quindi visualizzerai un errore perché ci sono metodi obbligatori da implementare.
class SleepNightAdapter: RecyclerView.Adapter<TextItemViewHolder>() {}
  1. Al livello superiore di SleepNightAdapter, crea una variabile listOf SleepNight per contenere i dati.
var data =  listOf<SleepNight>()
  1. In SleepNightAdapter, esegui l'override di getItemCount() per restituire la dimensione dell'elenco delle notti di sonno in data. RecyclerView deve sapere quanti elementi ha l'adattatore da visualizzare e lo fa chiamando getItemCount().
override fun getItemCount() = data.size
  1. In SleepNightAdapter, esegui l'override della funzione onBindViewHolder(), come mostrato di seguito.

    La funzione onBindViewHolder() viene chiamata da RecyclerView per visualizzare i dati di un elemento di elenco nella posizione specificata. Pertanto, il metodo onBindViewHolder() accetta due argomenti: un view holder e una posizione dei dati da associare. Per questa app, il titolare è TextItemViewHolder e la posizione è la posizione nell'elenco.
override fun onBindViewHolder(holder: TextItemViewHolder, position: Int) {
}
  1. In onBindViewHolder(), crea una variabile per un elemento in una determinata posizione nei dati.
 val item = data[position]
  1. Il ViewHolder che hai creato ha una proprietà denominata textView. All'interno di onBindViewHolder(), imposta text di textView sul numero di qualità del sonno. Questo codice mostra solo un elenco di numeri, ma questo semplice esempio ti consente di vedere come l'adattatore inserisce i dati nel view holder e sullo schermo.
holder.textView.text = item.sleepQuality.toString()
  1. In SleepNightAdapter, esegui l'override e implementa onCreateViewHolder(), che viene chiamato quando RecyclerView ha bisogno di un segnaposto della visualizzazione per rappresentare un elemento.

    Questa funzione accetta due parametri e restituisce un ViewHolder. Il parametro parent, ovvero il gruppo di visualizzazione che contiene il segnaposto della visualizzazione, è sempre RecyclerView. Il parametro viewType viene utilizzato quando sono presenti più visualizzazioni nello stesso RecyclerView. Ad esempio, se inserisci un elenco di visualizzazioni di testo, un'immagine e un video nello stesso RecyclerView, la funzione onCreateViewHolder() deve sapere quale tipo di visualizzazione utilizzare.
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): TextItemViewHolder {
}
  1. In onCreateViewHolder(), crea un'istanza di LayoutInflater.

    Il layout inflater sa come creare viste dai layout XML. context contiene informazioni su come gonfiare correttamente la visualizzazione. In un adattatore per una visualizzazione del riciclatore, passi sempre il contesto del gruppo di visualizzazione parent, ovvero RecyclerView.
val layoutInflater = LayoutInflater.from(parent.context)
  1. In onCreateViewHolder(), crea view chiedendo a layoutinflater di gonfiarlo.

    Passa nel layout XML per la visualizzazione e nel gruppo di visualizzazione parent per la visualizzazione. Il terzo argomento, booleano, è attachToRoot. Questo argomento deve essere false, perché RecyclerView aggiunge questo elemento alla gerarchia delle visualizzazioni quando è il momento.
val view = layoutInflater
       .inflate(R.layout.text_item_view, parent, false) as TextView
  1. In onCreateViewHolder(), restituisci un TextItemViewHolder acquistato con view.
return TextItemViewHolder(view)
  1. L'adattatore deve comunicare a RecyclerView quando data è cambiato, perché RecyclerView non sa nulla dei dati. Conosce solo i segnaposto della visualizzazione che l'adattatore gli fornisce.

    Per comunicare a RecyclerView quando i dati visualizzati sono cambiati, aggiungi un setter personalizzato alla variabile data che si trova nella parte superiore della classe SleepNightAdapter. Nel setter, assegna a data un nuovo valore, quindi chiama notifyDataSetChanged() per attivare il ridisegno dell'elenco con i nuovi dati.
var data =  listOf<SleepNight>()
   set(value) {
       field = value
       notifyDataSetChanged()
   }

Passaggio 4: comunica a RecyclerView l'adattatore

RecyclerView deve conoscere l'adattatore da utilizzare per ottenere i view holder.

  1. Apri SleepTrackerFragment.kt.
  2. In onCreateview(), crea un adattatore. Inserisci questo codice dopo la creazione del modello ViewModel e prima dell'istruzione return.
val adapter = SleepNightAdapter()
  1. Associa adapter a RecyclerView.
binding.sleepList.adapter = adapter
  1. Pulisci e ricompila il progetto per aggiornare l'oggetto binding.

    Se continui a visualizzare errori relativi a binding.sleepList o binding.FragmentSleepTrackerBinding, invalida le cache e riavvia. (Seleziona File > Invalida cache / Riavvia.)

    Se esegui l'app ora, non ci sono errori, ma non vedrai alcun dato visualizzato quando tocchi Avvia e poi Interrompi.

Passaggio 5: inserisci i dati nell'adattatore

Finora hai un adattatore e un modo per trasferire i dati dall'adattatore a RecyclerView. Ora devi inserire i dati nell'adattatore da ViewModel.

  1. Apri SleepTrackerViewModel.
  2. Trova la variabile nights, che memorizza tutte le notti di sonno, ovvero i dati da visualizzare. La variabile nights viene impostata chiamando getAllNights() sul database.
  3. Rimuovi private da nights, perché creerai un osservatore che deve accedere a questa variabile. La dichiarazione dovrebbe essere simile a questa:
val nights = database.getAllNights()
  1. Nel pacchetto database, apri SleepDatabaseDao.
  2. Trova la funzione getAllNights(). Tieni presente che questa funzione restituisce un elenco di valori SleepNight come LiveData. Ciò significa che la variabile nights contiene LiveData, che viene aggiornato da Room, e puoi osservare nights per sapere quando cambia.
  3. Apri SleepTrackerFragment.
  4. In onCreateView(), sotto la creazione di adapter, crea un osservatore sulla variabile nights.

    Fornendo viewLifecycleOwner del fragment come proprietario del ciclo di vita, puoi assicurarti che questo osservatore sia attivo solo quando RecyclerView è sullo schermo.
sleepTrackerViewModel.nights.observe(viewLifecycleOwner, Observer {
   })
  1. All'interno dell'observer, ogni volta che ricevi un valore non nullo (per nights), assegna il valore a data dell'adattatore. Ecco il codice completato per l'osservatore e l'impostazione dei dati:
sleepTrackerViewModel.nights.observe(viewLifecycleOwner, Observer {
   it?.let {
       adapter.data = it
   }
})
  1. Crea ed esegui il codice.

    Se l'adattatore funziona, vedrai i numeri della qualità del sonno in un elenco. Lo screenshot a sinistra mostra -1 dopo aver toccato Avvia. Lo screenshot a destra mostra il numero aggiornato della qualità del sonno dopo aver toccato Interrompi e selezionato una valutazione della qualità.

Passaggio 6: scopri come vengono riciclati i segnaposto delle visualizzazioni

RecyclerView Ricicla i segnaposto delle visualizzazioni, il che significa che li riutilizza. Quando una visualizzazione esce dallo schermo, RecyclerView la riutilizza per la visualizzazione che sta per entrare nello schermo.

Poiché questi segnaposto di visualizzazione vengono riciclati, assicurati che onBindViewHolder() imposti o reimposti le personalizzazioni che gli elementi precedenti potrebbero aver impostato su un segnaposto di visualizzazione.

Ad esempio, puoi impostare il colore del testo su rosso nei segnaposto della visualizzazione che contengono valutazioni della qualità inferiori o uguali a 1 e rappresentano un sonno di scarsa qualità.

  1. Nella classe SleepNightAdapter, aggiungi il seguente codice alla fine di onBindViewHolder().
if (item.sleepQuality <= 1) {
   holder.textView.setTextColor(Color.RED) // red
}
  1. Esegui l'app.
  2. Aggiungi alcuni dati sulla scarsa qualità del sonno e il numero è rosso.
  3. Aggiungi valutazioni elevate per la qualità del sonno finché non vedi un numero rosso elevato sullo schermo.

    Poiché RecyclerView riutilizza i segnaposto delle visualizzazioni, alla fine riutilizza uno dei segnaposto rossi per una valutazione di alta qualità. Il punteggio elevato viene visualizzato erroneamente in rosso.

  1. Per risolvere il problema, aggiungi un'istruzione else per impostare il colore nero se la qualità non è inferiore o uguale a uno.

    Con entrambe le condizioni esplicite, il segnaposto della visualizzazione utilizzerà il colore del testo corretto per ogni elemento.
if (item.sleepQuality <= 1) {
   holder.textView.setTextColor(Color.RED) // red
} else {
   // reset
   holder.textView.setTextColor(Color.BLACK) // black
}
  1. Esegui l'app e i numeri dovrebbero sempre avere il colore corretto.

Complimenti! Ora hai un RecyclerView di base completamente funzionante.

In questa attività, sostituisci il semplice segnaposto della visualizzazione con uno che possa visualizzare più dati per una notte di sonno.

Il semplice ViewHolder che hai aggiunto a Util.kt racchiude un TextView in un TextItemViewHolder.

class TextItemViewHolder(val textView: TextView): RecyclerView.ViewHolder(textView)

Allora perché RecyclerView non utilizza direttamente un TextView? Questa riga di codice offre molte funzionalità. Un ViewHolder descrive la visualizzazione di un elemento e i metadati relativi alla sua posizione all'interno di RecyclerView. RecyclerView si basa su questa funzionalità per posizionare correttamente la visualizzazione durante lo scorrimento dell'elenco e per eseguire operazioni interessanti come animare le visualizzazioni quando vengono aggiunti o rimossi elementi in Adapter.

Se RecyclerView deve accedere alle viste archiviate in ViewHolder, può farlo utilizzando la proprietà itemView del titolare della vista. RecyclerView utilizza itemView quando associa un elemento da visualizzare sullo schermo, quando disegna decorazioni intorno a una visualizzazione come un bordo e per l'implementazione dell'accessibilità.

Passaggio 1: crea il layout dell'elemento

In questo passaggio, crei il file di layout per un elemento. Il layout è costituito da un ConstraintLayout con un ImageView per la qualità del sonno, un TextView per la durata del sonno e un TextView per la qualità come testo. Poiché hai già creato layout, copia e incolla il codice XML fornito.

  1. Crea un nuovo file di risorse di layout e chiamalo list_item_sleep_night.
  2. Sostituisci tutto il codice nel file con il codice riportato di seguito. Poi acquisisci familiarità con il layout che hai appena creato.
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout 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:layout_width="match_parent"
   android:layout_height="wrap_content">

   <ImageView
       android:id="@+id/quality_image"
       android:layout_width="@dimen/icon_size"
       android:layout_height="60dp"
       android:layout_marginStart="16dp"
       android:layout_marginTop="8dp"
       android:layout_marginBottom="8dp"
       app:layout_constraintBottom_toBottomOf="parent"
       app:layout_constraintStart_toStartOf="parent"
       app:layout_constraintTop_toTopOf="parent"
       tools:srcCompat="@drawable/ic_sleep_5" />

   <TextView
       android:id="@+id/sleep_length"
       android:layout_width="0dp"
       android:layout_height="20dp"
       android:layout_marginStart="8dp"
       android:layout_marginTop="8dp"
       android:layout_marginEnd="16dp"
       app:layout_constraintEnd_toEndOf="parent"
       app:layout_constraintStart_toEndOf="@+id/quality_image"
       app:layout_constraintTop_toTopOf="@+id/quality_image"
       tools:text="Wednesday" />

   <TextView
       android:id="@+id/quality_string"
       android:layout_width="0dp"
       android:layout_height="20dp"
       android:layout_marginTop="8dp"
       app:layout_constraintEnd_toEndOf="@+id/sleep_length"
       app:layout_constraintHorizontal_bias="0.0"
       app:layout_constraintStart_toStartOf="@+id/sleep_length"
       app:layout_constraintTop_toBottomOf="@+id/sleep_length"
       tools:text="Excellent!!!" />
</androidx.constraintlayout.widget.ConstraintLayout>
  1. Passa alla scheda Design in Android Studio. Nella visualizzazione Progettazione, il layout è simile a quello dello screenshot a sinistra riportato di seguito. Nella visualizzazione Progetto, l'aspetto è simile a quello dello screenshot a destra.

Passaggio 2: crea ViewHolder

  1. Apri SleepNightAdapter.kt.
  2. Crea una classe all'interno di SleepNightAdapter denominata ViewHolder e falla estendere a RecyclerView.ViewHolder.
class ViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView){}
  1. All'interno di ViewHolder, ottieni riferimenti alle visualizzazioni. Devi fare riferimento alle visualizzazioni che verranno aggiornate da questo ViewHolder. Ogni volta che esegui il binding di questo ViewHolder, devi accedere all'immagine e a entrambe le visualizzazioni di testo. (Convertirai questo codice per utilizzare il data binding in un secondo momento.)
val sleepLength: TextView = itemView.findViewById(R.id.sleep_length)
val quality: TextView = itemView.findViewById(R.id.quality_string)
val qualityImage: ImageView = itemView.findViewById(R.id.quality_image)

Passaggio 3: utilizza ViewHolder in SleepNightAdapter

  1. Nella definizione di SleepNightAdapter, utilizza SleepNightAdapter.ViewHolder che hai appena creato anziché TextItemViewHolder.
class SleepNightAdapter: RecyclerView.Adapter<SleepNightAdapter.ViewHolder>() {

Aggiornamento onCreateViewHolder():

  1. Modifica la firma di onCreateViewHolder() per restituire ViewHolder.
  2. Modifica il layout inflator in modo che utilizzi la risorsa di layout corretta, list_item_sleep_night.
  3. Rimuovi la trasmissione a TextView.
  4. Anziché restituire un TextItemViewHolder, restituisci un ViewHolder.

    Ecco la funzione onCreateViewHolder() aggiornata:
    override fun onCreateViewHolder(
            parent: ViewGroup, viewType: Int): ViewHolder {
        val layoutInflater = 
            LayoutInflater.from(parent.context)
        val view = layoutInflater
                .inflate(R.layout.list_item_sleep_night, 
                         parent, false)
        return ViewHolder(view)
    }

Aggiornamento onBindViewHolder():

  1. Modifica la firma di onBindViewHolder() in modo che il parametro holder sia un ViewHolder anziché un TextItemViewHolder.
  2. All'interno di onBindViewHolder(), elimina tutto il codice, ad eccezione della definizione di item.
  3. Definisci un val res che contenga un riferimento al resources per questa visualizzazione.
val res = holder.itemView.context.resources
  1. Imposta il testo della visualizzazione di testo sleepLength sulla durata. Copia il codice riportato di seguito, che chiama una funzione di formattazione fornita con il codice iniziale.
holder.sleepLength.text = convertDurationToFormatted(item.startTimeMilli, item.endTimeMilli, res)
  1. Viene visualizzato un errore perché è necessario definire convertDurationToFormatted(). Apri Util.kt e rimuovi il commento dal codice e dalle importazioni associate. (Seleziona Codice > Commenta con commenti di riga.)
  2. In onBindViewHolder(), utilizza convertNumericQualityToString() per impostare la qualità.
holder.quality.text= convertNumericQualityToString(item.sleepQuality, res)
  1. Potrebbe essere necessario importare manualmente queste funzioni.
import com.example.android.trackmysleepquality.convertDurationToFormatted
import com.example.android.trackmysleepquality.convertNumericQualityToString
  1. Imposta l'icona corretta per la qualità. La nuova icona ic_sleep_active è fornita nel codice iniziale.
holder.qualityImage.setImageResource(when (item.sleepQuality) {
   0 -> R.drawable.ic_sleep_0
   1 -> R.drawable.ic_sleep_1
   2 -> R.drawable.ic_sleep_2
   3 -> R.drawable.ic_sleep_3
   4 -> R.drawable.ic_sleep_4
   5 -> R.drawable.ic_sleep_5
   else -> R.drawable.ic_sleep_active
})
  1. Ecco la funzione onBindViewHolder() aggiornata e completata, che imposta tutti i dati per ViewHolder:
   override fun onBindViewHolder(holder: ViewHolder, position: Int) {
        val item = data[position]
        val res = holder.itemView.context.resources
        holder.sleepLength.text = convertDurationToFormatted(item.startTimeMilli, item.endTimeMilli, res)
        holder.quality.text= convertNumericQualityToString(item.sleepQuality, res)
        holder.qualityImage.setImageResource(when (item.sleepQuality) {
            0 -> R.drawable.ic_sleep_0
            1 -> R.drawable.ic_sleep_1
            2 -> R.drawable.ic_sleep_2
            3 -> R.drawable.ic_sleep_3
            4 -> R.drawable.ic_sleep_4
            5 -> R.drawable.ic_sleep_5
            else -> R.drawable.ic_sleep_active
        })
    }
  1. Esegui l'app. Il display dovrebbe essere simile allo screenshot riportato di seguito, che mostra l'icona della qualità del sonno, insieme al testo relativo alla durata e alla qualità del sonno.

La tua RecyclerView è stata completata. Hai imparato a implementare un Adapter e un ViewHolder e li hai combinati per visualizzare un elenco con un RecyclerView Adapter.

Il codice finora mostra il processo di creazione di un adattatore e di un view holder. Tuttavia, puoi migliorare questo codice. Il codice da visualizzare e il codice per gestire i segnaposto della visualizzazione sono confusi e onBindViewHolder() conosce i dettagli su come aggiornare ViewHolder.

In un'app di produzione, potresti avere più titolari di visualizzazione, adattatori più complessi e più sviluppatori che apportano modifiche. Devi strutturare il codice in modo che tutto ciò che riguarda un view holder si trovi solo al suo interno.

Passaggio 1: esegui il refactoring di onBindViewHolder()

In questo passaggio, esegui il refactoring del codice e sposti tutte le funzionalità del segnaposto della visualizzazione in ViewHolder. Lo scopo di questo refactoring non è quello di modificare l'aspetto dell'app per l'utente, ma di rendere più facile e sicuro il lavoro degli sviluppatori sul codice. Fortunatamente, Android Studio dispone di strumenti utili.

  1. In SleepNightAdapter, in onBindViewHolder(), seleziona tutto tranne l'istruzione per dichiarare la variabile item.
  2. Fai clic con il tasto destro del mouse, poi seleziona Refactor > Extract > Function.
  3. Assegna alla funzione il nome bind e accetta i parametri suggeriti. Fai clic su Ok.

    La funzione bind() è posizionata sotto onBindViewHolder().
    private fun bind(holder: ViewHolder, item: SleepNight) {
        val res = holder.itemView.context.resources
        holder.sleepLength.text = convertDurationToFormatted(item.startTimeMilli, item.endTimeMilli, res)
        holder.quality.text = convertNumericQualityToString(item.sleepQuality, res)
        holder.qualityImage.setImageResource(when (item.sleepQuality) {
            0 -> R.drawable.ic_sleep_0
            1 -> R.drawable.ic_sleep_1
            2 -> R.drawable.ic_sleep_2
            3 -> R.drawable.ic_sleep_3
            4 -> R.drawable.ic_sleep_4
            5 -> R.drawable.ic_sleep_5
            else -> R.drawable.ic_sleep_active
        })
    }
  1. Posiziona il cursore sulla parola holder del parametro holder di bind(). Premi Alt+Enter (Option+Enter su Mac) per aprire il menu Intenzione. Seleziona Converti parametro in ricevitore per convertirlo in una funzione di estensione con la seguente firma:
private fun ViewHolder.bind(item: SleepNight) {...}
  1. Taglia e incolla la funzione bind() in ViewHolder.
  2. Rendi pubblico bind().
  3. Se necessario, importa bind() nell'adattatore.
  4. Poiché ora si trova in ViewHolder, puoi rimuovere la parte ViewHolder della firma. Ecco il codice finale per la funzione bind() nella classe ViewHolder.
fun bind(item: SleepNight) {
   val res = itemView.context.resources
   sleepLength.text = convertDurationToFormatted(
           item.startTimeMilli, item.endTimeMilli, res)
   quality.text = convertNumericQualityToString(
           item.sleepQuality, res)
   qualityImage.setImageResource(when (item.sleepQuality) {
       0 -> R.drawable.ic_sleep_0
       1 -> R.drawable.ic_sleep_1
       2 -> R.drawable.ic_sleep_2
       3 -> R.drawable.ic_sleep_3
       4 -> R.drawable.ic_sleep_4
       5 -> R.drawable.ic_sleep_5
       else -> R.drawable.ic_sleep_active
   })
}

Passaggio 2: refactor di onCreateViewHolder

Il metodo onCreateViewHolder() nell'adattatore attualmente gonfia la visualizzazione dalla risorsa di layout per ViewHolder. Tuttavia, l'inflazione non ha nulla a che fare con l'adattatore, ma con il ViewHolder. L'inflazione dovrebbe verificarsi nel ViewHolder.

  1. In onCreateViewHolder(), seleziona tutto il codice nel corpo della funzione.
  2. Fai clic con il tasto destro del mouse, poi seleziona Refactor > Extract > Function.
  3. Assegna alla funzione il nome from e accetta i parametri suggeriti. Fai clic su Ok.
  4. Posiziona il cursore sul nome della funzione from. Premi Alt+Enter (Option+Enter su Mac) per aprire il menu Intenzione.
  5. Seleziona Sposta nell'oggetto complementare. La funzione from() deve trovarsi in un oggetto complementare in modo che possa essere chiamata nella classe ViewHolder, non in un'istanza ViewHolder.
  6. Sposta l'oggetto companion nella classe ViewHolder.
  7. Rendi pubblico from().
  8. In onCreateViewHolder(), modifica l'istruzione return in modo che restituisca il risultato della chiamata a from() nella classe ViewHolder.

    I metodi onCreateViewHolder() e from() completati dovrebbero avere l'aspetto del codice riportato di seguito e il codice dovrebbe essere compilato ed eseguito senza errori.
    override fun onCreateViewHolder(parent: ViewGroup, viewType: 
Int): ViewHolder {
        return ViewHolder.from(parent)
    }
companion object {
   fun from(parent: ViewGroup): ViewHolder {
       val layoutInflater = LayoutInflater.from(parent.context)
       val view = layoutInflater
               .inflate(R.layout.list_item_sleep_night, parent, false)
       return ViewHolder(view)
   }
}
  1. Modifica la firma della classe ViewHolder in modo che il costruttore sia privato. Poiché from() ora è un metodo che restituisce una nuova istanza di ViewHolder, non c'è più motivo per chiamare il costruttore di ViewHolder.
class ViewHolder private constructor(itemView: View) : RecyclerView.ViewHolder(itemView){
  1. Esegui l'app. La tua app dovrebbe essere creata ed eseguita come prima, ovvero il risultato desiderato dopo il refactoring.

Progetto Android Studio: RecyclerViewFundamentals

  • La visualizzazione di un elenco o di una griglia di dati è una delle attività più comuni dell'interfaccia utente in Android. RecyclerView è progettato per essere efficiente anche quando vengono visualizzati elenchi molto grandi.
  • RecyclerView esegue solo il lavoro necessario per elaborare o disegnare gli elementi attualmente visibili sullo schermo.
  • Quando un elemento scorre fuori dallo schermo, le relative visualizzazioni vengono riciclate. Ciò significa che l'elemento è pieno di nuovi contenuti che scorrono sullo schermo.
  • Il pattern dell'adattatore nell'ingegneria del software aiuta un oggetto a funzionare insieme a un'altra API. RecyclerView utilizza un adattatore per trasformare i dati dell'app in un formato visualizzabile, senza dover modificare il modo in cui l'app archivia ed elabora i dati.

Per visualizzare i dati in un RecyclerView, sono necessarie le seguenti parti:

  • RecyclerView
    : per creare un'istanza di RecyclerView, definisci un elemento <RecyclerView> nel file di layout.
  • LayoutManager
    Un RecyclerView utilizza un LayoutManager per organizzare il layout degli elementi nel RecyclerView, ad esempio disponendoli in una griglia o in un elenco lineare.

    Nel <RecyclerView> nel file di layout, imposta l'attributo app:layoutManager sul gestore del layout (ad esempio LinearLayoutManager o GridLayoutManager).

    Puoi anche impostare LayoutManager per un RecyclerView in modo programmatico. Questa tecnica viene trattata in un codelab successivo.
  • Layout per ogni elemento
    Crea un layout per un elemento di dati in un file di layout XML.
  • Adattatore
    : crea un adattatore che prepari i dati e il modo in cui verranno visualizzati in un ViewHolder. Associa l'adattatore al RecyclerView.

    Quando viene eseguito RecyclerView, utilizza l'adattatore per capire come visualizzare i dati sullo schermo.

    L'adattatore richiede l'implementazione dei seguenti metodi:
    getItemCount() per restituire il numero di elementi.
    onCreateViewHolder() per restituire ViewHolder per un elemento nell'elenco.
    onBindViewHolder() per adattare i dati alle visualizzazioni per un elemento nell'elenco.

  • ViewHolder
    Un ViewHolder contiene le informazioni sulla visualizzazione per mostrare un elemento dal layout dell'elemento.
  • Il metodo onBindViewHolder() nell'adattatore adatta i dati alle visualizzazioni. Esegui sempre l'override di questo metodo. In genere, onBindViewHolder() gonfia il layout di un elemento e inserisce i dati nelle visualizzazioni del layout.
  • Poiché RecyclerView non conosce i dati, Adapter deve informare RecyclerView quando questi cambiano. Utilizza notifyDataSetChanged()per comunicare a Adapter che i dati sono stati modificati.

Corso Udacity:

Documentazione per sviluppatori Android:

Questa sezione elenca i possibili compiti a casa per gli studenti che seguono questo codelab nell'ambito di un corso guidato da un insegnante. Spetta all'insegnante:

  • Assegna i compiti, se richiesto.
  • Comunica agli studenti come inviare i compiti.
  • Valuta i compiti a casa.

Gli insegnanti possono utilizzare questi suggerimenti nella misura che ritengono opportuna e sono liberi di assegnare qualsiasi altro compito a casa che ritengono appropriato.

Se stai seguendo questo codelab in autonomia, sentiti libero di utilizzare questi compiti per casa per mettere alla prova le tue conoscenze.

Rispondi a queste domande

Domanda 1

Come vengono visualizzati gli elementi in RecyclerView? Seleziona tutte le opzioni pertinenti.

▢ Mostra gli elementi in un elenco o in una griglia.

▢ Scorri in verticale o in orizzontale.

▢ Scorrono in diagonale sui dispositivi più grandi, come i tablet.

▢ Consente layout personalizzati quando un elenco o una griglia non sono sufficienti per il caso d'uso.

Domanda 2

Quali sono i vantaggi dell'utilizzo di RecyclerView? Seleziona tutte le opzioni pertinenti.

▢ Visualizza in modo efficiente elenchi di grandi dimensioni.

▢ Aggiorna automaticamente i dati.

▢ Riduce al minimo la necessità di aggiornamenti quando un elemento viene aggiornato, eliminato o aggiunto all'elenco.

▢ Riutilizza la visualizzazione che scorre fuori dallo schermo per visualizzare l'elemento successivo che scorre sullo schermo.

Domanda 3

Quali sono alcuni dei motivi per utilizzare gli adattatori? Seleziona tutte le opzioni pertinenti.

▢ La separazione delle competenze semplifica la modifica e il test del codice.

RecyclerView è indipendente dai dati visualizzati.

▢ I livelli di trattamento dati non devono preoccuparsi di come verranno visualizzati i dati.

▢ L'app verrà eseguita più rapidamente.

Domanda 4

Quali delle seguenti affermazioni su ViewHolder sono vere? Seleziona tutte le opzioni pertinenti.

▢ Il layout ViewHolder è definito nei file di layout XML.

▢ Esiste un ViewHolder per ogni unità di dati nel set di dati.

▢ Puoi avere più di un ViewHolder in un RecyclerView.

Adapter associa i dati a ViewHolder.

Inizia la lezione successiva: 7.2: DiffUtil e data binding con RecyclerView