Questo codelab fa parte del corso Android Kotlin Fundamentals. Otterrai il massimo valore da questo corso se lavori in sequenza nei codelab. Tutti i codelab del corso sono elencati nella pagina di destinazione di Android Kotlin Fundamentals.
Introduzione
La maggior parte delle app che utilizza elenchi e griglie che mostrano elementi consente agli utenti di interagire con tali elementi. Toccare un elemento da un elenco e visualizzare i dettagli dell'articolo è un caso d'uso molto comune per questo tipo di interazione. A tal fine, puoi aggiungere dei listener di clic che rispondono ai tocchi degli utenti su alcuni elementi mostrando una visualizzazione dettagliata.
In questo codelab, aggiungi l'interazione con il tuo RecyclerView
, a partire da una versione estesa dell'app di monitoraggio del sonno della precedente serie di codelab.
Informazioni importanti
- Creare un'interfaccia utente di base con attività, frammenti e viste.
- Spostamento tra frammenti e utilizzo di
safeArgs
per trasmettere dati tra frammenti. - Visualizza modelli, fabbriche di modelli, trasformazioni e
LiveData
e i loro osservatori. - Come creare un database
Room
, creare un oggetto Data Access (DAO) e definire le entità. - Come utilizzare le coroutine per i database e altre attività di lunga durata.
- Come implementare un elemento
RecyclerView
di base con un layoutAdapter
,ViewHolder
e un elemento. - Come implementare l'associazione di dati per
RecyclerView
. - Come creare e utilizzare adattatori di associazione per trasformare i dati.
- Come utilizzare
GridLayoutManager
.
Obiettivi didattici
- Come rendere selezionabili gli elementi in
RecyclerView
. Implementare un listener di clic per accedere a una visualizzazione dei dettagli quando viene fatto clic su un elemento.
In questo lab proverai a:
- Crea su una versione estesa dell'app TrackMySleepQualità del codelab precedente di questa serie.
- Aggiungi un listener di clic al tuo elenco e inizia ad ascoltare l'interazione dell'utente. Quando un elemento dell'elenco viene toccato, viene attivata la navigazione verso un frammento con i dettagli dell'elemento selezionato. Il codice di avvio fornisce il codice per il frammento di dettaglio e il codice di navigazione.
L'app di monitoraggio del sonno iniziale ha due schermate, rappresentate da frammenti, come mostrato nella figura che segue.
La prima schermata, mostrata a sinistra, contiene pulsanti per avviare e interrompere il monitoraggio. Sullo schermo sono visualizzati alcuni dati del sonno dell'utente. Il pulsante Cancella elimina definitivamente tutti i dati raccolti dall'app per l'utente. La seconda schermata, mostrata a destra, consente di selezionare una valutazione della qualità del sonno.
Questa app utilizza un'architettura semplificata con un controller UI, un modello vista e LiveData
, oltre a un database Room
per conservare i dati relativi al sonno.
In questo codelab, puoi aggiungere la possibilità di rispondere quando un utente tocca un elemento della griglia e viene visualizzata una schermata dei dettagli come quella riportata di seguito. Il codice per questa schermata (frammento, modello di visualizzazione e navigazione) viene fornito con l'app iniziale e implementerai il meccanismo di gestione dei clic.
Passaggio 1: scarica l'app iniziale
- Scarica il codice RecyclerViewClickHandler-Starter da GitHub e apri il progetto in Android Studio.
- Crea ed esegui l'app di monitoraggio del sonno iniziale.
[Facoltativo] Aggiorna l'app se vuoi utilizzarla dal codelab precedente
Se stai lavorando dall'app iniziale fornita in GitHub per questo codelab, vai al passaggio successivo.
Se vuoi continuare a utilizzare la tua app di monitoraggio del sonno creata nel codelab precedente, segui le istruzioni riportate di seguito per aggiornare l'app esistente in modo che abbia il codice per il frammento della schermata dei dettagli.
- Anche se continui a utilizzare l'app esistente, recupera il codice RecyclerViewClickHandler-Starter di GitHub in modo da poter copiare i file.
- Copia tutti i file nel pacchetto
sleepdetail
. - Copia il file
fragment_sleep_detail.xml
nella cartellalayout
. - Copia i contenuti aggiornati di
navigation.xml
, in modo da aggiungere la navigazione persleep_detail_fragment
. - Nel pacchetto
database
, inSleepDatabaseDao
, aggiungi il nuovo metodogetNightWithId()
:
/**
* Selects and returns the night with given nightId.
*/
@Query("SELECT * from daily_sleep_quality_table WHERE nightId = :key")
fun getNightWithId(key: Long): LiveData<SleepNight>
- In
res/values/strings
, aggiungi la seguente risorsa stringa:
<string name="close">Close</string>
- Pulisci e ricrea l'app per aggiornare l'associazione di dati.
Passaggio 2: controlla il codice per la schermata dei dettagli del sonno
In questo codelab, implementi un gestore dei clic che passa a un frammento che mostra i dettagli della notte di sonno selezionata. Il tuo codice di avvio contiene già il frammento e il grafico di navigazione per questo SleepDetailFragment
, perché è un po' di codice e i frammenti e la navigazione non fanno parte di questo codelab. Acquisisci familiarità con il seguente codice:
- Nell'app, trova il pacchetto
sleepdetail
. Questo pacchetto contiene il frammento, il modello di visualizzazione e la fabbrica dei modelli di visualizzazione per un frammento che mostra i dettagli relativi a una notte di sonno. - Nel pacchetto
sleepdetail
, apri e analizza il codice per ilSleepDetailViewModel
. Questo modello di vista assume la chiave per unSleepNight
e un DAO nel costruttore.
Il corpo del corso contiene codice per recuperare l'elementoSleepNight
per la chiave specificata e la variabilenavigateToSleepTracker
per controllare la navigazione fino all'elementoSleepTrackerFragment
quando viene premuto il pulsante Chiudi.
La funzionegetNightWithId()
restituisceLiveData<SleepNight>
ed è definita nelSleepDatabaseDao
(nel pacchettodatabase
). - Nel pacchetto
sleepdetail
, apri e analizza il codice per ilSleepDetailFragment
. Noterai la configurazione dell'associazione di dati, il modello di visualizzazione e l'osservatore per la navigazione. - Nel pacchetto
sleepdetail
, apri e ispeziona il codice diSleepDetailViewModelFactory
. - Nella cartella del layout, controlla
fragment_sleep_detail.xml
. Osserva la variabilesleepDetailViewModel
definita nel tag<data>
per ottenere i dati da visualizzare in ogni vista dal modello della vista.
Il layout contiene unConstraintLayout
che contiene unImageView
per la qualità del sonno, unTextView
per una valutazione della qualità, unTextView
per la durata del sonno e unButton
per chiudere il frammento di dettaglio. - Apri il file
navigation.xml
. Per lasleep_tracker_fragment
, nota la nuova azione persleep_detail_fragment
.
La nuova azione,action_sleep_tracker_fragment_to_sleepDetailFragment
, è la navigazione dal frammento del tracker del sonno alla schermata dei dettagli.
In questa attività, dovrai aggiornare RecyclerView
in modo che risponda ai tocchi degli utenti mostrando una schermata dei dettagli relativi all'elemento toccato.
Ricevere i clic e gestirli è un'attività composta da due parti: per prima cosa, devi ascoltare e ricevere il clic e determinare su quale elemento è stato fatto clic. Devi poi rispondere al clic con un'azione.
Qual è quindi il punto migliore per aggiungere un listener di clic per questa app?
- Il
SleepTrackerFragment
ospita molte visualizzazioni e, quindi, l'ascolto degli eventi di clic a livello di frammento non indica l'elemento su cui è stato fatto clic. Non ti dirà nemmeno se si è trattato di un elemento su cui è stato fatto clic o di uno degli altri elementi dell'interfaccia utente. - Ascoltando a livello di
RecyclerView
, è difficile capire esattamente su quale elemento dell'elenco ha fatto clic l'utente. - Il modo migliore per ottenere informazioni su un elemento selezionato è l'oggetto
ViewHolder
, poiché rappresenta un elemento dell'elenco.
ViewHolder
è la scelta migliore per ascoltare i clic, ma in genere non è il posto giusto per gestirli. Qual è quindi il miglior modo per gestire i clic?
Adapter
mostra gli elementi di dati nelle viste, per consentirti di gestire i clic nell'adattatore. Tuttavia, il compito dell'adattatore è adattare i dati alla Rete Display, non alla logica dell'app.- Solitamente devi gestire i clic in
ViewModel
, perchéViewModel
ha accesso ai dati e alla logica per determinare cosa deve succedere in risposta al clic.
Passaggio 1: crea un listener di clic e attivalo dal layout degli elementi
- Nella cartella
sleeptracker
, apri SleepNightAdapter.kt. - Alla fine del file, crea una nuova classe del listener,
SleepNightListener
, al livello superiore.
class SleepNightListener() {
}
- All'interno della classe
SleepNightListener
, aggiungi una funzioneonClick()
. Quando viene fatto clic sulla visualizzazione che mostra un elemento dell'elenco, la chiamata chiama questa funzioneonClick()
. La proprietàandroid:onClick
verrà impostata in seguito su questa funzione.
class SleepNightListener() {
fun onClick() =
}
- Aggiungi un argomento di funzione
night
di tipoSleepNight
aonClick()
. La vista sa quale elemento sta visualizzando e quali informazioni devono essere trasmesse per gestire il clic.
class SleepNightListener() {
fun onClick(night: SleepNight) =
}
- Per definire le operazioni di
onClick()
, fornisci un callbackclickListener
nel costruttore diSleepNightListener
e assegnalo aonClick()
.
Assegnare il lambda che gestisce il nome con un nome,clickListener
, è utile per tenerne traccia nel corso della lezione. Il callbackclickListener
necessita solo dinight.nightId
per accedere ai dati dal database. Il corso completato diSleepNightListener
dovrebbe essere simile al seguente codice.
class SleepNightListener(val clickListener: (sleepId: Long) -> Unit) {
fun onClick(night: SleepNight) = clickListener(night.nightId)
}
- Apri list_item_sleep_night.xml.
- All'interno del blocco
data
, aggiungi una nuova variabile per rendere disponibile la classeSleepNightListener
tramite l'associazione di dati. Assegna al nuovo<variable>
unname
dell'elementoclickListener.
Imposta il valoretype
sul nome completo della classecom.example.android.trackmysleepquality.sleeptracker.SleepNightListener
, come mostrato di seguito. Ora puoi accedere alla funzioneonClick()
inSleepNightListener
da questo layout.
<variable
name="clickListener"
type="com.example.android.trackmysleepquality.sleeptracker.SleepNightListener" />
- Per ascoltare i clic su qualsiasi parte di questo elemento dell'elenco, aggiungi l'attributo
android:onClick
aConstraintLayout
.
Imposta l'attributo suclickListener:onClick(sleep)
utilizzando una lambda per l'associazione di dati, come mostrato di seguito:
android:onClick="@{() -> clickListener.onClick(sleep)}"
Passaggio 2: passa il listener di clic al proprietario della vista e all'oggetto dell'associazione
- Apri SleepNightAdapter.kt.
- Modifica il costruttore della classe
SleepNightAdapter
per ricevere unval clickListener: SleepNightListener
. Quando l'adattatore vincolaViewHolder
, dovrà fornirlo con il listener di clic.
class SleepNightAdapter(val clickListener: SleepNightListener):
ListAdapter<SleepNight, SleepNightAdapter.ViewHolder>(SleepNightDiffCallback()) {
- In
onBindViewHolder()
, aggiorna la chiamata aholder.bind()
in modo che passi anche il listener di clic aViewHolder
. Verrà visualizzato un errore del compilatore perché hai aggiunto un parametro alla chiamata funzione.
holder.bind(getItem(position)!!, clickListener)
- Aggiungi il parametro
clickListener
abind()
. Per farlo, posiziona il cursore sull'errore e premiAlt+Enter
(Windows) oOption+Enter
(Mac) sull'errore, come mostrato nello screenshot di seguito.
- All'interno della classe
ViewHolder
, all'interno della funzionebind()
, assegna il listener di clic all'oggettobinding
. Viene visualizzato un errore perché devi aggiornare l'oggetto associazione.
binding.clickListener = clickListener
- Per aggiornare l'associazione di dati, pulisci e ricrea il progetto. Potresti dover invalidare anche le cache. Quindi, hai preso un listener di clic dal costruttore di adattatori e li hai trasmessi fino al proprietario della vista e all'oggetto di associazione.
Passaggio 3: viene visualizzato un toast quando viene toccato un elemento
Ora hai il codice per acquisire un clic, ma non hai implementato cosa succede quando viene toccato un elemento dell'elenco. La risposta più semplice è visualizzare un toast che mostra nightId
quando viene fatto clic su un elemento. In questo modo, quando viene fatto clic su un elemento dell'elenco, viene acquisita e trasmessa l'elemento nightId
corretto.
- Apri SleepTrackerFragment.kt.
- In
onCreateView()
, trova la variabileadapter
. Tieni presente che viene mostrato un errore perché ora è previsto un parametro listener di clic. - Definisci un listener di clic passando un lambda al
SleepNightAdapter
. Questo semplice lambda mostra semplicemente un toast con l'iconanightId
, come mostrato di seguito. Dovrai importareToast
. Di seguito è riportata la definizione aggiornata completa.
val adapter = SleepNightAdapter(SleepNightListener { nightId ->
Toast.makeText(context, "${nightId}", Toast.LENGTH_LONG).show()
})
- Esegui l'app, tocca gli elementi e verifica che mostrino un toast con l'elemento
nightId
corretto. Poiché gli elementi hanno valorinightId
in aumento e l'app mostra per prima cosa la notte più recente, l'elemento con ilnightId
più basso si trova in fondo all'elenco.
In questa attività, cambi il comportamento quando viene fatto clic su un elemento in RecyclerView
; in questo modo, invece di mostrare un toast, l'app passa a un frammento di dettaglio che mostra ulteriori informazioni sulla notte selezionata.
Passaggio 1. Naviga al clic
In questo passaggio, invece di visualizzare solo un toast, devi modificare il lambda del listener di clic in onCreateView()
di SleepTrackerFragment
per passare il nightId
a SleepTrackerViewModel
e attivare la navigazione per SleepDetailFragment
.
Definisci la funzione di gestore dei clic:
- Apri SleepTrackerViewmodel.kt.
- All'interno di
SleepTrackerViewModel
, verso la fine, definisci laonSleepNightClicked()
funzione di gestione dei clic.
fun onSleepNightClicked(id: Long) {
}
- All'interno di
onSleepNightClicked()
, attiva la navigazione impostando_navigateToSleepDetail
sul valore superato diid
della notte di sonno selezionata.
fun onSleepNightClicked(id: Long) {
_navigateToSleepDetail.value = id
}
- Implementa
_navigateToSleepDetail
. Come hai già fatto in precedenza, definisci unprivate MutableLiveData
per lo stato di navigazione. E anche una risorsa pubblicaval
da associare.
private val _navigateToSleepDetail = MutableLiveData<Long>()
val navigateToSleepDetail
get() = _navigateToSleepDetail
- Definisci il metodo da chiamare al termine della navigazione dell'app. Chiamalo
onSleepDetailNavigated()
e imposta il relativo valore sunull
.
fun onSleepDetailNavigated() {
_navigateToSleepDetail.value = null
}
Aggiungi il codice per chiamare il gestore dei clic:
- Apri SleepTrackerFragment.kt e scorri verso il basso fino al codice che crea l'adattatore e definisce
SleepNightListener
per mostrare un toast.
val adapter = SleepNightAdapter(SleepNightListener { nightId ->
Toast.makeText(context, "${nightId}", Toast.LENGTH_LONG).show()
})
- Aggiungi il seguente codice sotto il toast per chiamare un gestore dei clic,
onSleepNighClicked()
, insleepTrackerViewModel
quando viene toccato un elemento. Passa ilnightId
, in modo che il modello di vista sappia quale notte dormire. Questo genera un errore perché non hai ancora definitoonSleepNightClicked()
. Puoi tenere, commentare o eliminare il toast come preferisci.
sleepTrackerViewModel.onSleepNightClicked(nightId)
Aggiungi il codice per osservare i clic:
- Apri SleepTrackerFragment.kt.
- In
onCreateView()
, subito sopra la dichiarazione dimanager
, aggiungi il codice per osservare il nuovonavigateToSleepDetail
LiveData
. QuandonavigateToSleepDetail
cambia, vai allaSleepDetailFragment
, passa allanight
, quindi chiamaonSleepDetailNavigated()
dopo. Poiché hai eseguito questa operazione in precedenza in un codelab precedente, ecco il codice:
sleepTrackerViewModel.navigateToSleepDetail.observe(this, Observer { night ->
night?.let {
this.findNavController().navigate(
SleepTrackerFragmentDirections
.actionSleepTrackerFragmentToSleepDetailFragment(night))
sleepTrackerViewModel.onSleepDetailNavigated()
}
})
- Esegui il codice, fai clic su un elemento e ... l'app si arresta in modo anomalo.
Gestisci i valori null negli adattatori di associazione:
- Esegui di nuovo l'app in modalità di debug. Tocca un elemento e filtra i log per mostrare Errori. Verrà mostrata un'analisi dello stack che include informazioni simili a quelle che seguono.
Caused by: java.lang.IllegalArgumentException: Parameter specified as non-null is null: method kotlin.jvm.internal.Intrinsics.checkParameterIsNotNull, parameter item
Purtroppo, l'analisi dello stack non rende evidente dove viene attivato questo errore. Uno svantaggio dell'associazione di dati è che può rendere più difficile eseguire il debug del codice. L'app si arresta in modo anomalo quando fai clic su un elemento e l'unico nuovo codice viene gestito per il clic.
Tuttavia, con il nuovo meccanismo di gestione dei clic ora è possibile chiamare gli adattatori di associazione con un valore null
per item
. In particolare, all'avvio dell'app, LiveData
inizia come null
, pertanto devi aggiungere controlli null a ogni adattatore.
- In
BindingUtils.kt
, per ciascuno degli adattatori di associazione, modifica il tipo di argomentoitem
in zeroble e aggrega il corpo conitem?.let{...}
. Ad esempio, l'adattatore persleepQualityString
sarà simile a questo. Cambia nello stesso modo anche gli altri adattatori.
@BindingAdapter("sleepQualityString")
fun TextView.setSleepQualityString(item: SleepNight?) {
item?.let {
text = convertNumericQualityToString(item.sleepQuality, context.resources)
}
}
- Esegui l'app. Tocca un elemento per aprire una visualizzazione dei dettagli.
Progetto Android Studio: RecyclerViewClickHandler.
Per fare in modo che gli elementi di un RecyclerView
rispondano ai clic, allega i listener di clic per elencare gli elementi in ViewHolder
e gestisci i clic in ViewModel
.
Per fare in modo che gli elementi di un elemento RecyclerView
rispondano ai clic, è necessario:
- Crea una classe listener che accetta un lambda e lo assegna a una funzione
onClick()
.
class SleepNightListener(val clickListener: (sleepId: Long) -> Unit) {
fun onClick(night: SleepNight) = clickListener(night.nightId)
}
- Imposta il listener di clic sulla vista.
android:onClick="@{() -> clickListener.onClick(sleep)}"
- Passa il listener di clic al costruttore di adattatori e inseriscilo nel visualizzatore, poi aggiungilo all'oggetto di associazione.
class SleepNightAdapter(val clickListener: SleepNightListener):
ListAdapter<DataItem, RecyclerView.ViewHolder>(SleepNightDiffCallback()
holder.bind(getItem(position)!!, clickListener)
binding.clickListener = clickListener
- Nel frammento che mostra la vista del riciclo, in cui crei l'adattatore, definisci un listener di clic passando un lambda all'adattatore.
val adapter = SleepNightAdapter(SleepNightListener { nightId ->
sleepTrackerViewModel.onSleepNightClicked(nightId)
})
- Implementare il gestore dei clic nel modello di visualizzazione. Di solito, per i clic sugli elementi dell'elenco viene attivata la navigazione verso un frammento di dettaglio.
Corso Udacity:
Documentazione per gli sviluppatori Android:
In questa sezione sono elencati i possibili compiti per gli studenti che lavorano attraverso questo codelab nell'ambito di un corso tenuto da un insegnante. Spetta all'insegnante fare quanto segue:
- Assegna i compiti, se necessario.
- Comunica agli studenti come inviare compiti.
- Valuta i compiti.
Gli insegnanti possono utilizzare i suggerimenti solo quanto e come vogliono e dovrebbero assegnare i compiti che ritengono appropriati.
Se stai lavorando da solo a questo codelab, puoi utilizzare questi compiti per mettere alla prova le tue conoscenze.
Rispondi a queste domande
Domanda 1
Supponiamo che la tua app contenga un elemento RecyclerView
che mostra gli articoli in una lista della spesa. La tua applicazione definisce anche una classe di elenco dei clic:
class ShoppingListItemListener(val clickListener: (itemId: Long) -> Unit) {
fun onClick(cartItem: CartItem) = clickListener(cartItem.itemId)
}
Come rendi disponibile ShoppingListItemListener
per l'associazione di dati? Seleziona un'opzione.
▢ nel file di layout contenente l'elemento RecyclerView
che mostra la lista della spesa, aggiungi una variabile <data>
per l'attributo ShoppingListItemListener
.
▢ nel file di layout che definisce il layout di una singola riga della lista della spesa: aggiungi una variabile <data>
per ShoppingListItemListener
.
▢ nel corso ShoppingListItemListener
, aggiungi una funzione per abilitare l'associazione di dati:
fun onBinding (cartItem: CartItem) {dataBindingEnable(true)}
▢ nella classe ShoppingListItemListener
, all'interno della funzione onClick()
, aggiungi una chiamata per attivare l'associazione di dati:
fun onClick(cartItem: CartItem) = {
clickListener(cartItem.itemId)
dataBindingEnable(true)
}
Domanda 2
Dove aggiungi l'attributo android:onClick
per far sì che gli elementi di un elemento RecyclerView
rispondano ai clic? Seleziona tutte le risposte pertinenti.
▢ nel file di layout in cui è visualizzato RecyclerView
, che puoi aggiungere a <androidx.recyclerview.widget.RecyclerView>
▢ Aggiungilo al file di layout di un elemento nella riga. Se vuoi che l'intero elemento sia cliccabile, aggiungilo alla vista principale che contiene gli elementi nella riga.
▢ Aggiungilo al file di layout di un elemento nella riga. Se vuoi che sia possibile fare clic su un singolo TextView
nell'elemento, aggiungilo a <TextView>
.
▢ Aggiungi sempre il file di layout per MainActivity
.
Inizia la lezione successiva: