Créer des vues personnalisées

Cet atelier de programmation fait partie du cours Développement Android avancé en Kotlin. Vous tirerez pleinement parti de ce cours en suivant les ateliers de programmation dans l'ordre, mais ce n'est pas obligatoire. Tous les ateliers de programmation du cours sont listés sur la page de destination des ateliers de programmation Android avancé en Kotlin.

Introduction

Android propose un grand nombre de sous-classes View, telles que Button, TextView, EditText, ImageView, CheckBox ou RadioButton. Vous pouvez utiliser ces sous-classes pour créer une UI qui permet l'interaction de l'utilisateur et affiche des informations dans votre application. Si aucune des sous-classes View ne répond à vos besoins, vous pouvez créer une sous-classe View appelée vue personnalisée .

Pour créer une vue personnalisée, vous pouvez étendre une sous-classe View existante (telle que Button ou EditText) ou créer votre propre sous-classe de View. En étendant directement View, vous pouvez créer un élément d'UI interactif de n'importe quelle taille et forme en remplaçant la méthode onDraw() pour que View le dessine.

Une fois que vous avez créé une vue personnalisée, vous pouvez l'ajouter à vos mises en page d'activité de la même manière que vous ajouteriez un TextView ou un Button.

Cette leçon vous explique comment créer une vue personnalisée à partir de zéro en étendant View.

Ce que vous devez déjà savoir

  • Savoir créer une application avec une activité et l'exécuter à l'aide d'Android Studio

Points abordés

  • Comment étendre View pour créer une vue personnalisée.
  • Comment dessiner une vue personnalisée de forme circulaire.
  • Comment utiliser des écouteurs pour gérer l'interaction de l'utilisateur avec la vue personnalisée.
  • Vous savez utiliser une vue personnalisée dans une mise en page.

Objectifs de l'atelier

  • Étendez View pour créer une vue personnalisée.
  • Initialisez la vue personnalisée avec les valeurs de dessin et de peinture.
  • Remplacez onDraw() pour dessiner la vue.
  • Utilisez des écouteurs pour fournir le comportement de la vue personnalisée.
  • Ajoutez la vue personnalisée à une mise en page.

L'application CustomFanController montre comment créer une sous-classe de vue personnalisée en étendant la classe View. La nouvelle sous-classe est appelée DialView.

L'application affiche un élément d'interface utilisateur circulaire qui ressemble à une commande de ventilateur physique, avec des paramètres pour éteint (0), faible (1), moyen (2) et élevé (3). Lorsque l'utilisateur appuie sur la vue, l'indicateur de sélection passe à la position suivante : 0-1-2-3, puis revient à 0. De plus, si la sélection est égale ou supérieure à 1, la couleur d'arrière-plan de la partie circulaire de la vue passe du gris au vert (indiquant que la puissance du ventilateur est activée).

Les vues sont les composants de base de l'UI d'une application. La classe View fournit de nombreuses sous-classes, appelées widgets d'UI, qui couvrent la plupart des besoins de l'interface utilisateur d'une application Android standard.

Les blocs de construction de l'UI, tels que Button et TextView, sont des sous-classes qui étendent la classe View. Pour gagner du temps et réduire les efforts de développement, vous pouvez étendre l'une de ces sous-classes View. La vue personnalisée hérite de l'apparence et du comportement de son parent. Vous pouvez modifier le comportement ou l'aspect de l'apparence selon vos besoins. Par exemple, si vous étendez EditText pour créer une vue personnalisée, la vue se comporte comme une vue EditText, mais peut également être personnalisée pour afficher, par exemple, un bouton X qui efface le texte du champ de saisie de texte.

Vous pouvez étendre n'importe quelle sous-classe View, telle que EditText, pour obtenir une vue personnalisée. Choisissez celle qui se rapproche le plus de ce que vous souhaitez accomplir. Vous pouvez ensuite utiliser la vue personnalisée comme n'importe quelle autre sous-classe View dans une ou plusieurs mises en page en tant qu'élément XML avec des attributs.

Pour créer votre propre vue personnalisée de A à Z, étendez la classe View elle-même. Votre code remplace les méthodes View pour définir l'apparence et la fonctionnalité de la vue. Pour créer votre propre vue personnalisée, vous devez dessiner l'intégralité de l'élément d'UI de n'importe quelle taille et forme à l'écran. Si vous créez une sous-classe d'une vue existante telle que Button, cette classe gère le dessin pour vous. (Vous en apprendrez davantage sur le dessin dans la suite de cet atelier de programmation.)

Pour créer une vue personnalisée, suivez ces étapes générales :

  • Créez une classe de vue personnalisée qui étend View ou une sous-classe View (telle que Button ou EditText).
  • Si vous étendez une sous-classe View existante, remplacez uniquement le comportement ou les aspects de l'apparence que vous souhaitez modifier.
  • Si vous étendez la classe View, dessinez la forme de la vue personnalisée et contrôlez son apparence en remplaçant les méthodes View telles que onDraw() et onMeasure() dans la nouvelle classe.
  • Ajoutez du code pour répondre à l'interaction de l'utilisateur et, si nécessaire, redessinez la vue personnalisée.
  • Utilisez la classe de vue personnalisée comme widget d'UI dans la mise en page XML de votre activité. Vous pouvez également définir des attributs personnalisés pour la vue afin de la personnaliser dans différentes mises en page.

Dans cette tâche, vous allez :

  • Créez une application avec un ImageView comme espace réservé temporaire pour la vue personnalisée.
  • Étendez View pour créer la vue personnalisée.
  • Initialisez la vue personnalisée avec les valeurs de dessin et de peinture.

Étape 1 : Créez une application avec un espace réservé ImageView

  1. Créez une application Kotlin intitulée CustomFanController à l'aide du modèle Activité vide. Assurez-vous que le nom du package est com.example.android.customfancontroller.
  2. Ouvrez activity_main.xml dans l'onglet Texte pour modifier le code XML.
  3. Remplacez le TextView existant par ce code. Ce texte sert de libellé dans l'activité de la vue personnalisée.
<TextView
       android:id="@+id/customViewLabel"
       android:textAppearance="@style/Base.TextAppearance.AppCompat.Display3"
       android:layout_width="wrap_content"
       android:layout_height="wrap_content"
       android:padding="16dp"
       android:textColor="@android:color/black"
       android:layout_marginStart="8dp"
       android:layout_marginEnd="8dp"
       android:layout_marginTop="24dp"
       android:text="Fan Control"
       app:layout_constraintLeft_toLeftOf="parent"
       app:layout_constraintRight_toRightOf="parent"
       app:layout_constraintTop_toTopOf="parent"/>
  1. Ajoutez cet élément ImageView à la mise en page. Il s'agit d'un espace réservé pour la vue personnalisée que vous allez créer dans cet atelier de programmation.
<ImageView
       android:id="@+id/dialView"
       android:layout_width="200dp"
       android:layout_height="200dp"
       android:background="@android:color/darker_gray"
       app:layout_constraintTop_toBottomOf="@+id/customViewLabel"
       app:layout_constraintLeft_toLeftOf="parent"
       app:layout_constraintRight_toRightOf="parent"
       android:layout_marginLeft="8dp"
       android:layout_marginRight="8dp"
       android:layout_marginTop="8dp"/>
  1. Extrayez les ressources de chaîne et de dimension dans les deux éléments d'interface utilisateur.
  2. Cliquez sur l'onglet Conception. La mise en page doit se présenter comme suit :

Étape 2 : Créer votre classe de vue personnalisée

  1. Créez une classe Kotlin appelée DialView.
  2. Modifiez la définition de la classe pour étendre View. Importez android.view.View lorsque vous y êtes invité.
  3. Cliquez sur View, puis sur l'ampoule rouge. Choisissez Add Android View constructors using '@JvmOverloads' (Ajouter des constructeurs Android View à l'aide de "@JvmOverloads"). Android Studio ajoute le constructeur à partir de la classe View. L'annotation @JvmOverloads indique au compilateur Kotlin de générer des surcharges pour cette fonction qui remplacent les valeurs de paramètre par défaut.
class DialView @JvmOverloads constructor(
   context: Context,
   attrs: AttributeSet? = null,
   defStyleAttr: Int = 0
) : View(context, attrs, defStyleAttr) {
  1. Au-dessus de la définition de la classe DialView, juste en dessous des importations, ajoutez un enum de premier niveau pour représenter les vitesses de ventilateur disponibles. Notez que ce enum est de type Int, car les valeurs sont des ressources de chaîne plutôt que des chaînes réelles. Android Studio affichera des erreurs pour les ressources de chaîne manquantes dans chacune de ces valeurs. Vous les corrigerez lors d'une étape ultérieure.
private enum class FanSpeed(val label: Int) {
   OFF(R.string.fan_off),
   LOW(R.string.fan_low),
   MEDIUM(R.string.fan_medium),
   HIGH(R.string.fan_high);
}
  1. Sous enum, ajoutez ces constantes. Vous les utiliserez pour dessiner les indicateurs et les libellés.
private const val RADIUS_OFFSET_LABEL = 30      
private const val RADIUS_OFFSET_INDICATOR = -35
  1. Dans la classe DialView, définissez plusieurs variables dont vous avez besoin pour dessiner la vue personnalisée. Si vous y êtes invité, importez android.graphics.PointF.
private var radius = 0.0f                   // Radius of the circle.
private var fanSpeed = FanSpeed.OFF         // The active selection.
// position variable which will be used to draw label and indicator circle position
private val pointPosition: PointF = PointF(0.0f, 0.0f)
  • radius correspond au rayon actuel du cercle. Cette valeur est définie lorsque la vue est dessinée à l'écran.
  • fanSpeed correspond à la vitesse actuelle du ventilateur, qui est l'une des valeurs de l'énumération FanSpeed. La valeur par défaut est OFF.
  • Enfin postPosition est un point X,Y qui sera utilisé pour dessiner plusieurs éléments de la vue à l'écran.

Ces valeurs sont créées et initialisées ici au lieu de l'être lorsque la vue est réellement dessinée, afin de garantir que l'étape de dessin proprement dite s'exécute le plus rapidement possible.

  1. Toujours dans la définition de la classe DialView, initialisez un objet Paint avec quelques styles de base. Importez android.graphics.Paint et android.graphics.Typeface lorsque vous y êtes invité. Comme pour les variables, ces styles sont initialisés ici pour accélérer l'étape de dessin.
private val paint = Paint(Paint.ANTI_ALIAS_FLAG).apply {
   style = Paint.Style.FILL
   textAlign = Paint.Align.CENTER
   textSize = 55.0f
   typeface = Typeface.create( "", Typeface.BOLD)
}
  1. Ouvrez res/values/strings.xml et ajoutez les ressources de chaîne pour les vitesses du ventilateur :
<string name="fan_off">off</string>
<string name="fan_low">1</string>
<string name="fan_medium">2</string>
<string name="fan_high">3</string>

Une fois que vous avez créé une vue personnalisée, vous devez pouvoir la dessiner. Lorsque vous étendez une sous-classe View telle que EditText, cette sous-classe définit l'apparence et les attributs de la vue, et se dessine à l'écran. Par conséquent, vous n'avez pas besoin d'écrire de code pour dessiner la vue. Vous pouvez remplacer les méthodes du parent pour personnaliser votre vue.

Si vous créez votre propre vue à partir de zéro (en étendant View), vous êtes responsable du dessin de la vue entière chaque fois que l'écran est actualisé, et du remplacement des méthodes View qui gèrent le dessin. Pour dessiner correctement une vue personnalisée qui étend View, vous devez :

  • Calculez la taille de la vue lorsqu'elle apparaît pour la première fois, et chaque fois que sa taille change, en remplaçant la méthode onSizeChanged().
  • Remplacez la méthode onDraw() pour dessiner la vue personnalisée, en utilisant un objet Canvas stylisé par un objet Paint.
  • Appelez la méthode invalidate() lorsque vous répondez à un clic de l'utilisateur qui modifie la façon dont la vue est dessinée pour invalider l'intégralité de la vue, ce qui force un appel à onDraw() pour redessiner la vue.

La méthode onDraw() est appelée chaque fois que l'écran est actualisé, ce qui peut se produire plusieurs fois par seconde. Pour des raisons de performances et pour éviter les problèmes visuels, vous devez travailler le moins possible dans onDraw(). En particulier, ne placez pas d'allocations dans onDraw(), car elles peuvent entraîner un garbage collection qui peut provoquer un bégaiement visuel.

Les classes Canvas et Paint offrent un certain nombre de raccourcis de dessin utiles :

Vous en apprendrez davantage sur Canvas et Paint dans un prochain atelier de programmation. Pour en savoir plus sur la façon dont Android dessine les vues, consultez Dessiner des vues via Android.

Dans cette tâche, vous allez dessiner la vue personnalisée du contrôleur de ventilateur sur l'écran (le cadran lui-même, l'indicateur de position actuelle et les libellés des indicateurs) avec les méthodes onSizeChanged() et onDraw(). Vous allez également créer une méthode d'assistance, computeXYForSpeed(),, pour calculer la position X,Y actuelle du libellé de l'indicateur sur le cadran.

Étape 1 : Calculer les positions et dessiner la vue

  1. Dans la classe DialView, sous les initialisations, remplacez la méthode onSizeChanged() de la classe View pour calculer la taille du cadran de la vue personnalisée. Importez kotlin.math.min lorsque vous y êtes invité.

    La méthode onSizeChanged() est appelée chaque fois que la taille de la vue change, y compris la première fois qu'elle est dessinée lorsque la mise en page est gonflée. Remplacez onSizeChanged() pour calculer les positions, les dimensions et toute autre valeur liée à la taille de votre vue personnalisée, au lieu de les recalculer à chaque fois que vous dessinez. Dans ce cas, vous utilisez onSizeChanged() pour calculer le rayon actuel de l'élément circulaire du sélecteur.
override fun onSizeChanged(width: Int, height: Int, oldWidth: Int, oldHeight: Int) {
   radius = (min(width, height) / 2.0 * 0.8).toFloat()
}
  1. Sous onSizeChanged(), ajoutez ce code pour définir une fonction d'extension computeXYForSpeed() pour la classe PointF . Importez kotlin.math.cos et kotlin.math.sin lorsque vous y êtes invité. Cette fonction d'extension de la classe PointF calcule les coordonnées X et Y à l'écran pour le libellé de texte et l'indicateur actuel (0, 1, 2 ou 3), en fonction de la position FanSpeed actuelle et du rayon du sélecteur. Vous l'utiliserez dans onDraw().
private fun PointF.computeXYForSpeed(pos: FanSpeed, radius: Float) {
   // Angles are in radians.
   val startAngle = Math.PI * (9 / 8.0)   
   val angle = startAngle + pos.ordinal * (Math.PI / 4)
   x = (radius * cos(angle)).toFloat() + width / 2
   y = (radius * sin(angle)).toFloat() + height / 2
}
  1. Remplacez la méthode onDraw() pour afficher la vue à l'écran avec les classes Canvas et Paint. Lorsque vous y êtes invité, importez android.graphics.Canvas. Voici le remplacement du squelette :
override fun onDraw(canvas: Canvas) {
   super.onDraw(canvas)
   
}
  1. Dans onDraw(), ajoutez cette ligne pour définir la couleur de peinture sur gris (Color.GRAY) ou vert (Color.GREEN) selon que la vitesse du ventilateur est OFF ou toute autre valeur. Lorsque vous y êtes invité, importez android.graphics.Color.
// Set dial background color to green if selection not off.
paint.color = if (fanSpeed == FanSpeed.OFF) Color.GRAY else Color.GREEN
  1. Ajoutez ce code pour dessiner un cercle pour le cadran, avec la méthode drawCircle(). Cette méthode utilise la largeur et la hauteur de la vue actuelle pour trouver le centre du cercle, son rayon et la couleur de peinture actuelle. Les propriétés width et height sont des membres de la superclasse View et indiquent les dimensions actuelles de la vue.
// Draw the dial.
canvas.drawCircle((width / 2).toFloat(), (height / 2).toFloat(), radius, paint)
  1. Ajoutez le code suivant pour dessiner un cercle plus petit pour le repère de l'indicateur de vitesse du ventilateur, également avec la méthode drawCircle(). Cette partie utilise PointF.Méthode d'extension computeXYforSpeed() pour calculer les coordonnées X et Y du centre de l'indicateur en fonction de la vitesse du ventilateur actuelle.
// Draw the indicator circle.
val markerRadius = radius + RADIUS_OFFSET_INDICATOR
pointPosition.computeXYForSpeed(fanSpeed, markerRadius)
paint.color = Color.BLACK
canvas.drawCircle(pointPosition.x, pointPosition.y, radius/12, paint)
  1. Enfin, dessinez les libellés de vitesse du ventilateur (0, 1, 2, 3) aux emplacements appropriés autour du cadran. Cette partie de la méthode appelle à nouveau PointF.computeXYForSpeed() pour obtenir la position de chaque libellé et réutilise l'objet pointPosition à chaque fois pour éviter les allocations. Utilisez drawText() pour dessiner les libellés.
// Draw the text labels.
val labelRadius = radius + RADIUS_OFFSET_LABEL
for (i in FanSpeed.values()) {
   pointPosition.computeXYForSpeed(i, labelRadius)
   val label = resources.getString(i.label)
   canvas.drawText(label, pointPosition.x, pointPosition.y, paint)
}

Une fois terminée, la méthode onDraw() se présente comme suit :

override fun onDraw(canvas: Canvas) {
   super.onDraw(canvas)
   // Set dial background color to green if selection not off.
   paint.color = if (fanSpeed == FanSpeed.OFF) Color.GRAY else Color.GREEN
   // Draw the dial.
   canvas.drawCircle((width / 2).toFloat(), (height / 2).toFloat(), radius, paint)
   // Draw the indicator circle.
   val markerRadius = radius + RADIUS_OFFSET_INDICATOR
   pointPosition.computeXYForSpeed(fanSpeed, markerRadius)
   paint.color = Color.BLACK
   canvas.drawCircle(pointPosition.x, pointPosition.y, radius/12, paint)
   // Draw the text labels.
   val labelRadius = radius + RADIUS_OFFSET_LABEL
   for (i in FanSpeed.values()) {
       pointPosition.computeXYForSpeed(i, labelRadius)
       val label = resources.getString(i.label)
       canvas.drawText(label, pointPosition.x, pointPosition.y, paint)
   }
}

Étape 2 : Ajouter la vue à la mise en page

Pour ajouter une vue personnalisée à l'UI d'une application, spécifiez-la comme élément dans la mise en page XML de l'activité. Contrôlez son apparence et son comportement avec les attributs des éléments XML, comme vous le feriez pour n'importe quel autre élément d'UI.

  1. Dans activity_main.xml, remplacez la balise ImageView de dialView par com.example.android.customfancontroller.DialView et supprimez l'attribut android:background. Les classes DialView et ImageView d'origine héritent des attributs standards de la classe View. Il n'est donc pas nécessaire de modifier les autres attributs. Le nouvel élément DialView se présente comme suit :
<com.example.android.customfancontroller.DialView
       android:id="@+id/dialView"
       android:layout_width="@dimen/fan_dimen"
       android:layout_height="@dimen/fan_dimen"
       app:layout_constraintTop_toBottomOf="@+id/customViewLabel"
       app:layout_constraintLeft_toLeftOf="parent"
       app:layout_constraintRight_toRightOf="parent"
       android:layout_marginLeft="@dimen/default_margin"
       android:layout_marginRight="@dimen/default_margin"
       android:layout_marginTop="@dimen/default_margin" />
  1. Exécutez l'application. La vue de contrôle du ventilateur s'affiche dans l'activité.

La dernière tâche consiste à permettre à votre vue personnalisée d'effectuer une action lorsque l'utilisateur appuie dessus. Chaque pression doit déplacer l'indicateur de sélection vers la position suivante : désactivé-1-2-3 et retour à désactivé. De plus, si la sélection est définie sur 1 ou plus, changez l'arrière-plan de gris à vert pour indiquer que le ventilateur est en marche.

Pour rendre votre vue personnalisée cliquable, vous devez :

  • Définissez la propriété isClickable de la vue sur true. Cela permet à votre vue personnalisée de répondre aux clics.
  • Implémentez performClick() de la classe View pour effectuer des opérations lorsque l'utilisateur clique sur la vue.
  • Appelez la méthode invalidate(). Cela indique au système Android d'appeler la méthode onDraw() pour redessiner la vue.

Normalement, avec une vue Android standard, vous implémentez OnClickListener() pour effectuer une action lorsque l'utilisateur clique sur cette vue. Pour une vue personnalisée, vous implémentez plutôt la méthode performClick() de la classe View et appelez super.performClick(). La méthode performClick() par défaut appelle également onClickListener(). Vous pouvez donc ajouter vos actions à performClick() et laisser onClickListener() disponible pour une personnalisation ultérieure par vous ou d'autres développeurs susceptibles d'utiliser votre vue personnalisée.

  1. Dans DialView.kt, à l'intérieur de l'énumération FanSpeed, ajoutez une fonction d'extension next() qui modifie la vitesse actuelle du ventilateur pour passer à la vitesse suivante de la liste (de OFF à LOW, MEDIUM et HIGH, puis de nouveau à OFF). L'énumération complète ressemble maintenant à ceci :
private enum class FanSpeed(val label: Int) {
   OFF(R.string.fan_off),
   LOW(R.string.fan_low),
   MEDIUM(R.string.fan_medium),
   HIGH(R.string.fan_high);

   fun next() = when (this) {
       OFF -> LOW
       LOW -> MEDIUM
       MEDIUM -> HIGH
       HIGH -> OFF
   }
}
  1. Dans la classe DialView, juste avant la méthode onSizeChanged(), ajoutez un bloc init(). Si vous définissez la propriété isClickable de la vue sur "true", cette vue peut accepter les entrées utilisateur.
init {
   isClickable = true
}
  1. Sous init(),, remplacez la méthode performClick() par le code ci-dessous.
override fun performClick(): Boolean {
   if (super.performClick()) return true

   fanSpeed = fanSpeed.next()
   contentDescription = resources.getString(fanSpeed.label)
  
   invalidate()
   return true
}

Appel à super.performClick() doit avoir lieu en premier, ce qui permet les événements d'accessibilité ainsi que les appels onClickListener().

Les deux lignes suivantes incrémentent la vitesse du ventilateur avec la méthode next() et définissent la description du contenu de la vue sur la ressource de chaîne représentant la vitesse actuelle (arrêt, 1, 2 ou 3).

Enfin, la méthode invalidate() invalide l'intégralité de la vue, ce qui force un appel à onDraw() pour redessiner la vue. Si quelque chose change dans votre vue personnalisée pour une raison quelconque, y compris une interaction utilisateur, et que la modification doit être affichée, appelez invalidate()..

  1. Exécutez l'application. Appuyez sur l'élément DialView pour faire passer l'indicateur de "off" à 1. Le cadran doit devenir vert. À chaque pression, l'indicateur doit passer à la position suivante. Lorsque l'indicateur est à nouveau désactivé, le cadran doit redevenir gris.

Cet exemple montre les mécanismes de base de l'utilisation d'attributs personnalisés avec votre vue personnalisée. Vous définissez des attributs personnalisés pour la classe DialView avec une couleur différente pour chaque position du sélecteur.

  1. Créez et ouvrez res/values/attrs.xml.
  2. Dans <resources>, ajoutez un élément de ressource <declare-styleable>.
  3. Dans l'élément de ressource <declare-styleable>, ajoutez trois éléments attr, un pour chaque attribut, avec un name et un format. format est comme un type, et dans ce cas, il s'agit de color.
<?xml version="1.0" encoding="utf-8"?>
<resources>
       <declare-styleable name="DialView">
           <attr name="fanColor1" format="color" />
           <attr name="fanColor2" format="color" />
           <attr name="fanColor3" format="color" />
       </declare-styleable>
</resources>
  1. Ouvrez le fichier de mise en page activity_main.xml.
  2. Dans DialView, ajoutez des attributs pour fanColor1, fanColor2 et fanColor3, puis définissez leurs valeurs sur les couleurs indiquées ci-dessous. Utilisez app: comme préface pour l'attribut personnalisé (comme dans app:fanColor1) plutôt que android:, car vos attributs personnalisés appartiennent à l'espace de noms schemas.android.com/apk/res/your_app_package_name plutôt qu'à l'espace de noms android.
app:fanColor1="#FFEB3B"
app:fanColor2="#CDDC39"
app:fanColor3="#009688"

Pour utiliser les attributs de votre classe DialView, vous devez les récupérer. Ils sont stockés dans un AttributeSet, qui est remis à votre cours lors de sa création, s'il existe. Vous récupérez les attributs dans init et attribuez les valeurs des attributs à des variables locales pour la mise en cache.

  1. Ouvrez le fichier de classe DialView.kt.
  2. Dans DialView, déclarez des variables pour mettre en cache les valeurs des attributs.
private var fanSpeedLowColor = 0
private var fanSpeedMediumColor = 0
private var fanSeedMaxColor = 0
  1. Dans le bloc init, ajoutez le code suivant à l'aide de la fonction d'extension withStyledAttributes. Vous fournissez les attributs et la vue, et définissez vos variables locales. L'importation de withStyledAttributes importera également la fonction getColor() appropriée.
context.withStyledAttributes(attrs, R.styleable.DialView) {
   fanSpeedLowColor = getColor(R.styleable.DialView_fanColor1, 0)
   fanSpeedMediumColor = getColor(R.styleable.DialView_fanColor2, 0)
   fanSeedMaxColor = getColor(R.styleable.DialView_fanColor3, 0)
}
  1. Utilisez les variables locales dans onDraw() pour définir la couleur du sélecteur en fonction de la vitesse actuelle du ventilateur. Remplacez la ligne où la couleur de peinture est définie (paint.color = if (fanSpeed == FanSpeed.OFF) Color.GRAY else Color.GREEN) par le code ci-dessous.
paint.color = when (fanSpeed) {
   FanSpeed.OFF -> Color.GRAY
   FanSpeed.LOW -> fanSpeedLowColor
   FanSpeed.MEDIUM -> fanSpeedMediumColor
   FanSpeed.HIGH -> fanSeedMaxColor
} as Int
  1. Exécutez votre application, cliquez sur le sélecteur et le paramètre de couleur devrait être différent pour chaque position, comme indiqué ci-dessous.

Pour en savoir plus sur les attributs de vue personnalisés, consultez Créer une classe de vue.

L'accessibilité est un ensemble de techniques de conception, d'implémentation et de test qui permettent à votre application d'être utilisable par tous, y compris par les personnes ayant un handicap.

Les handicaps courants susceptibles d'affecter l'utilisation d'un appareil Android sont, par exemple, la cécité, les déficiences visuelles, le daltonisme, la surdité ou la perte d'audition, ainsi que la réduction de l'habileté motrice. Lorsque vous développez vos applications en gardant l'accessibilité à l'esprit, vous améliorez l'expérience utilisateur non seulement pour les personnes souffrant de ces handicaps, mais aussi pour tous vos autres utilisateurs.

Android fournit plusieurs fonctionnalités d'accessibilité par défaut dans les vues d'UI standards telles que TextView et Button. Toutefois, lorsque vous créez une vue personnalisée, vous devez réfléchir à la manière dont elle fournira des fonctionnalités accessibles, telles que des descriptions vocales du contenu à l'écran.

Dans cette tâche, vous allez découvrir TalkBack, le lecteur d'écran d'Android, et modifier votre application pour inclure des conseils et des descriptions vocaux pour la vue personnalisée DialView.

Étape 1 : Explorer TalkBack

TalkBack est le lecteur d'écran intégré à Android. Lorsque TalkBack est activé, l'utilisateur peut interagir avec son appareil Android sans voir l'écran, car Android décrit les éléments de l'écran à voix haute. Les utilisateurs ayant une déficience visuelle peuvent utiliser votre application grâce à TalkBack.

Dans cette tâche, vous allez activer TalkBack pour comprendre comment fonctionnent les lecteurs d'écran et comment naviguer dans les applications.

  1. Sur un appareil ou un émulateur Android, accédez à Paramètres > Accessibilité > TalkBack.
  2. Appuyez sur le bouton bascule Activé/Désactivé pour activer TalkBack.
  3. Appuyez sur OK pour confirmer les autorisations.
  4. Si vous y êtes invité, confirmez le mot de passe de votre appareil. Si c'est la première fois que vous exécutez TalkBack, un tutoriel se lance. (Il est possible que le tutoriel ne soit pas disponible sur les appareils plus anciens.)
  5. Il peut être utile de parcourir le tutoriel les yeux fermés. Pour rouvrir le tutoriel ultérieurement, accédez à Paramètres > Accessibilité > TalkBack > Paramètres > Lancer le tutoriel TalkBack.
  6. Compilez et exécutez l'application CustomFanController, ou ouvrez-la avec le bouton Aperçu ou Récents sur votre appareil. Lorsque TalkBack est activé, vous remarquerez que le nom de l'application est annoncé, ainsi que le texte du libellé TextView ("Contrôle du ventilateur"). Toutefois, si vous appuyez sur la vue DialView elle-même, aucune information n'est énoncée sur l'état de la vue (le paramètre actuel du sélecteur) ni sur l'action qui aura lieu lorsque vous appuierez sur la vue pour l'activer.

Étape 2 : Ajouter des descriptions de contenu pour les libellés des sélecteurs

Les descriptions de contenu décrivent la signification et l'objectif des vues dans votre application. Ces libellés permettent aux lecteurs d'écran tels que la fonctionnalité TalkBack d'Android d'expliquer précisément la fonction de chaque élément. Pour les vues statiques telles que ImageView, vous pouvez ajouter la description du contenu à la vue dans le fichier de mise en page avec l'attribut contentDescription. Les vues de texte (TextView et EditText) utilisent automatiquement le texte de la vue comme description du contenu.

Pour la vue de contrôle personnalisé du ventilateur, vous devez mettre à jour dynamiquement la description du contenu chaque fois que l'utilisateur clique sur la vue, afin d'indiquer le réglage actuel du ventilateur.

  1. En bas de la classe DialView, déclarez une fonction updateContentDescription() sans arguments ni type de retour.
fun updateContentDescription() {
}
  1. Dans updateContentDescription(), remplacez la propriété contentDescription de la vue personnalisée par la ressource de chaîne associée à la vitesse du ventilateur actuelle (arrêt, 1, 2 ou 3). Il s'agit des mêmes libellés que ceux utilisés dans onDraw() lorsque le sélecteur est affiché à l'écran.
fun updateContentDescription() {
   contentDescription = resources.getString(fanSpeed.label)
}
  1. Faites défiler la page jusqu'au bloc init(), puis ajoutez un appel à updateContentDescription() à la fin de ce bloc. Cela permet d'initialiser la description du contenu lorsque la vue est initialisée.
init {
   isClickable = true
   // ...

   updateContentDescription()
}
  1. Ajoutez un autre appel à updateContentDescription() dans la méthode performClick(), juste avant invalidate().
override fun performClick(): Boolean {
   if (super.performClick()) return true
   fanSpeed = fanSpeed.next()
   updateContentDescription()
   invalidate()
   return true
}
  1. Compilez et exécutez l'application, et assurez-vous que TalkBack est activé. Appuyez pour modifier le paramètre de la vue du sélecteur et remarquez que TalkBack annonce désormais le libellé actuel (désactivé, 1, 2, 3) ainsi que la phrase "Appuyez deux fois pour activer".

Étape 3 : Ajouter des informations pour l'action de clic

Vous pourriez vous arrêter là et votre vue serait utilisable dans TalkBack. Toutefois, il serait utile que votre vue indique non seulement qu'elle peut être activée ("Appuyez deux fois pour activer"), mais aussi ce qui se passera lorsqu'elle le sera ("Appuyez deux fois pour modifier" ou "Appuyez deux fois pour réinitialiser").

Pour ce faire, vous ajoutez des informations sur l'action de la vue (ici, une action de clic ou d'appui) à un objet d'informations de nœud d'accessibilité, par le biais d'un délégué d'accessibilité. Un délégué d'accessibilité vous permet de personnaliser les fonctionnalités d'accessibilité de votre application par composition (plutôt que par héritage).

Pour cette tâche, vous utiliserez les classes d'accessibilité des bibliothèques Android Jetpack (androidx.*) afin d'assurer la rétrocompatibilité.

  1. Dans DialView.kt, dans le bloc init, définissez un délégué d'accessibilité sur la vue en tant que nouvel objet AccessibilityDelegateCompat. Importez androidx.core.view.ViewCompat et androidx.core.view.AccessibilityDelegateCompat lorsque vous y êtes invité. Cette stratégie permet la plus grande rétrocompatibilité dans votre application.
ViewCompat.setAccessibilityDelegate(this, object : AccessibilityDelegateCompat() {
   
})
  1. Dans l'objet AccessibilityDelegateCompat, remplacez la fonction onInitializeAccessibilityNodeInfo() par un objet AccessibilityNodeInfoCompat et appelez la méthode du super. Importez androidx.core.view.accessibility.AccessibilityNodeInfoCompat lorsque vous y êtes invité.
ViewCompat.setAccessibilityDelegate(this, object : AccessibilityDelegateCompat() {
   override fun onInitializeAccessibilityNodeInfo(host: View, 
                            info: AccessibilityNodeInfoCompat) {
      super.onInitializeAccessibilityNodeInfo(host, info)

   }  
})

Chaque vue comporte un arbre de nœuds d'accessibilité, qui peut ou non correspondre aux composants de mise en page réels de la vue. Les services d'accessibilité d'Android parcourent ces nœuds pour obtenir des informations sur la vue (par exemple, des descriptions de contenu vocales ou des actions possibles sur cette vue). Lorsque vous créez une vue personnalisée, vous devrez peut-être également remplacer les informations sur le nœud afin de fournir des informations personnalisées pour l'accessibilité. Dans ce cas, vous allez remplacer les informations du nœud pour indiquer qu'il existe des informations personnalisées pour l'action de la vue.

  1. Dans onInitializeAccessibilityNodeInfo(), créez un objet AccessibilityNodeInfoCompat.AccessibilityActionCompat et attribuez-le à la variable customClick. Transmettez la constante AccessibilityNodeInfo.ACTION_CLICK et une chaîne d'espace réservé au constructeur. Lorsque vous y êtes invité, importez AccessibilityNodeInfo.
ViewCompat.setAccessibilityDelegate(this, object : AccessibilityDelegateCompat() {
   override fun onInitializeAccessibilityNodeInfo(host: View, 
                            info: AccessibilityNodeInfoCompat) {
      super.onInitializeAccessibilityNodeInfo(host, info)
      val customClick = AccessibilityNodeInfoCompat.AccessibilityActionCompat(
         AccessibilityNodeInfo.ACTION_CLICK,
        "placeholder"
      )
   }  
})

La classe AccessibilityActionCompat représente une action sur une vue à des fins d'accessibilité. Une action typique est un clic ou un appui, comme vous l'utilisez ici, mais d'autres actions peuvent inclure la prise ou la perte du focus, une opération du presse-papiers (couper/copier/coller) ou le défilement dans la vue. Le constructeur de cette classe nécessite une constante d'action (ici, AccessibilityNodeInfo.ACTION_CLICK) et une chaîne utilisée par TalkBack pour indiquer l'action.

  1. Remplacez la chaîne "placeholder" par un appel à context.getString() pour récupérer une ressource de chaîne. Pour la ressource spécifique, testez la vitesse actuelle du ventilateur. Si la vitesse est actuellement FanSpeed.HIGH, la chaîne est "Reset". Si la vitesse du ventilateur est différente, la chaîne est "Change.". Vous créerez ces ressources de chaîne à une étape ultérieure.
ViewCompat.setAccessibilityDelegate(this, object : AccessibilityDelegateCompat() {
   override fun onInitializeAccessibilityNodeInfo(host: View, 
                            info: AccessibilityNodeInfoCompat) {
      super.onInitializeAccessibilityNodeInfo(host, info)
      val customClick = AccessibilityNodeInfoCompat.AccessibilityActionCompat(
         AccessibilityNodeInfo.ACTION_CLICK,
        context.getString(if (fanSpeed !=  FanSpeed.HIGH) R.string.change else R.string.reset)
      )
   }  
})
  1. Après la parenthèse fermante de la définition customClick, utilisez la méthode addAction() pour ajouter la nouvelle action d'accessibilité à l'objet d'informations sur le nœud.
ViewCompat.setAccessibilityDelegate(this, object : AccessibilityDelegateCompat() {
   override fun onInitializeAccessibilityNodeInfo(host: View, 
                            info: AccessibilityNodeInfoCompat) {
       super.onInitializeAccessibilityNodeInfo(host, info)
       val customClick = AccessibilityNodeInfoCompat.AccessibilityActionCompat(
           AccessibilityNodeInfo.ACTION_CLICK,
           context.getString(if (fanSpeed !=  FanSpeed.HIGH) 
                                 R.string.change else R.string.reset)
       )
       info.addAction(customClick)
   }
})
  1. Dans res/values/strings.xml, ajoutez les ressources de chaîne pour "Change" (Modifier) et "Reset" (Réinitialiser).
<string name="change">Change</string>
<string name="reset">Reset</string>
  1. Compilez et exécutez l'application, puis assurez-vous que TalkBack est activé. Notez que la phrase "Appuyer deux fois pour activer" est désormais remplacée par "Appuyer deux fois pour modifier" (si la vitesse du ventilateur est inférieure à élevée ou 3) ou "Appuyer deux fois pour réinitialiser" (si la vitesse du ventilateur est déjà élevée ou 3). Notez que l'invite "Appuyez deux fois pour…" est fournie par le service TalkBack lui-même.

Téléchargez le code de l'atelier de programmation terminé.

$  git clone https://github.com/googlecodelabs/android-kotlin-drawing-custom-views


Vous pouvez également télécharger le dépôt sous forme de fichier ZIP, le décompresser et l'ouvrir dans Android Studio.

Télécharger le fichier ZIP

  • Pour créer une vue personnalisée qui hérite de l'apparence et du comportement d'une sous-classe View telle que EditText, ajoutez une classe qui étend cette sous-classe et apportez des modifications en remplaçant certaines méthodes de la sous-classe.
  • Pour créer une vue personnalisée de n'importe quelle taille et forme, ajoutez une classe qui étend View.
  • Remplacez les méthodes View telles que onDraw() pour définir la forme et l'apparence de base de la vue.
  • Utilisez invalidate() pour forcer le dessin ou le redessin de la vue.
  • Pour optimiser les performances, allouez des variables et attribuez les valeurs requises pour le dessin et la peinture avant de les utiliser dans onDraw(), par exemple lors de l'initialisation des variables membres.
  • Remplacez performClick() par OnClickListener() dans la vue personnalisée pour fournir le comportement interactif de la vue. Cela permet à vous ou à d'autres développeurs Android qui peuvent utiliser votre classe de vue personnalisée d'utiliser onClickListener() pour fournir un comportement supplémentaire.
  • Ajoutez la vue personnalisée à un fichier de mise en page XML avec des attributs pour définir son apparence, comme vous le feriez avec d'autres éléments d'interface utilisateur.
  • Créez le fichier attrs.xml dans le dossier values pour définir des attributs personnalisés. Vous pouvez ensuite utiliser les attributs personnalisés pour la vue personnalisée dans le fichier de mise en page XML.

Cours Udacity :

Documentation pour les développeurs Android :

Vidéos :

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.

Question 1

Quelle méthode remplacez-vous pour calculer les positions, les dimensions et toute autre valeur lorsque la taille est attribuée à la vue personnalisée pour la première fois ?

▢ onMeasure()

▢ onSizeChanged()

▢ invalidate()

▢ onDraw()

Question 2

Pour indiquer que vous souhaitez que votre vue soit redessinée avec onDraw(), quelle méthode appelez-vous à partir du thread d'UI, après qu'une valeur d'attribut a changé ?

▢ onMeasure()

▢ onSizeChanged()

▢ invalidate()

▢ getVisibility()

Question 3

Quelle méthode View devez-vous remplacer pour ajouter de l'interactivité à votre vue personnalisée ?

▢ setOnClickListener()

▢ onSizeChanged()

▢ isClickable()

▢ performClick()

Pour accéder aux autres ateliers de programmation de ce cours, consultez la page de destination des ateliers de programmation "Développement Android avancé en Kotlin".