Principes de base d'Android en Kotlin 07.1 : Principes de base de RecyclerView

Cet atelier de programmation fait partie du cours Principes de base d'Android en Kotlin. Vous tirerez pleinement parti de ce cours en suivant les ateliers de programmation dans l'ordre. Tous les ateliers de programmation du cours sont listés sur la page de destination des ateliers de programmation Principes de base d'Android en Kotlin.

Introduction

Cet atelier de programmation vous explique comment utiliser un RecyclerView pour afficher des listes d'éléments. En vous appuyant sur l'application de suivi du sommeil de la série d'ateliers de programmation précédente, vous allez découvrir une façon plus efficace et polyvalente d'afficher les données à l'aide d'un RecyclerView avec une architecture recommandée.

Ce que vous devez déjà savoir

Vous devez maîtriser les éléments suivants :

  • Créer une interface utilisateur (UI) de base à l'aide d'une activité, de fragments et de vues.
  • Naviguer entre les fragments et utiliser safeArgs pour transmettre des données entre les fragments.
  • Utilisation des ViewModels, des ViewModelFactories, des transformations, de LiveData et de leurs observateurs.
  • Créer une base de données Room, un DAO et définir des entités
  • Utiliser des coroutines pour les tâches de base de données et autres tâches de longue durée.

Points abordés

  • Comment utiliser un RecyclerView avec un Adapter et un ViewHolder pour afficher une liste d'éléments.

Objectifs de l'atelier

  • Modifiez l'application TrackMySleepQuality de la leçon précédente pour qu'elle utilise un RecyclerView afin d'afficher les données sur la qualité du sommeil.

Dans cet atelier de programmation, vous allez créer la partie RecyclerView d'une application qui suit la qualité du sommeil. L'application utilise une base de données Room pour stocker les données de sommeil au fil du temps.

L'application de suivi du sommeil de démarrage comporte deux écrans, représentés par des fragments, comme illustré dans la figure ci-dessous.

Le premier écran, affiché à gauche, comporte des boutons permettant de démarrer et d'arrêter le suivi. Cet écran affiche également toutes les données de sommeil de l'utilisateur. Le bouton Effacer supprime définitivement toutes les données que l'application a collectées pour l'utilisateur. Le deuxième écran, à droite, permet de sélectionner une note de qualité du sommeil.

Cette application utilise une architecture simplifiée avec un contrôleur d'UI, ViewModel et LiveData. L'application utilise également une base de données Room pour rendre les données de sommeil persistantes.

La liste des nuits de sommeil affichée sur le premier écran est fonctionnelle, mais pas très esthétique. L'application utilise un formateur complexe pour créer des chaînes de texte pour la vue de texte et des nombres pour la qualité. De plus, cette conception n'est pas évolutive. Une fois que vous aurez résolu tous ces problèmes dans cet atelier de programmation, l'application finale aura les mêmes fonctionnalités et l'écran principal ressemblera à ceci :

L'affichage d'une liste ou d'une grille de données est l'une des tâches d'UI les plus courantes dans Android. Les listes peuvent être simples ou très complexes. Une liste de vues de texte peut afficher des données simples, comme une liste de courses. Une liste complexe, comme une liste annotée de destinations de vacances, peut afficher de nombreux détails à l'utilisateur dans une grille à faire défiler avec des en-têtes.

Pour prendre en charge tous ces cas d'utilisation, Android fournit le widget RecyclerView.

Le principal avantage de RecyclerView est son efficacité pour les listes volumineuses :

  • Par défaut, RecyclerView permet seulement de traiter ou dessiner les éléments actuellement visibles à l'écran. Par exemple, si votre liste comporte 1 000 éléments, mais que seuls 10 éléments sont visibles, RecyclerView se contente de dessiner 10 éléments à l'écran. Lorsque l'utilisateur fait défiler la page, RecyclerView identifie les nouveaux éléments à l'écran et affiche juste ces éléments.
  • Lorsqu'un élément n'est plus affiché à l'écran, ses vues sont recyclées. Cela signifie que l'élément affiche le nouveau contenu qui défile à l'écran. Ce comportement RecyclerView vous fait gagner beaucoup de temps de traitement et garantit un défilement fluide des listes.
  • Lorsqu'un élément change, au lieu de redessiner toute la liste, RecyclerView peut mettre à jour cet élément. Cela représente un gain d'efficacité considérable lors de l'affichage de listes d'éléments complexes.

Dans la séquence ci-dessous, vous pouvez voir que les données ABC ont été affichées dans une vue. Une fois que cette vue est sortie de l'écran, RecyclerView la réutilise pour les nouvelles données, XYZ.

Modèle d'adaptateur

Si vous voyagez entre des pays qui utilisent des prises électriques différentes, vous savez probablement comment brancher vos appareils à l'aide d'un adaptateur. L'adaptateur vous permet de convertir un type de prise en un autre, ce qui revient à convertir une interface en une autre.

Le modèle d'adaptateur en ingénierie logicielle aide un objet à fonctionner avec une autre API. RecyclerView utilise un adaptateur pour transformer les données de l'application en un format que RecyclerView peut afficher, sans modifier la façon dont l'application stocke et traite les données. Pour l'application de suivi du sommeil, vous créez un adaptateur qui adapte les données de la base de données Room en un format que RecyclerView sait afficher, sans modifier ViewModel.

Implémenter un RecyclerView

Pour afficher vos données dans un RecyclerView, vous avez besoin des éléments suivants :

  • Données à afficher.
  • Une instance RecyclerView définie dans votre fichier de mise en page, qui sert de conteneur pour les vues.
  • Mise en page pour un élément de données.
    Si tous les éléments de la liste se ressemblent, vous pouvez utiliser la même mise en page pour tous, mais ce n'est pas obligatoire. La mise en page de l'élément doit être créée séparément de celle du fragment, afin qu'une vue d'élément puisse être créée et remplie avec des données à la fois.
  • Gestionnaire de mise en page.
    Le gestionnaire de mise en page gère l'organisation (la mise en page) des composants d'interface utilisateur dans une vue.
  • Support de vue.
    Le support de vue étend la classe ViewHolder. Il contient les informations de la vue permettant d'afficher un élément de la mise en page de l'élément. Les conteneurs de vues ajoutent également des informations que RecyclerView utilise pour déplacer efficacement des vues sur l'écran.
  • Un adaptateur.
    L'adaptateur connecte vos données à RecyclerView. Il adapte les données pour qu'elles puissent être affichées dans un ViewHolder. Un RecyclerView utilise l'adaptateur pour déterminer comment afficher les données à l'écran.

Dans cette tâche, vous allez ajouter un RecyclerView à votre fichier de mise en page et configurer un Adapter pour exposer les données de sommeil au RecyclerView.

Étape 1 : Ajouter RecyclerView avec LayoutManager

Dans cette étape, vous allez remplacer le ScrollView par un RecyclerView dans le fichier fragment_sleep_tracker.xml.

  1. Téléchargez l'application RecyclerViewFundamentals-Starter depuis GitHub.
  2. Créez et exécutez l'application. Notez que les données sont affichées sous forme de texte simple.
  3. Ouvrez le fichier de mise en page fragment_sleep_tracker.xml dans l'onglet Design d'Android Studio.
  4. Dans le volet Arborescence des composants, supprimez ScrollView. Cette action supprime également le TextView qui se trouve dans le ScrollView.
  5. Dans le volet Palette, faites défiler la liste des types de composants sur la gauche pour trouver Conteneurs, puis sélectionnez-le.
  6. Faites glisser un RecyclerView du volet Palette vers le volet Arborescence des composants. Placez le RecyclerView dans le ConstraintLayout.

  1. Si une boîte de dialogue s'ouvre pour vous demander si vous souhaitez ajouter une dépendance, cliquez sur OK pour permettre à Android Studio d'ajouter la dépendance recyclerview à votre fichier Gradle. La synchronisation de votre application peut prendre quelques secondes.

  1. Ouvrez le fichier build.gradle du module, faites défiler la page jusqu'à la fin et notez la nouvelle dépendance, qui ressemble au code ci-dessous :
implementation 'androidx.recyclerview:recyclerview:1.0.0'
  1. Rebasculez sur fragment_sleep_tracker.xml.
  2. Dans l'onglet Texte, recherchez le code RecyclerView indiqué ci-dessous :
<androidx.recyclerview.widget.RecyclerView
   android:layout_width="match_parent"
   android:layout_height="match_parent" />
  1. Attribuez l'id de sleep_list à RecyclerView.
android:id="@+id/sleep_list"
  1. Positionnez le RecyclerView pour qu'il occupe la partie restante de l'écran à l'intérieur du ConstraintLayout. Pour ce faire, limitez le haut de RecyclerView au bouton Start (Démarrer), le bas au bouton Clear (Effacer) et chaque côté au parent. Définissez la largeur et la hauteur de la mise en page sur 0 dp dans l'éditeur de mise en page ou dans le fichier XML, à l'aide du code suivant :
    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. Ajoutez un gestionnaire de mise en page au fichier XML RecyclerView. Chaque RecyclerView a besoin d'un gestionnaire de mise en page qui lui indique comment positionner les éléments dans la liste. Android fournit un LinearLayoutManager, qui dispose par défaut les éléments dans une liste verticale de lignes de pleine largeur.
app:layoutManager="androidx.recyclerview.widget.LinearLayoutManager"
  1. Passez à l'onglet Conception et remarquez que les contraintes ajoutées ont entraîné l'expansion de RecyclerView pour remplir l'espace disponible.

Étape 2 : Créez la mise en page de l'élément de liste et le support de vue de texte

RecyclerView n'est qu'un conteneur. Dans cette étape, vous allez créer la mise en page et l'infrastructure pour les éléments à afficher dans RecyclerView.

Pour obtenir un RecyclerView fonctionnel le plus rapidement possible, vous commencez par utiliser un élément de liste simpliste qui n'affiche la qualité du sommeil que sous forme de nombre. Pour cela, vous avez besoin d'un support de vue, TextItemViewHolder. Vous avez également besoin d'une vue, d'un TextView, pour les données. (Dans une étape ultérieure, vous en apprendrez plus sur les porte-vues et sur la façon de présenter toutes les données de sommeil.)

  1. Créez un fichier de mise en page appelé text_item_view.xml. L'élément racine que vous utilisez n'a pas d'importance, car vous allez remplacer le code du modèle.
  2. Dans text_item_view.xml, supprimez tout le code fourni.
  3. Ajoutez un TextView avec une marge intérieure de 16dp au début et à la fin, et une taille de texte de 24sp. La largeur doit correspondre à celle du parent et la hauteur doit s'adapter au contenu. Étant donné que cette vue est affichée dans RecyclerView, vous n'avez pas besoin de la placer dans 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. Ouvrez Util.kt. Faites défiler la page jusqu'à la fin et ajoutez la définition ci-dessous, qui crée la classe TextItemViewHolder. Placez le code en bas du fichier, après la dernière accolade fermante. Le code est placé dans Util.kt, car ce support de vue est temporaire et vous le remplacerez ultérieurement.
class TextItemViewHolder(val textView: TextView): RecyclerView.ViewHolder(textView)
  1. Si vous y êtes invité, importez android.widget.TextView et androidx.recyclerview.widget.RecyclerView.

Étape 3 : Créer SleepNightAdapter

La tâche principale lors de l'implémentation d'un RecyclerView consiste à créer l'adaptateur. Vous disposez d'un simple conteneur de vue pour la vue de l'élément et d'une mise en page pour chaque élément. Vous pouvez maintenant créer un adaptateur. L'adaptateur crée un support de vue et le remplit de données pour que RecyclerView les affiche.

  1. Dans le package sleeptracker, créez une classe Kotlin appelée SleepNightAdapter.
  2. Faites en sorte que la classe SleepNightAdapter étende RecyclerView.Adapter. La classe est appelée SleepNightAdapter, car elle adapte un objet SleepNight en quelque chose que RecyclerView peut utiliser. L'adaptateur doit savoir quel support de vue utiliser. Transmettez donc TextItemViewHolder. Importez les composants nécessaires lorsque vous y êtes invité, puis une erreur s'affiche, car il existe des méthodes obligatoires à implémenter.
class SleepNightAdapter: RecyclerView.Adapter<TextItemViewHolder>() {}
  1. Au niveau supérieur de SleepNightAdapter, créez une variable listOf SleepNight pour contenir les données.
var data =  listOf<SleepNight>()
  1. Dans SleepNightAdapter, remplacez getItemCount() pour renvoyer la taille de la liste des nuits de sommeil dans data. RecyclerView doit savoir combien d'éléments l'adaptateur doit afficher. Pour ce faire, il appelle getItemCount().
override fun getItemCount() = data.size
  1. Dans SleepNightAdapter, remplacez la fonction onBindViewHolder(), comme indiqué ci-dessous.

    La fonction onBindViewHolder() est appelée par RecyclerView pour afficher les données d'un élément de liste à la position spécifiée. La méthode onBindViewHolder() prend donc deux arguments : un support de vue et une position des données à lier. Pour cette application, le support est TextItemViewHolder et la position est la position dans la liste.
override fun onBindViewHolder(holder: TextItemViewHolder, position: Int) {
}
  1. Dans onBindViewHolder(), créez une variable pour un élément à une position donnée dans les données.
 val item = data[position]
  1. Le ViewHolder que vous avez créé possède une propriété appelée textView. Dans onBindViewHolder(), définissez le text de textView sur le nombre correspondant à la qualité du sommeil. Ce code n'affiche qu'une liste de nombres, mais cet exemple simple vous permet de voir comment l'adaptateur insère les données dans le support de vue et à l'écran.
holder.textView.text = item.sleepQuality.toString()
  1. Dans SleepNightAdapter, remplacez et implémentez onCreateViewHolder(), qui est appelé lorsque RecyclerView a besoin d'un conteneur de vue pour représenter un élément.

    Cette fonction accepte deux paramètres et renvoie un ViewHolder. Le paramètre parent, qui est le groupe de vues contenant le support de vue, est toujours RecyclerView. Le paramètre viewType est utilisé lorsqu'une même RecyclerView contient plusieurs vues. Par exemple, si vous placez une liste de vues de texte, une image et une vidéo dans le même RecyclerView, la fonction onCreateViewHolder() doit savoir quel type de vue utiliser.
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): TextItemViewHolder {
}
  1. Dans onCreateViewHolder(), créez une instance de LayoutInflater.

    Le système de gonflage de mise en page sait comment créer des vues à partir de mises en page XML. Le context contient des informations sur la façon d'augmenter correctement la vue. Dans un adaptateur pour une vue du recycleur, vous transmettez toujours le contexte du groupe de vues parent, qui est RecyclerView.
val layoutInflater = LayoutInflater.from(parent.context)
  1. Dans onCreateViewHolder(), créez le view en demandant à layoutinflater de le développer.

    Transmettez la mise en page XML de la vue et le groupe de vues parent de la vue. Le troisième argument booléen est attachToRoot. Cet argument doit être false, car RecyclerView ajoute automatiquement cet élément à la hiérarchie des vues le moment venu.
val view = layoutInflater
       .inflate(R.layout.text_item_view, parent, false) as TextView
  1. Dans onCreateViewHolder(), renvoyez un TextItemViewHolder créé avec view.
return TextItemViewHolder(view)
  1. L'adaptateur doit informer RecyclerView lorsque data a changé, car RecyclerView ne connaît rien des données. Il ne connaît que les holders de vue que l'adaptateur lui fournit.

    Pour indiquer à RecyclerView quand les données qu'il affiche ont changé, ajoutez un setter personnalisé à la variable data qui se trouve en haut de la classe SleepNightAdapter. Dans le setter, attribuez une nouvelle valeur à data, puis appelez notifyDataSetChanged() pour déclencher le redessin de la liste avec les nouvelles données.
var data =  listOf<SleepNight>()
   set(value) {
       field = value
       notifyDataSetChanged()
   }

Étape 4 : Informez RecyclerView de l'adaptateur

RecyclerView doit connaître l'adaptateur à utiliser pour obtenir les supports de vue.

  1. Ouvrez SleepTrackerFragment.kt.
  2. Dans onCreateview(), créez un adaptateur. Placez ce code après la création du modèle ViewModel et avant l'instruction return.
val adapter = SleepNightAdapter()
  1. Associez adapter à RecyclerView.
binding.sleepList.adapter = adapter
  1. Nettoyez et recréez votre projet pour mettre à jour l'objet binding.

    Si des erreurs concernant binding.sleepList ou binding.FragmentSleepTrackerBinding s'affichent toujours, invalidez les caches et redémarrez. (Sélectionnez File > Invalidate Caches / Restart.)

    Si vous exécutez l'application maintenant, aucune erreur ne s'affiche, mais aucune donnée n'est affichée lorsque vous appuyez sur Start (Démarrer), puis sur Stop (Arrêter).

Étape 5 : Transférez les données dans l'adaptateur

Jusqu'à présent, vous avez un adaptateur et un moyen d'obtenir des données de l'adaptateur dans le RecyclerView. Vous devez maintenant insérer des données dans l'adaptateur à partir de ViewModel.

  1. Ouvrez SleepTrackerViewModel.
  2. Recherchez la variable nights, qui stocke toutes les nuits de sommeil, c'est-à-dire les données à afficher. La variable nights est définie en appelant getAllNights() sur la base de données.
  3. Supprimez private de nights, car vous allez créer un observateur qui doit accéder à cette variable. Votre déclaration doit se présenter comme suit :
val nights = database.getAllNights()
  1. Dans le package database, ouvrez SleepDatabaseDao.
  2. Recherchez la fonction getAllNights(). Notez que cette fonction renvoie une liste de valeurs SleepNight sous la forme LiveData. Cela signifie que la variable nights contient LiveData, qui est tenu à jour par Room. Vous pouvez observer nights pour savoir quand il change.
  3. Ouvrez SleepTrackerFragment.
  4. Dans onCreateView(), sous la création de adapter, créez un observateur sur la variable nights.

    En fournissant le viewLifecycleOwner du fragment comme propriétaire du cycle de vie, vous pouvez vous assurer que cet observateur n'est actif que lorsque RecyclerView est à l'écran.
sleepTrackerViewModel.nights.observe(viewLifecycleOwner, Observer {
   })
  1. Dans l'observateur, chaque fois que vous obtenez une valeur non nulle (pour nights), attribuez la valeur à data de l'adaptateur. Voici le code finalisé pour l'observateur et la définition des données :
sleepTrackerViewModel.nights.observe(viewLifecycleOwner, Observer {
   it?.let {
       adapter.data = it
   }
})
  1. Compilez et exécutez votre code.

    Si votre adaptateur fonctionne, les chiffres de qualité du sommeil s'affichent sous forme de liste. La capture d'écran de gauche montre "-1" après que vous avez appuyé sur Démarrer. La capture d'écran de droite montre le nombre de qualité du sommeil mis à jour après que vous avez appuyé sur Arrêter et sélectionné une note de qualité.

Étape 6 : Découvrez comment les porte-vues sont recyclés

RecyclerView recycle les porte-vues, ce qui signifie qu'il les réutilise. Lorsqu'une vue défile hors de l'écran, RecyclerView réutilise cette vue pour celle qui est sur le point de défiler à l'écran.

Étant donné que ces porte-vues sont recyclés, assurez-vous que onBindViewHolder() définit ou réinitialise toutes les personnalisations que les éléments précédents ont pu définir sur un porte-vue.

Par exemple, vous pouvez définir la couleur du texte sur rouge dans les espaces réservés pour les vues qui contiennent des notes de qualité inférieures ou égales à 1 et qui représentent un mauvais sommeil.

  1. Dans la classe SleepNightAdapter, ajoutez le code suivant à la fin de onBindViewHolder().
if (item.sleepQuality <= 1) {
   holder.textView.setTextColor(Color.RED) // red
}
  1. Exécutez l'application.
  2. Si vous ajoutez des données de mauvaise qualité sur le sommeil, le nombre devient rouge.
  3. Ajoutez des notes élevées pour la qualité du sommeil jusqu'à ce qu'un nombre rouge élevé s'affiche à l'écran.

    Comme RecyclerView réutilise les emplacements de vue, il finit par réutiliser l'un des emplacements de vue rouges pour une note de qualité élevée. La note élevée s'affiche à tort en rouge.

  1. Pour résoudre ce problème, ajoutez une instruction else afin de définir la couleur sur noir si la qualité n'est pas inférieure ou égale à un.

    Avec les deux conditions explicites, le support de vue utilisera la couleur de texte appropriée pour chaque élément.
if (item.sleepQuality <= 1) {
   holder.textView.setTextColor(Color.RED) // red
} else {
   // reset
   holder.textView.setTextColor(Color.BLACK) // black
}
  1. Exécutez l'application. Les nombres doivent toujours avoir la bonne couleur.

Félicitations ! Vous disposez désormais d'une RecyclerView de base entièrement fonctionnelle.

Dans cette tâche, vous allez remplacer le simple support de vue par un support capable d'afficher plus de données pour une nuit de sommeil.

Le ViewHolder simple que vous avez ajouté à Util.kt encapsule simplement un TextView dans un TextItemViewHolder.

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

Alors pourquoi RecyclerView n'utilise-t-il pas directement un TextView ? Cette ligne de code unique offre de nombreuses fonctionnalités. Un ViewHolder décrit une vue d'élément et les métadonnées concernant sa place dans le RecyclerView. RecyclerView s'appuie sur cette fonctionnalité pour positionner correctement la vue lorsque la liste défile, et pour effectuer des actions intéressantes comme animer les vues lorsque des éléments sont ajoutés ou supprimés dans Adapter.

Si RecyclerView doit accéder aux vues stockées dans ViewHolder, il peut le faire à l'aide de la propriété itemView du support de vue. RecyclerView utilise itemView lorsqu'il lie un élément à afficher à l'écran, lorsqu'il dessine des décorations autour d'une vue, comme une bordure, et pour implémenter l'accessibilité.

Étape 1 : Créez la mise en page de l'article

Dans cette étape, vous allez créer le fichier de mise en page pour un élément. La mise en page se compose d'un ConstraintLayout avec un ImageView pour la qualité du sommeil, un TextView pour la durée du sommeil et un TextView pour la qualité sous forme de texte. Comme vous avez déjà créé des mises en page, copiez et collez le code XML fourni.

  1. Créez un fichier de ressources de mise en page et nommez-le list_item_sleep_night.
  2. Remplacez tout le code du fichier par le code ci-dessous. Familiarisez-vous ensuite avec la mise en page que vous venez de créer.
<?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. Passez à l'onglet Design dans Android Studio. En mode Conception, votre mise en page ressemble à la capture d'écran de gauche ci-dessous. Dans la vue Schéma, il ressemble à la capture d'écran de droite.

Étape 2 : Créez ViewHolder

  1. Ouvrez SleepNightAdapter.kt.
  2. Créez une classe dans SleepNightAdapter appelée ViewHolder et faites-la étendre RecyclerView.ViewHolder.
class ViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView){}
  1. Dans ViewHolder, obtenez des références aux vues. Vous avez besoin d'une référence aux vues que ce ViewHolder mettra à jour. Chaque fois que vous liez ce ViewHolder, vous devez accéder à l'image et aux deux vues de texte. (Vous convertirez ce code pour utiliser la liaison de données ultérieurement.)
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)

Étape 3 : Utiliser le ViewHolder dans SleepNightAdapter

  1. Dans la définition SleepNightAdapter, au lieu de TextItemViewHolder, utilisez SleepNightAdapter.ViewHolder que vous venez de créer.
class SleepNightAdapter: RecyclerView.Adapter<SleepNightAdapter.ViewHolder>() {

Mettez à jour onCreateViewHolder() :

  1. Modifiez la signature de onCreateViewHolder() pour renvoyer ViewHolder.
  2. Modifiez le programme d'inflation de la mise en page pour utiliser la ressource de mise en page appropriée, list_item_sleep_night.
  3. Supprimez le casting vers TextView.
  4. Au lieu de renvoyer un objet TextItemViewHolder, renvoyez un objet ViewHolder.

    Voici la fonction onCreateViewHolder() mise à jour :
    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)
    }

Mettez à jour onBindViewHolder() :

  1. Modifiez la signature de onBindViewHolder() pour que le paramètre holder soit un ViewHolder au lieu d'un TextItemViewHolder.
  2. Dans onBindViewHolder(), supprimez tout le code, à l'exception de la définition de item.
  3. Définissez un res val qui contient une référence au resources pour cette vue.
val res = holder.itemView.context.resources
  1. Définissez le texte de la vue de texte sleepLength sur la durée. Copiez le code ci-dessous, qui appelle une fonction de mise en forme fournie avec le code de démarrage.
holder.sleepLength.text = convertDurationToFormatted(item.startTimeMilli, item.endTimeMilli, res)
  1. Cela génère une erreur, car convertDurationToFormatted() doit être défini. Ouvrez Util.kt, puis annulez la mise en commentaire du code et des importations associées. (Sélectionnez Code > Commenter avec des commentaires sur les lignes.)
  2. De retour dans onBindViewHolder(), utilisez convertNumericQualityToString() pour définir la qualité.
holder.quality.text= convertNumericQualityToString(item.sleepQuality, res)
  1. Vous devrez peut-être importer manuellement ces fonctions.
import com.example.android.trackmysleepquality.convertDurationToFormatted
import com.example.android.trackmysleepquality.convertNumericQualityToString
  1. Définissez l'icône correspondant à la qualité. La nouvelle icône ic_sleep_active vous est fournie dans le code de démarrage.
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. Voici la fonction onBindViewHolder() mise à jour, qui définit toutes les données pour 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. Exécutez votre application. L'écran devrait ressembler à la capture d'écran ci-dessous, avec l'icône de qualité du sommeil, ainsi que le texte indiquant la durée et la qualité du sommeil.

Votre RecyclerView est maintenant terminé. Vous avez appris à implémenter un Adapter et un ViewHolder, et vous les avez combinés pour afficher une liste avec un RecyclerView Adapter.

Le code que vous avez écrit jusqu'à présent montre comment créer un adaptateur et un support de vue. Toutefois, vous pouvez améliorer ce code. Le code permettant d'afficher et de gérer les porte-vues est mélangé, et onBindViewHolder() connaît les détails sur la façon de mettre à jour ViewHolder.

Dans une application de production, vous pouvez avoir plusieurs supports de vue, des adaptateurs plus complexes et plusieurs développeurs qui apportent des modifications. Vous devez structurer votre code de sorte que tout ce qui concerne un ViewHolder se trouve uniquement dans le ViewHolder.

Étape 1 : Refactoriser onBindViewHolder()

Dans cette étape, vous refactorisez le code et déplacez toutes les fonctionnalités du support de vue dans ViewHolder. L'objectif de cette refactorisation n'est pas de modifier l'apparence de l'application pour l'utilisateur, mais de permettre aux développeurs de travailler sur le code plus facilement et plus sûrement. Heureusement, Android Studio dispose d'outils pour vous aider.

  1. Dans SleepNightAdapter, dans onBindViewHolder(), sélectionnez tout sauf l'instruction permettant de déclarer la variable item.
  2. Effectuez un clic droit, puis sélectionnez Refactor > Extract > Function (Refactoriser > Extraire > Fonction).
  3. Nommez la fonction bind et acceptez les paramètres suggérés. puis sur OK.

    La fonction bind() est placée sous 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. Placez le curseur sur le mot holder du paramètre holder de bind(). Appuyez sur Alt+Enter (Option+Enter sur Mac) pour ouvrir le menu d'intention. Sélectionnez Convert parameter to receiver (Convertir le paramètre en récepteur) pour le convertir en fonction d'extension avec la signature suivante :
private fun ViewHolder.bind(item: SleepNight) {...}
  1. Coupez et collez la fonction bind() dans ViewHolder.
  2. Rendez bind() public.
  3. Si nécessaire, importez bind() dans l'adaptateur.
  4. Comme il se trouve désormais dans ViewHolder, vous pouvez supprimer la partie ViewHolder de la signature. Voici le code final de la fonction bind() dans la 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
   })
}

Étape 2 : Refactoriser onCreateViewHolder

La méthode onCreateViewHolder() de l'adaptateur gonfle actuellement la vue à partir de la ressource de mise en page pour ViewHolder. Toutefois, l'inflation n'a rien à voir avec l'adaptateur, mais tout à voir avec le ViewHolder. L'inflation doit se produire dans ViewHolder.

  1. Dans onCreateViewHolder(), sélectionnez tout le code dans le corps de la fonction.
  2. Effectuez un clic droit, puis sélectionnez Refactor > Extract > Function (Refactoriser > Extraire > Fonction).
  3. Nommez la fonction from et acceptez les paramètres suggérés. puis sur OK.
  4. Placez le curseur sur le nom de la fonction from. Appuyez sur Alt+Enter (Option+Enter sur Mac) pour ouvrir le menu d'intention.
  5. Sélectionnez Déplacer vers l'objet associé. La fonction from() doit se trouver dans un objet compagnon pour pouvoir être appelée sur la classe ViewHolder, et non sur une instance ViewHolder.
  6. Déplacez l'objet companion dans la classe ViewHolder.
  7. Rendez from() public.
  8. Dans onCreateViewHolder(), remplacez l'instruction return pour renvoyer le résultat de l'appel de from() dans la classe ViewHolder.

    Vos méthodes onCreateViewHolder() et from() terminées devraient ressembler au code ci-dessous, et votre code devrait se compiler et s'exécuter sans erreur.
    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. Modifiez la signature de la classe ViewHolder pour que le constructeur soit privé. Étant donné que from() est désormais une méthode qui renvoie une nouvelle instance ViewHolder, il n'y a plus aucune raison d'appeler le constructeur de ViewHolder.
class ViewHolder private constructor(itemView: View) : RecyclerView.ViewHolder(itemView){
  1. Exécutez l'application. Elle devrait se compiler et s'exécuter de la même manière qu'auparavant, ce qui est le résultat souhaité après la refactorisation.

Projet Android Studio : RecyclerViewFundamentals

  • L'affichage d'une liste ou d'une grille de données est l'une des tâches d'UI les plus courantes dans Android. RecyclerView est conçu pour être efficace, même lors de l'affichage de très longues listes.
  • RecyclerView ne traite ou ne dessine que les éléments actuellement visibles à l'écran.
  • Lorsqu'un élément disparaît de l'écran, ses vues sont recyclées. Cela signifie que l'élément affiche le nouveau contenu qui défile à l'écran.
  • Le modèle d'adaptateur en génie logiciel permet à un objet de fonctionner avec une autre API. RecyclerView utilise un adaptateur pour transformer les données de l'application en un format qu'il peut afficher, sans avoir à modifier la façon dont l'application stocke et traite les données.

Pour afficher vos données dans un RecyclerView, vous avez besoin des éléments suivants :

  • RecyclerView
     : pour créer une instance de RecyclerView, définissez un élément <RecyclerView> dans le fichier de mise en page.
  • LayoutManager
     : un RecyclerView utilise un LayoutManager pour organiser la mise en page des éléments du RecyclerView, par exemple en les disposant dans une grille ou dans une liste linéaire.

    Dans le <RecyclerView> du fichier de mise en page, définissez l'attribut app:layoutManager sur le gestionnaire de mise en page (par exemple, LinearLayoutManager ou GridLayoutManager).

    Vous pouvez également définir le LayoutManager d'un RecyclerView de manière programmatique. (Cette technique sera abordée dans un prochain atelier de programmation.)
  • Mise en page pour chaque élément
     : créez une mise en page pour un élément de données dans un fichier de mise en page XML.
  • Adaptateur
     : créez un adaptateur qui prépare les données et la façon dont elles seront affichées dans un ViewHolder. Associez l'adaptateur à RecyclerView.

    Lorsque RecyclerView s'exécute, il utilise l'adaptateur pour déterminer comment afficher les données à l'écran.

    L'adaptateur vous oblige à implémenter les méthodes suivantes :
    – getItemCount() pour renvoyer le nombre d'éléments.
    – onCreateViewHolder() pour renvoyer le ViewHolder d'un élément de la liste.
    – onBindViewHolder() pour adapter les données aux vues d'un élément de la liste.

  • ViewHolder
     : un ViewHolder contient les informations de vue pour afficher un élément de la mise en page de l'élément.
  • La méthode onBindViewHolder() de l'adaptateur adapte les données aux vues. Vous remplacez toujours cette méthode. En règle générale, onBindViewHolder() développe la mise en page d'un élément et place les données dans les vues de la mise en page.
  • Étant donné que RecyclerView ne sait rien des données, Adapter doit informer RecyclerView lorsque ces données changent. Utilisez notifyDataSetChanged() pour notifier au Adapter que les données ont changé.

Cours Udacity :

Documentation pour les développeurs Android :

Cette section répertorie les devoirs possibles pour les élèves qui suivent cet atelier de programmation dans le cadre d'un cours animé par un enseignant. Il revient à l'enseignant d'effectuer les opérations suivantes :

  • Attribuer des devoirs si nécessaire
  • Indiquer aux élèves comment rendre leurs devoirs
  • Noter les devoirs

Les enseignants peuvent utiliser ces suggestions autant qu'ils le souhaitent, et ne doivent pas hésiter à attribuer d'autres devoirs aux élèves s'ils le jugent nécessaire.

Si vous suivez cet atelier de programmation par vous-même, n'hésitez pas à utiliser ces devoirs pour tester vos connaissances.

Répondre aux questions suivantes

Question 1

Comment RecyclerView affiche-t-il les éléments ? Plusieurs réponses possibles.

▢ Affiche les éléments sous forme de liste ou de grille.

▢ Faire défiler l'écran verticalement ou horizontalement.

▢ Possibilité de faire défiler l'écran en diagonale sur les appareils plus grands, comme les tablettes.

▢ Possibilité d'utiliser des mises en page personnalisées lorsqu'une liste ou une grille ne sont pas suffisantes pour le cas d'utilisation.

Question 2

Quels sont les avantages de RecyclerView ? Plusieurs réponses possibles.

▢ Affichage efficace des listes volumineuses.

▢ Les données sont automatiquement mises à jour.

▢ Moins d'actualisations nécessaires à chaque fois qu'un élément est modifié, supprimé ou ajouté à la liste.

▢ Réutilise la vue qui défile hors écran pour afficher l'élément suivant qui défile à l'écran.

Question 3

Pourquoi utiliser des adaptateurs ? Plusieurs réponses possibles.

▢ La séparation des tâches simplifie la modification et la phase de test du code.

▢ RecyclerView est agnostique par rapport aux données affichées.

▢ Les couches de traitement des données n'ont pas à se soucier de la manière dont les données seront affichées.

▢ L'application s'exécutera plus rapidement.

Question 4

Parmi les affirmations suivantes concernant les ViewHolder, lesquelles sont vraies ? Plusieurs réponses possibles.

▢ La mise en page ViewHolder est définie dans des fichiers de mise en page XML.

▢ Il existe un ViewHolder pour chaque unité de données de l'ensemble de données.

▢ Une RecyclerView peut contenir plusieurs ViewHolder.

▢ L'Adapter associe les données au ViewHolder.

Passez à la leçon suivante : 7.2 : DiffUtil et liaison de données avec RecyclerView