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
La maggior parte delle app che utilizzano elenchi e griglie che mostrano elementi consente agli utenti di interagire con gli elementi. Toccando un elemento di un elenco e visualizzandone i dettagli, si verifica un caso d'uso molto comune per questo tipo di interazione. Per farlo, puoi aggiungere listener di clic che rispondono ai tocchi degli utenti sugli elementi mostrando una visualizzazione dettagliata.
In questo codelab, aggiungi l'interazione al tuo RecyclerView, basandoti su una versione estesa dell'app per il monitoraggio del sonno della precedente serie di codelab.
Cosa devi già sapere
- Creazione di un'interfaccia utente di base utilizzando un'attività, frammenti e visualizzazioni.
- Spostarsi tra i fragment e utilizzare
safeArgsper passare i dati tra i fragment. - Visualizza modelli, fabbriche di modelli, trasformazioni e
LiveDatae i relativi osservatori. - Come creare un database
Room, creare un oggetto di accesso ai dati (DAO) e definire le entità. - Come utilizzare le coroutine per il database e altre attività di lunga durata.
- Come implementare un
RecyclerViewdi base con un layoutAdapter,ViewHoldere degli elementi. - Come implementare l'associazione di dati per
RecyclerView. - Come creare e utilizzare gli adattatori di binding per trasformare i dati.
- Come utilizzare
GridLayoutManager.
Obiettivi didattici
- Come rendere cliccabili gli elementi in
RecyclerView. Implementa un listener di clic per passare a una visualizzazione dettagliata quando viene fatto clic su un elemento.
In questo lab proverai a:
- Sviluppa una versione estesa dell'app TrackMySleepQuality del codelab precedente di questa serie.
- Aggiungi un listener di clic all'elenco e inizia ad ascoltare l'interazione dell'utente. Quando viene toccato un elemento dell'elenco, viene attivata la navigazione a un frammento con i dettagli dell'elemento su cui è stato fatto clic. Il codice iniziale fornisce il codice per il frammento dei dettagli, nonché il codice di navigazione.
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. La schermata mostra alcuni 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, un modello di visualizzazione e LiveData e un database Room per archiviare i dati sul sonno.

In questo codelab, aggiungi la possibilità di rispondere quando un utente tocca un elemento nella griglia, visualizzando 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 iniziale RecyclerViewClickHandler 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 utilizzare quella del codelab precedente
Se utilizzerai l'app iniziale fornita in GitHub per questo codelab, vai al passaggio successivo.
Se vuoi continuare a utilizzare l'app per il monitoraggio del sonno che hai creato nel codelab precedente, segui le istruzioni riportate di seguito per aggiornare l'app esistente in modo che contenga il codice per il fragment della schermata dei dettagli.
- Anche se continui a utilizzare la tua app esistente, scarica il codice iniziale RecyclerViewClickHandler da GitHub per poter copiare i file.
- Copia tutti i file nel pacchetto
sleepdetail. - Nella cartella
layout, copia il filefragment_sleep_detail.xml. - Copia i contenuti aggiornati di
navigation.xml, che aggiungono 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/stringsaggiungi la seguente risorsa stringa:
<string name="close">Close</string>- Pulisci e ricrea l'app per aggiornare l'associazione di dati.
Passaggio 2: esamina il codice per la schermata dei dettagli del sonno
In questo codelab, implementerai un gestore di clic che passa a un fragment che mostra i dettagli della notte di sonno su cui è stato fatto clic. Il codice iniziale contiene già il fragment e il grafico di navigazione per questo SleepDetailFragment, perché si tratta di una quantità considerevole di codice e i fragment e la navigazione non fanno parte di questo codelab. Familiarizza con il seguente codice:
- Nella tua app, trova il pacchetto
sleepdetail. Questo pacchetto contiene il fragment, il modello di visualizzazione e la relativa factory per un fragment che mostra i dettagli di una notte di sonno. - Nel pacchetto
sleepdetail, apri e controlla il codice perSleepDetailViewModel. Questo modello di visualizzazione accetta la chiave per unSleepNighte un DAO nel costruttore.
Il corpo della classe contiene il codice per ottenere l'SleepNightper la chiave specificata e la variabilenavigateToSleepTrackerper controllare la navigazione di ritorno aSleepTrackerFragmentquando viene premuto il pulsante Chiudi.
La funzionegetNightWithId()restituisce un valoreLiveData<SleepNight>ed è definita inSleepDatabaseDao(nel pacchettodatabase). - Nel pacchetto
sleepdetail, apri e controlla il codice perSleepDetailFragment. Nota la configurazione per il data binding, il modello di visualizzazione e l'observer per la navigazione. - Nel pacchetto
sleepdetail, apri e controlla il codice perSleepDetailViewModelFactory. - Nella cartella del layout, esamina
fragment_sleep_detail.xml. Nota la variabilesleepDetailViewModeldefinita nel tag<data>per visualizzare i dati in ogni visualizzazione dal modello di visualizzazione.
Il layout contiene unConstraintLayoutche contiene unImageViewper la qualità del sonno, unTextViewper una valutazione della qualità, unTextViewper la durata del sonno e unButtonper chiudere il frammento dei dettagli. - Apri il file
navigation.xml. Persleep_tracker_fragment, nota la nuova azione persleep_detail_fragment.
La nuova azione,action_sleep_tracker_fragment_to_sleepDetailFragment, è la navigazione dal frammento del monitoraggio del sonno alla schermata dei dettagli.
In questa attività, aggiorna RecyclerView per rispondere ai tocchi dell'utente mostrando una schermata dei dettagli per l'elemento toccato.
La ricezione e la gestione dei clic è un'attività in due parti: innanzitutto, devi ascoltare e ricevere il clic e determinare su quale elemento è stato fatto clic. Dopodiché, devi rispondere al clic con un'azione.
Qual è quindi il posto migliore per aggiungere un listener di clic per questa app?
- Il
SleepTrackerFragmentospita molte visualizzazioni, quindi l'ascolto degli eventi di clic a livello di frammento non ti dirà su quale elemento è stato fatto clic. Non ti dirà nemmeno se si trattava di un elemento su cui è stato fatto clic o di uno degli altri elementi dell'interfaccia utente. - Se ascolti a livello di
RecyclerView, è difficile capire esattamente su quale elemento dell'elenco ha fatto clic l'utente. - Il ritmo migliore per ottenere informazioni su un elemento su cui è stato fatto clic è nell'oggetto
ViewHolder, poiché rappresenta una voce di elenco.
Sebbene ViewHolder sia un ottimo posto per ascoltare i clic, di solito non è il posto giusto per gestirli. Qual è quindi il posto migliore per gestire i clic?
Adaptermostra gli elementi di dati nelle visualizzazioni, quindi puoi gestire i clic nell'adattatore. Tuttavia, il compito dell'adattatore è adattare i dati per la visualizzazione, non gestire la logica dell'app.- In genere, i clic devono essere gestiti in
ViewModel, perchéViewModelha accesso ai dati e alla logica per determinare cosa deve succedere in risposta al clic.
Passaggio 1: crea un listener di clic e attivarlo dal layout dell'elemento
- Nella cartella
sleeptracker, apri SleepNightAdapter.kt. - Alla fine del file, al primo livello, crea una nuova classe di listener,
SleepNightListener.
class SleepNightListener() {
}- All'interno della classe
SleepNightListener, aggiungi una funzioneonClick(). Quando viene fatto clic sulla visualizzazione che mostra un elemento dell'elenco, la visualizzazione chiama questa funzioneonClick(). In un secondo momento, imposterai la proprietàandroid:onClickdella vista su questa funzione.
class SleepNightListener() {
fun onClick() =
}- Aggiungi un argomento della funzione
nightdi tipoSleepNightaonClick(). La visualizzazione sa quale elemento sta mostrando e queste informazioni devono essere trasmesse per la gestione del clic.
class SleepNightListener() {
fun onClick(night: SleepNight) =
}- Per definire cosa fa
onClick(), fornisci un callbackclickListenernel costruttore diSleepNightListenere assegnaglionClick().
Assegnare un nome alla lambda che gestisce il clic,clickListener, aiuta a tenerne traccia quando viene passata tra le classi. Il callbackclickListenerrichiede solonight.nightIdper accedere ai dati del database. La classeSleepNightListenercompletata dovrebbe avere l'aspetto del codice riportato di seguito.
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 classeSleepNightListenertramite il data binding. Assegna al nuovo<variable>unnamediclickListener.Impostatypesul nome completo della classecom.example.android.trackmysleepquality.sleeptracker.SleepNightListener, come mostrato di seguito. Ora puoi accedere alla funzioneonClick()inSleepNightListenerda questo layout.
<variable
name="clickListener"
type="com.example.android.trackmysleepquality.sleeptracker.SleepNightListener" />- Per rilevare i clic su qualsiasi parte di questo elemento dell'elenco, aggiungi l'attributo
android:onClickaConstraintLayout.
Imposta l'attributo suclickListener:onClick(sleep)utilizzando una lambda di data binding, come mostrato di seguito:
android:onClick="@{() -> clickListener.onClick(sleep)}"Passaggio 2: passa il listener di clic al view holder e all'oggetto di binding
- Apri SleepNightAdapter.kt.
- Modifica il costruttore della classe
SleepNightAdapterper ricevere unval clickListener: SleepNightListener. Quando l'adattatore associaViewHolder, deve fornirgli questo listener dei clic.
class SleepNightAdapter(val clickListener: SleepNightListener):
ListAdapter<SleepNight, SleepNightAdapter.ViewHolder>(SleepNightDiffCallback()) {- In
onBindViewHolder(), aggiorna la chiamata aholder.bind()per passare anche il listener di clic aViewHolder. Riceverai un errore del compilatore perché hai aggiunto un parametro alla chiamata di funzione.
holder.bind(getItem(position)!!, clickListener)- Aggiungi il parametro
clickListenerabind(). 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. Visualizzi un errore perché devi aggiornare l'oggetto di binding.
binding.clickListener = clickListener- Per aggiornare il data binding, Pulisci e Ricompila il progetto. Potrebbe essere necessario invalidare anche le cache. Quindi, hai preso un listener di clic dal costruttore dell'adattatore e lo hai passato fino al view holder e all'oggetto di binding.
Passaggio 3: mostra un messaggio di notifica 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 messaggio di notifica che mostra nightId quando viene fatto clic su un elemento. In questo modo si verifica che quando viene fatto clic su un elemento dell'elenco, venga acquisito e trasmesso il nightId corretto.
- Apri SleepTrackerFragment.kt.
- In
onCreateView(), trova la variabileadapter. Tieni presente che viene visualizzato un errore perché ora è previsto un parametro del listener di clic. - Definisci un listener di clic passando un'espressione lambda a
SleepNightAdapter. Questa semplice lambda mostra un toast connightId, come mostrato di seguito. Devi 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 venga visualizzato un messaggio popup con il valore
nightIdcorretto. Poiché gli elementi hanno valorinightIdcrescenti e l'app mostra prima la notte più recente, l'elemento con il valorenightIdpiù basso si trova in fondo all'elenco.
In questa attività, modifichi il comportamento quando viene fatto clic su un elemento in RecyclerView, in modo che, anziché mostrare un messaggio di notifica, l'app passi a un fragment di dettagli che mostra ulteriori informazioni sulla notte selezionata.
Passaggio 1: navigare con un clic
In questo passaggio, anziché visualizzare solo un messaggio popup, modifichi l'espressione lambda del listener di clic in onCreateView() di SleepTrackerFragment per passare nightId a SleepTrackerViewModel e attivare la navigazione a SleepDetailFragment.
Definisci la funzione di gestione dei clic:
- Apri SleepTrackerViewModel.kt.
- All'interno di
SleepTrackerViewModel, verso la fine, definisci la funzione di gestione dei cliconSleepNightClicked().
fun onSleepNightClicked(id: Long) {
}- All'interno di
onSleepNightClicked(), attiva la navigazione impostando_navigateToSleepDetailsul valoreiddella notte di sonno selezionata.
fun onSleepNightClicked(id: Long) {
_navigateToSleepDetail.value = id
}- Implementa
_navigateToSleepDetail. Come hai fatto in precedenza, definisci unprivate MutableLiveDataper lo stato di navigazione. E unvalpubblico da abbinare.
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 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
SleepNightListenerper mostrare un messaggio di notifica.
val adapter = SleepNightAdapter(SleepNightListener { nightId ->
Toast.makeText(context, "${nightId}", Toast.LENGTH_LONG).show()
})- Aggiungi il seguente codice sotto il messaggio di notifica per chiamare un gestore di clic,
onSleepNighClicked(), insleepTrackerViewModelquando viene toccato un elemento. Passa innightId, in modo che il modello di visualizzazione sappia quale notte di sonno recuperare. Si verifica un errore perché non hai ancora definitoonSleepNightClicked(). Puoi mantenere, commentare o eliminare il messaggio di notifica, a seconda delle tue preferenze.
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 nuovonavigateToSleepDetailLiveData. QuandonavigateToSleepDetailcambia, vai aSleepDetailFragment, passando pernight, quindi chiamaonSleepDetailNavigated(). Poiché l 'hai già fatto 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 binding:
- Esegui di nuovo l'app in modalità di debug. Tocca un elemento e filtra i log per mostrare gli errori. Verrà visualizzata una traccia dello stack che include qualcosa di simile a quanto riportato di seguito.
Caused by: java.lang.IllegalArgumentException: Parameter specified as non-null is null: method kotlin.jvm.internal.Intrinsics.checkParameterIsNotNull, parameter itemPurtroppo, l'analisi dello stack non indica chiaramente dove viene attivato questo errore. Uno svantaggio del data binding è che può rendere più difficile il debug del codice. L'app va in arresto anomalo quando fai clic su un elemento e l'unico nuovo codice è per la gestione del clic.
Tuttavia, si è scoperto che con questo nuovo meccanismo di gestione dei clic, ora è possibile chiamare gli adattatori di binding con un valore null per item. In particolare, all'avvio dell'app, LiveData inizia come null, quindi devi aggiungere controlli null a ciascun adattatore.
- In
BindingUtils.kt, per ciascuno degli adattatori di binding, modifica il tipo dell'argomentoitemin modo che accetti valori nulli e racchiudi il corpo conitem?.let{...}. Ad esempio, l'adattatore persleepQualityStringavrà questo aspetto. Modifica gli altri adattatori allo stesso modo.
@BindingAdapter("sleepQualityString")
fun TextView.setSleepQualityString(item: SleepNight?) {
item?.let {
text = convertNumericQualityToString(item.sleepQuality, context.resources)
}
}- Esegui l'app. Tocca un elemento e si apre una visualizzazione dettagliata.
Progetto Android Studio: RecyclerViewClickHandler.
Per fare in modo che gli elementi di un RecyclerView rispondano ai clic, collega i listener di clic agli elementi dell'elenco nel ViewHolder e gestisci i clic nel ViewModel.
Per fare in modo che gli elementi di un RecyclerView rispondano ai clic, devi:
- Crea una classe di listener che accetta una lambda e la assegna a una funzione
onClick().
class SleepNightListener(val clickListener: (sleepId: Long) -> Unit) {
fun onClick(night: SleepNight) = clickListener(night.nightId)
}- Imposta il listener dei clic sulla visualizzazione.
android:onClick="@{() -> clickListener.onClick(sleep)}"- Passa il listener di clic al costruttore dell'adattatore, al view holder e aggiungilo all'oggetto di binding.
class SleepNightAdapter(val clickListener: SleepNightListener):
ListAdapter<DataItem, RecyclerView.ViewHolder>(SleepNightDiffCallback()holder.bind(getItem(position)!!, clickListener)binding.clickListener = clickListener- Nel frammento che mostra la visualizzazione elenco, in cui crei l'adattatore, definisci un listener di clic passando un'espressione lambda all'adattatore.
val adapter = SleepNightAdapter(SleepNightListener { nightId ->
sleepTrackerViewModel.onSleepNightClicked(nightId)
})- Implementa il gestore dei clic nel modello di visualizzazione. Per i clic sugli elementi di elenco, in genere viene attivata la navigazione a un frammento di dettagli.
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
Supponiamo che la tua app contenga un RecyclerView che mostra gli articoli in una lista della spesa. La tua app definisce anche una classe di listener di clic:
class ShoppingListItemListener(val clickListener: (itemId: Long) -> Unit) {
fun onClick(cartItem: CartItem) = clickListener(cartItem.itemId)
}Come rendi ShoppingListItemListener disponibile al data binding? Seleziona un'opzione.
▢ Nel file di layout che contiene RecyclerView che mostra la lista della spesa, aggiungi una variabile <data> per ShoppingListItemListener.
▢ Nel file di layout che definisce il layout per una singola riga nella lista della spesa, aggiungi una variabile <data> per ShoppingListItemListener.
▢ Nella classe ShoppingListItemListener, aggiungi una funzione per attivare 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 fare in modo che gli elementi di un RecyclerView rispondano ai clic? Seleziona tutte le opzioni pertinenti.
▢ Nel file di layout che mostra RecyclerView, aggiungilo a <androidx.recyclerview.widget.RecyclerView>
▢ Aggiungilo al file di layout per un elemento nella riga. Se vuoi che l'intero elemento sia cliccabile, aggiungilo alla visualizzazione principale che contiene gli elementi nella riga.
▢ Aggiungilo al file di layout per un elemento nella riga. Se vuoi che un singolo TextView nell'elemento sia selezionabile, aggiungilo a <TextView>.
▢ Aggiungi sempre il file di layout per MainActivity.
Inizia la lezione successiva:

