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

Cet atelier de programmation fait partie du cours Android Kotlin Fundamentals. Vous tirerez le meilleur parti de ce cours si vous suivez les ateliers de programmation dans l'ordre. Tous les ateliers de programmation du cours sont répertoriés sur la page de destination des ateliers de programmation Android Kotlin Fundamentals.

Présentation

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

Ce que vous devez déjà savoir

Vous devez être au fait:

  • Créer une interface utilisateur de base à l'aide d'une activité, de fragments et de vues
  • Navigation entre les fragments et utilisation de safeArgs pour transmettre des données entre fragments.
  • Affichez des modèles, affichez les usines de modèles, les transformations et LiveData ainsi que leurs observateurs.
  • Créer une base de données Room, créer un DAO et définir des entités
  • Utilisation de coroutines pour les tâches de base de données et autres tâches de longue durée

Points abordés

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

Objectifs de l'atelier

  • Remplacez l'application TrackMySleepQuality de la leçon précédente pour utiliser un RecyclerView pour afficher les données de qualité du sommeil.

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

L'application de départ pour le suivi du sommeil 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 sur le sommeil de l'utilisateur. Le bouton Effacer supprime définitivement toutes les données collectées par l'utilisateur. Le deuxième écran, à droite, permet de sélectionner un niveau de qualité du sommeil.

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

La liste des nuits de sommeil affichée dans le premier écran est fonctionnelle, mais n'est pas élégante. L'application utilise un outil de mise en forme complexe pour créer des chaînes de texte pour la vue textuelle et des nombres pour la qualité. En outre, cette conception n'est pas évolutive. Une fois ces problèmes résolus dans cet atelier de programmation, l'application finale offre les mêmes fonctionnalités et l'écran principal se présente comme suit:

L'affichage d'une liste ou d'une grille de données est l'une des tâches les plus courantes dans l'UI d'Android. Les listes varient du plus simple au plus complexe. Une liste de vues de texte peut contenir des données simples, comme une liste de courses. Une liste complexe, telle qu'une liste annotée de destinations de vacances, peut présenter à l'utilisateur de nombreux détails dans une grille de défilement avec des en-têtes.

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

Le principal avantage de RecyclerView est qu'il est très efficace pour les longues listes:

  • Par défaut, RecyclerView ne fonctionne que pour traiter ou dessiner les éléments actuellement visibles à l'écran. Par exemple, si votre liste comporte 1 000 éléments, mais que seuls 10 d'entre eux sont visibles, RecyclerView n'utilise que 10 éléments pour afficher 10 éléments à l'écran. Lorsque l'utilisateur fait défiler la page, RecyclerView détermine quels nouveaux éléments doivent être affichés à l'écran et fait suffisamment de travail pour les afficher.
  • Lorsqu'un article fait défiler l'écran, les vues associées sont recyclées. Cela signifie que l'élément est rempli de nouveaux contenus qui défilent à l'écran. Ce comportement RecyclerView permet d'économiser beaucoup de temps de traitement et d'aider les listes à faire défiler les pages facilement.
  • Lorsqu'un élément change, au lieu de redessiner toute la liste, RecyclerView peut mettre à jour cet élément. C'est une grande efficacité en termes d'affichage de listes d'éléments complexes !

La séquence ci-dessous montre qu'une vue contient des données, ABC. Une fois que la vue a fait défiler l'écran, RecyclerView la réutilise pour les nouvelles données, XYZ.

Motif de l'adaptateur

Si vous vous rendez dans un pays où vous utilisez une prise électrique différente, vous savez probablement comment brancher vos appareils sur des prises à l'aide d'un adaptateur. L'adaptateur vous permet de convertir un type de prise à un autre, ce qui convertit vraiment une interface en une autre.

Le modèle d'adaptateur en ingénierie logicielle permet à un objet de fonctionner avec une autre API. RecyclerView utilise un adaptateur pour transformer les données d'application en un élément 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 à un élément que RecyclerView sait comment afficher, sans modifier le ViewModel.

Implémenter 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, servant de conteneur aux vues.
  • Mise en page pour un seul é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 les éléments, mais ce n'est pas obligatoire. La mise en page d'élément doit être créée séparément de la mise en page du fragment. Un élément à la fois peut donc être créé et rempli de données.
  • Gestionnaire de mises en page.
    Le gestionnaire de mise en page gère l'organisation (la mise en page) des composants d'interface utilisateur dans une vue.
  • Titulaire de la vue.
    Le conteneur de vues étend la classe ViewHolder. Elle contient des informations concernant l'affichage d'un élément de la mise en page. Les titulaires de vues ajoutent également des informations dont RecyclerView se sert pour déplacer efficacement les vues à l'écran.
  • Un adaptateur.
    L'adaptateur connecte vos données à la RecyclerView. Il adapte les données pour pouvoir les afficher 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 sur votre sommeil à RecyclerView.

Étape 1: Ajouter RecyclerView avec le gestionnaire de mise en page

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

  1. Téléchargez l'application RecyclerViewFundamentals-Starter sur GitHub.
  2. Compilez 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 le ScrollView. Cette action supprime également les TextView de ScrollView.
  5. Dans le volet Palette, faites défiler la liste des types de composants jusqu'à Conteneurs, puis sélectionnez-la.
  6. Faites glisser un RecyclerView depuis le 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 laisser Android Studio ajouter la dépendance recyclerview à votre fichier Gradle. Cela peut prendre quelques secondes, puis votre application se synchronise.

  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. Revenir à fragment_sleep_tracker.xml.
  2. Dans l'onglet Text (Texte), recherchez le code RecyclerView présenté ci-dessous:
<androidx.recyclerview.widget.RecyclerView
   android:layout_width="match_parent"
   android:layout_height="match_parent" />
  1. Attribuez un id de sleep_list à la RecyclerView.
android:id="@+id/sleep_list"
  1. Positionnez le RecyclerView de sorte à occuper la partie restante de l'écran dans le ConstraintLayout. Pour ce faire, forcez la partie supérieure du bouton RecyclerView au bouton Démarrer, la partie inférieure du bouton 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 au format XML, en utilisant le 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 indique comment positionner les éléments dans la liste. Android fournit une LinearLayoutManager qui présente par défaut les éléments dans une liste verticale de lignes pleine largeur.
app:layoutManager="androidx.recyclerview.widget.LinearLayoutManager"
  1. Accédez à l'onglet Design (Conception) et notez que les contraintes supplémentaires ont causé l'expansion de RecyclerView pour remplir l'espace disponible.

Étape 2: Créez la mise en page et l'emplacement du texte de l'élément de liste

RecyclerView n'est qu'un conteneur. Au cours de cette étape, vous allez créer la mise en page et l'infrastructure pour que les éléments s'affichent dans le RecyclerView.

Pour accéder rapidement à un RecyclerView fonctionnel, vous devez d'abord utiliser un élément de liste simple qui affiche uniquement la qualité du sommeil sous forme de nombre. Pour cela, vous devez disposer d'un titulaire de vue, TextItemViewHolder. Vous avez également besoin d'une vue TextView pour les données. Plus tard, vous en apprendrez davantage sur les titulaires de vues et sur la manière d'afficher toutes les données sur votre sommeil.

  1. Créez un fichier de mise en page appelé text_item_view.xml. Peu importe ce que vous utilisez en tant qu'élément racine, car vous remplacerez le code du modèle.
  2. Dans text_item_view.xml, supprimez tout le code indiqué.
  3. Ajoutez un TextView avec une marge intérieure 16dp au début et à la fin, et une taille de texte de 24sp. Laissez la largeur correspondre au parent et la hauteur enveloppe le contenu. Puisque cette vue est affichée dans la 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 affichée ci-dessous, qui crée la classe TextItemViewHolder. Placez le code au bas du fichier, après l'accolade finale. Le code est placé dans Util.kt, car ce conteneur de vue est temporaire et vous le remplacerez plus tard.
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éez SleepNightAdapter

La tâche principale de mise en œuvre d'une RecyclerView consiste à créer l'adaptateur. Vous disposez d'un support de vue simple pour la vue de l'élément et d'une mise en page pour chacun. Vous pouvez désormais créer un adaptateur. L'adaptateur crée un champ de vision et remplit les données pour que la RecyclerView s'affiche.

  1. Dans le package sleeptracker, créez une classe Kotlin appelée SleepNightAdapter.
  2. Faites en sorte que la classe SleepNightAdapter étend RecyclerView.Adapter. Cette classe s'appelle SleepNightAdapter, car elle adapte un objet SleepNight à un objet que RecyclerView peut utiliser. L'adaptateur doit connaître le support de vue à utiliser. Par conséquent, transmettez TextItemViewHolder. Importez les composants nécessaires lorsque vous y êtes invité. Ensuite, un message d'erreur s'affiche, car des méthodes obligatoires sont à mettre en œuvre.
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. La RecyclerView doit connaître le nombre d'éléments que l'adaptateur doit afficher pour cela. Pour cela, il doit appeler getItemCount().
override fun getItemCount() = data.size
  1. Dans SleepNightAdapter, ignorez 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. Ainsi, la méthode onBindViewHolder() utilise deux arguments: un conteneur de vue et une position des données à lier. Pour cette application, l'objet est TextItemViewHolder et la position correspond à celle de 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 text sur textView pour déterminer la qualité du sommeil. Ce code n'affiche qu'une liste de nombres, mais cet exemple simple vous permet de voir comment l'adaptateur transmet les données au conteneur de vue et à l'écran.
holder.textView.text = item.sleepQuality.toString()
  1. Dans SleepNightAdapter, ignorez et implémentez onCreateViewHolder(), qui est appelé lorsque RecyclerView a besoin d'un titulaire de vue pour représenter un élément.

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

    L'auteur de la 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 de recyclage, vous transmettez toujours le contexte du groupe de vues parent, qui est la RecyclerView.
val layoutInflater = LayoutInflater.from(parent.context)
  1. Dans onCreateViewHolder(), créez le view en demandant à layoutinflater de le gonfler.

    Transmettez la mise en page XML de la vue et le groupe de vues parent pour la vue. Le troisième argument, booléen, est attachToRoot. Cet argument doit être false, car RecyclerView ajoute cet élément à la hiérarchie des vues à l'heure de votre choix.
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 le RecyclerView lorsque le data a changé, car le RecyclerView ne sait rien des données. Il connaît uniquement les titulaires de vues que l'adaptateur lui fournit.

    Pour indiquer à RecyclerView quand les données affichées 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 la recréation 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 titulaires 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 se produisent encore autour de binding.sleepList ou binding.FragmentSleepTrackerBinding, invalidez les caches et redémarrez. (Sélectionnez File > Invalidate Cache/Restart (Fichier et invalider/Redémarrer).

    Si vous exécutez l'application maintenant, aucune erreur ne s'affichera, mais aucune donnée ne s'affichera lorsque vous appuyerez sur Démarrer, puis sur Arrêter.

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

Jusqu'à présent, vous disposez d'un adaptateur et d'un moyen de transférer des données de l'adaptateur vers RecyclerView. Vous devez maintenant importer des données dans l'adaptateur à partir de ViewModel.

  1. Ouvrez SleepTrackerViewModel.
  2. Recherchez la variable nights, qui stocke toutes les nuits de sommeil, ce qui correspond aux 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 en tant que LiveData. Cela signifie que la variable nights contient LiveData qui est mis à jour par Room. Vous pouvez observer nights si le résultat change.
  3. Ouvrez SleepTrackerFragment.
  4. Dans onCreateView(), sous la création de adapter, créez un observateur sur la variable nights.

    En fournissant le fragment viewLifecycleOwner en tant que propriétaire du cycle de vie, vous pouvez vous assurer que cet observateur n'est actif que lorsque le RecyclerView s'affiche à 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 à l'data de l'adaptateur. Voici le code final pour l'observateur et la définition des données:
sleepTrackerViewModel.nights.observe(viewLifecycleOwner, Observer {
   it?.let {
       adapter.data = it
   }
})
  1. Créez et exécutez votre code.

    Les chiffres sur la qualité du sommeil s'affichent sous forme de liste si votre adaptateur fonctionne. La capture d'écran sur la gauche s'affiche lorsque vous appuyez sur Démarrer. La capture d'écran sur la droite montre le nombre mis à jour pour la qualité du sommeil après avoir appuyé sur Arrêter et sélectionné un niveau de qualité.

Étape 6: Découvrir comment les titulaires de vues sont recyclés

RecyclerView recycle les titulaires de vues. Cela signifie qu'il les réutilise. Lorsqu'une vue fait défiler l'écran, RecyclerView la réutilise pour faire défiler l'écran.

Ces supports de vue étant recyclés, assurez-vous que onBindViewHolder() définit ou réinitialise les personnalisations que les éléments précédents peuvent avoir définies sur un détenteur de vue.

Par exemple, vous pouvez définir la couleur du texte sur rouge dans les supports visibles qui présentent des niveaux de qualité inférieurs ou égaux à 1 et représentent un sommeil de mauvaise qualité.

  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. Ajoutez des données de faible qualité concernant le sommeil, et le nombre est rouge.
  3. Ajoutez des notes élevées pour la qualité du sommeil jusqu'à ce qu'un nombre élevé s'affiche à l'écran.

    Étant donné que RecyclerView réutilise les titulaires d'une vue, il réutilise l'un des deux affichages rouges pour obtenir une haute qualité. La note élevée est affichée par erreur 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 ces deux conditions explicites, le détenteur de la 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 numéros doivent toujours être dans la bonne couleur.

Félicitations ! Vous disposez à présent d'une propriété RecyclerView de base entièrement fonctionnelle.

Dans cette tâche, vous allez remplacer le support de vue simple par un modèle pouvant 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)

Pourquoi RecyclerView n'utilise-t-il pas directement une valeur TextView ? Cette ligne de code offre de nombreuses fonctionnalités. Un ViewHolder décrit une vue d'élément et des métadonnées sur son emplacement dans 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 des vues lorsque des éléments sont ajoutés ou supprimés dans Adapter.

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

Étape 1: Créez la mise en page des éléments

Au cours de cette étape, vous allez créer le fichier de mise en page d'un élément. La mise en page comprend une ConstraintLayout avec une 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à effectué 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. Accédez à l'onglet Design dans Android Studio. Dans la vue Conception, votre mise en page ressemble à la capture d'écran sur la gauche. Sur la vue en plan, elle ressemble à la capture d'écran sur la droite.

Étape 2: Créez ViewHolder

  1. Ouvrez SleepNightAdapter.kt.
  2. Créez une classe dans SleepNightAdapter appelée ViewHolder et prolongez-la 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 va mettre à jour. Chaque fois que vous liez ces ViewHolder, vous devez accéder à l'image et aux deux vues de texte. (Vous convertirez ce code pour utiliser une liaison de données plus tard.)
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: Utilisez ViewHolder dans SleepNightAdapter

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

Mettez à jour onCreateViewHolder() :

  1. Modifiez la signature de onCreateViewHolder() pour qu'elle renvoie ViewHolder.
  2. Modifiez le gonfleur d'affichage pour qu'il utilise la bonne ressource de mise en page, list_item_sleep_night.
  3. Supprimez la diffusion vers TextView.
  4. Au lieu de renvoyer un objet TextItemViewHolder, renvoyez un ViewHolder.

    Voici la fonction onCreateViewHolder() terminée:
    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() afin que le paramètre holder soit ViewHolder au lieu de TextItemViewHolder.
  2. Dans onBindViewHolder(), supprimez tout le code, à l'exception de la définition de item.
  3. Définissez une res val contenant une référence à la resources de cette vue.
val res = holder.itemView.context.resources
  1. Définissez le texte de la vue Texte sleepLength sur la durée. Copiez le code ci-dessous, qui appelle une fonction de mise en forme qui fournit 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 > Comment with Line Comments (Code &gt ; commenter avec des commentaires de ligne).
  2. Revenez dans onBindViewHolder() et 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() terminée : elle 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. Votre écran doit ressembler à la capture d'écran ci-dessous, avec l'icône de qualité du sommeil, ainsi qu'un texte indiquant la durée et la qualité du sommeil.

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

Jusqu'à présent, votre code montre le processus de création d'un adaptateur et d'un support de vue. Vous pouvez toutefois améliorer ce code. Le code à afficher et le code permettant de gérer les titulaires de vues sont mélangés. onBindViewHolder() sait comment mettre à jour les ViewHolder.

Dans une application de production, vous pouvez avoir plusieurs titulaires de vues, des adaptateurs plus complexes et plusieurs développeurs qui effectuent des modifications. Vous devez structurer votre code de sorte que tous les éléments associés à un détenteur de vue ne soient disponibles que dans ce dernier.

Étape 1: Refactoriser (onBindViewHolder())

Au cours de cette étape, vous allez refactoriser le code et déplacer toutes les fonctionnalités du support de vue dans ViewHolder. Cette refactorisation ne vise pas à modifier l'apparence de l'application pour l'utilisateur, mais à permettre aux développeurs de travailler plus facilement sur le code. Heureusement, Android Studio propose des outils pour vous aider.

  1. Dans SleepNightAdapter, dans onBindViewHolder(), sélectionnez tout, sauf l'instruction pour déclarer la variable item.
  2. Effectuez un clic droit, puis sélectionnez Refactor > Extract > Function (Refactoriser &gt ; Extraire &gt ; Fonction).
  3. Nommez la fonction bind et acceptez les paramètres suggérés. Cliquez 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 un Mac) pour ouvrir le menu d'intent. Sélectionnez Convertir le paramètre en destinataire pour le convertir en une fonction d'extension ayant la signature suivante:
private fun ViewHolder.bind(item: SleepNight) {...}
  1. Coupez et collez la fonction bind() dans ViewHolder.
  2. Rendre bind() public.
  3. Importez bind() dans l'adaptateur, si nécessaire.
  4. Étant donné qu'il s'agit maintenant d'un élément ViewHolder, vous pouvez supprimer la partie ViewHolder de la signature. Voici le code final de la fonction bind() de 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: Refactorisez sur CreateViewHolder

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 et rien à voir avec le ViewHolder. L'inflation doit se produire dans le ViewHolder.

  1. Dans onCreateViewHolder(), sélectionnez tout le code du corps de la fonction.
  2. Effectuez un clic droit, puis sélectionnez Refactor > Extract > Function (Refactoriser &gt ; Extraire &gt ; Fonction).
  3. Nommez la fonction from et acceptez les paramètres suggérés. Cliquez sur OK.
  4. Placez le curseur sur le nom de la fonction from. Appuyez sur Alt+Enter (Option+Enter sur un Mac) pour ouvrir le menu d'intent.
  5. Sélectionnez Déplacer vers l'objet associé. La fonction from() doit se trouver dans un objet associé 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. Rendre from() public.
  8. Dans onCreateViewHolder(), modifiez l'instruction return pour qu'elle renvoie le résultat de l'appel de from() dans la classe ViewHolder.

    Votre méthode onCreateViewHolder() et from() devrait ressembler à celle du code ci-dessous. Votre code doit normalement être compilé et exécuté 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é. from() étant maintenant une méthode qui renvoie une nouvelle instance ViewHolder, il n'y a aucune raison pour quiconque d'appeler le constructeur de ViewHolder.
class ViewHolder private constructor(itemView: View) : RecyclerView.ViewHolder(itemView){
  1. Exécutez l'application. Votre application devrait se développer et s'exécuter comme avant, ce qui correspond au résultat souhaité après refactorisation.

Projet Android Studio : RecyclerViewFundamentals

  • L'affichage d'une liste ou d'une grille de données est l'une des tâches les plus courantes dans l'UI d'Android. RecyclerView est conçu pour être efficace même lorsque les listes sont très volumineuses.
  • RecyclerView effectue uniquement les tâches de traitement ou de dessin des éléments actuellement visibles à l'écran.
  • Lorsqu'un élément défile à l'écran, ses vues sont recyclées. Cela signifie que l'élément est rempli de nouveaux contenus qui défilent à l'écran.
  • Le modèle d'adaptateur en ingénierie logicielle permet d'associer un objet à une autre API. RecyclerView utilise un adaptateur pour transformer les données d'application en un écran 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 dans le RecyclerView, par exemple en les plaçant 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 (comme LinearLayoutManager ou GridLayoutManager).

    Vous pouvez aussi définir le LayoutManager pour un RecyclerView. (Nous aborderons cette technique 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 comment elles seront affichées dans un ViewHolder. Associez l'adaptateur à RecyclerView.

    Lorsque RecyclerView s'exécute, il détermine comment afficher les données à l'écran à l'aide de l'adaptateur.

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

  • ViewHolder
    Une ViewHolder contient les informations d'affichage d'un élément de la mise en page.
  • La méthode onBindViewHolder() de l'adaptateur adapte les données aux vues. Vous ignorez toujours cette méthode. En règle générale, onBindViewHolder() gonfle la mise en page d'un élément et place les données dans les vues.
  • Étant donné que la RecyclerView ne dispose d'aucune information concernant les données, la Adapter doit en informer le RecyclerView lorsque ces données changent. Utilisez notifyDataSetChanged() pour informer Adapter que les données ont été modifiées.

Cours Udacity:

Documentation pour les développeurs Android:

Cette section répertorie les devoirs possibles pour les élèves qui effectuent cet atelier de programmation dans le cadre d'un cours animé par un enseignant. C'est à l'enseignant de procéder comme suit:

  • Si nécessaire, rendez-les.
  • Communiquez aux élèves comment rendre leurs devoirs.
  • Notez les devoirs.

Les enseignants peuvent utiliser ces suggestions autant qu'ils le souhaitent, et ils n'ont pas besoin d'attribuer les devoirs de leur choix.

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

Répondez à ces questions.

Question 1

Comment RecyclerView affiche-t-il les éléments ? Sélectionnez toutes les réponses applicables.

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

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

▢ Définit en diagonale sur des appareils de grande taille, comme les tablettes.

▢ Permet les mises en page personnalisées lorsqu'une liste ou une grille n'est pas suffisante pour le cas d'utilisation.

Question 2

Quels sont les avantages d'utiliser RecyclerView ? Sélectionnez toutes les réponses applicables.

▢ Permet d'afficher efficacement de longues listes.

▢ Mettez automatiquement à jour les données.

▢ Cela signifie que les actualisations sont minimes lorsqu'un élément est mis à jour, supprimé ou ajouté à la liste.

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

Question 3

Quelles sont les raisons pour lesquelles vous utilisez des adaptateurs ? Sélectionnez toutes les réponses applicables.

▢ La séparation des préoccupations facilite le changement et le test du code.

RecyclerView est indépendant des données affichées.

▢ Les couches de traitement des données ne doivent pas se préoccuper de l'affichage des données.

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

Question 4

Parmi les affirmations suivantes concernant ViewHolder, lesquelles sont vraies ? Sélectionnez toutes les réponses applicables.

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

▢ Il y a un ViewHolder pour chaque unité de données dans l'ensemble de données.

▢ Vous pouvez avoir plusieurs ViewHolder dans un RecyclerView.

Adapter associe les données à ViewHolder.

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