Créer des vues personnalisées

Cet atelier de programmation fait partie du cours "Advanced Android" en langage Kotlin. Vous tirerez pleinement parti de ce cours si vous suivez les ateliers en séquence, mais ce n'est pas obligatoire. Tous les ateliers de programmation du cours sont répertoriés sur la page de destination des ateliers de programmation Android avancés sur 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 interface utilisateur qui permet l'interaction de l'utilisateur et afficher 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 (Button ou EditText, par exemple) ou créer votre propre sous-classe View. En étendant directement View, vous pouvez créer un élément d'interface utilisateur interactif de n'importe quelle taille et forme, en remplaçant la méthode onDraw() pour que View puisse la dessiner.

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 utilisez une TextView ou un Button.

Dans cette leçon, vous allez apprendre à créer entièrement une vue personnalisée en étendant View.

Ce que vous devez déjà savoir

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

Points abordés

  • Prolonger l'affichage de View pour créer une vue personnalisée
  • Comment dessiner une vue personnalisée de forme circulaire ?
  • Utiliser les écouteurs pour gérer les interactions des utilisateurs avec la vue personnalisée
  • Utiliser une vue personnalisée dans une mise en page

Objectifs de l'atelier

  • Développez View pour créer une vue personnalisée.
  • Initialisez la vue personnalisée avec des valeurs de dessin et de peinture.
  • Ignorez onDraw() pour dessiner la vue.
  • Utilisez les écouteurs pour fournir le comportement de la vue personnalisée.
  • Ajouter 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 physique du ventilateur, avec les paramètres "Désactivé" (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 et retour à 0. En outre, si la valeur 1 ou supérieure est sélectionnée, la couleur d'arrière-plan de la partie circulaire de la vue passe du gris au vert (indiquant que l'alimentation du ventilateur est activée).

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

Les composants de l'interface utilisateur, tels que Button et TextView, sont des sous-classes qui étendent la classe View. Pour gagner du temps et simplifier le 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, et vous pouvez remplacer le comportement ou l'aspect de l'apparence que vous souhaitez modifier. Par exemple, si vous étendez EditText pour créer une vue personnalisée, la vue fonctionne 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.

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

Pour créer votre propre affichage personnalisé, développez la classe View. Votre code remplace les méthodes View pour définir l'apparence et la fonctionnalité de la vue. Pour créer votre propre affichage personnalisé, il est essentiel de dessiner à l'écran tout l'élément d'interface utilisateur, quelle que soit sa taille et sa forme. Si vous sous-classez une vue existante telle que Button, cette classe gère le dessin pour vous. (Vous en apprendrez plus sur le dessin dans la suite de cet atelier de programmation.)

Pour créer une vue personnalisée, procédez comme suit:

  • Créez une classe de vue personnalisée qui étend View ou étend une sous-classe View (telle que Button ou EditText).
  • Si vous étendez une sous-classe View existante, ne modifiez que 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 personnalisée en tant que widget 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.
  • Développez View pour créer la vue personnalisée.
  • Initialisez la vue personnalisée avec des valeurs de dessin et de peinture.

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

  1. Créez une application Kotlin avec le titre CustomFanController à l'aide du modèle "Empty Activity" (Activité vide). Assurez-vous que le nom du package est com.example.android.customfancontroller.
  2. Ouvrez activity_main.xml dans l'onglet Text (Texte) pour modifier le code XML.
  3. Remplacez le fichier TextView existant par ce code. Ce texte fait office 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 créerez 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 Design. La mise en page devrait 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 constructeurs using '@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 la vitesse de ventilateur disponible. 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 corrigerez cela ultérieurement.
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. Ces éléments vous serviront à dessiner les indicateurs de numérotation 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. Importez android.graphics.PointF si nécessaire.
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)
  • L'radius est le rayon actuel du cercle. Cette valeur est définie lorsque la vue est affichée à l'écran.
  • La valeur fanSpeed correspond à la vitesse actuelle du ventilateur. Il s'agit de l'une des valeurs de l'énumération FanSpeed. Par défaut, cette valeur est OFF.
  • Enfin, postPosition est un point X,Y qui vous permettra de dessiner plusieurs éléments de la vue à l'écran.

Ces valeurs sont créées et initialisées ici plutôt que lorsque la vue est réellement dessinée pour garantir que l'étape de dessin réelle s'exécute aussi vite que 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 le demandez. Comme avec 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, celle-ci définit l'apparence et les attributs de la vue, puis se dessine à l'écran. Vous n'avez donc pas à écrire de code pour dessiner la vue. Vous pouvez toutefois 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 de l'affichage de la vue complète à chaque actualisation de l'écran, 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 lors de sa première affichage et, à chaque fois que cette taille change, en remplaçant la méthode onSizeChanged().
  • Ignorez la méthode onDraw() pour dessiner la vue personnalisée à l'aide d'un objet Canvas stylisé par un objet Paint.
  • Appelez la méthode invalidate() lorsque vous répondez à un clic d'utilisateur qui modifie la façon dont la vue est dessinée pour invalider l'ensemble de la vue, ce qui force l'appel de onDraw() à la redessiner.

La méthode onDraw() est appelée à chaque actualisation de l'écran, qui peut être plusieurs fois par seconde. Pour des raisons de performances et afin d'éviter les problèmes visuels, nous vous conseillons de faire le moins de travail possible dans onDraw(). En particulier, ne placez pas les allocations dans onDraw(), car les allocations peuvent entraîner une récupération de mémoire qui peut engendrer un bégaiement visuel.

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

Vous en apprendrez plus sur Canvas et Paint dans un prochain atelier de programmation. Pour en savoir plus sur la manière dont Android génère des vues, consultez la section Comment Android dessine des vues.

Dans cette tâche, vous allez afficher la vue personnalisée de la ventilation sur l'écran (le cadran proprement dit, l'indicateur de position actuel et les libellés des indicateurs) à l'aide des 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é d'indicateur sur le cadran.

Étape 1 : Calculer les positions et dessiner la vue

  1. Dans la classe DialView, sous les initialisations, ignorez la méthode onSizeChanged() de la classe View pour calculer la taille du numéro de la vue personnalisée. Importez kotlin.math.min sur demande.

    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. Ignorez onSizeChanged() pour calculer les positions, les dimensions et toutes les autres valeurs liées à la taille de votre vue personnalisée, au lieu de les recalculer à chaque dessin. Dans ce cas, vous utilisez onSizeChanged() pour calculer le rayon actuel de l'élément du cercle "Appeler".
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 le demandez. Cette fonction d'extension de la classe PointF calcule les coordonnées X, Y sur 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 cadran. 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 par les classes Canvas et Paint. Importez android.graphics.Canvas lorsque vous le demandez. 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 la peinture sur gris (Color.GRAY) ou vert (Color.GREEN), selon que la vitesse du ventilateur est égale à OFF ou une autre valeur. Importez android.graphics.Color lorsque vous le demandez.
// 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 sur le cadran avec la méthode drawCircle(). Cette méthode utilise la largeur et la hauteur actuelles de la vue pour trouver le centre du cercle, le rayon de ce dernier et sa couleur de peinture actuelle. Les propriétés width et height sont membres de la super-classe 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 tracer un cercle plus petit pour l'indicateur de vitesse du ventilateur, également associé à la méthode drawCircle(). Cette partie utilise PointF.Méthode de l'extension computeXYforSpeed() pour calculer les coordonnées X,Y pour le centre de l'indicateur en fonction de la vitesse actuelle du ventilateur.
// 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, tracez les étiquettes de vitesse du ventilateur (0, 1, 2, 3) aux endroits appropriés autour du cadran. Cette partie de la méthode appelle de nouveau PointF.computeXYForSpeed() pour obtenir la position pour chaque étiquette, 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)
}

La méthode onDraw() terminée 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'interface utilisateur d'une application, vous devez l'indiquer en tant qu'élément dans la mise en page XML de l'activité. Contrôlez son apparence et son comportement à l'aide des attributs d'élément XML, comme vous le feriez pour n'importe quel autre élément de l'interface utilisateur.

  1. Dans activity_main.xml, remplacez la balise ImageView du dialView par com.example.android.customfancontroller.DialView, puis supprimez l'attribut android:background. DialView et l'ImageView d'origine héritent des attributs standards de la classe View. Vous n'avez donc pas besoin 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 apparaît dans l'activité.

La dernière tâche consiste à permettre à votre vue personnalisée d'effectuer une action lorsque l'utilisateur appuie sur la vue. Chaque pression doit déplacer l'indicateur de sélection jusqu'à la position suivante: arrêt-1-2-3 et retour éteint. De plus, si la sélection est 1 ou une valeur supérieure, faites passer l'arrière-plan du gris au vert pour indiquer que l'alimentation du ventilateur est allumée.

Pour que les utilisateurs puissent cliquer sur votre vue personnalisée, procédez comme suit:

  • Définissez la propriété isClickable de la vue sur true. Votre vue personnalisée peut ainsi répondre aux clics.
  • Implémentez les View de la classe performClick() 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 pouvez implémenter OnClickListener() pour effectuer une action lorsque l'utilisateur clique sur cette vue. Pour obtenir une vue personnalisée, vous devez plutôt implémenter la méthode performClick() View et appeler 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 plus avancée par vous ou d'autres développeurs susceptibles d'utiliser votre vue personnalisée.

  1. Dans DialView.kt, dans l'énumération FanSpeed, ajoutez une fonction d'extension next() qui passe la vitesse du ventilateur actuel à la vitesse suivante dans la liste (de OFF à LOW, MEDIUM et HIGH), puis à OFF. L'énumération complète ressemble à 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(). Définir la propriété isClickable de la vue sur "true" permet à cette vue d'accepter l'entrée utilisateur.
init {
   isClickable = true
}
  1. En dessous de 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.Le performClick() doit d'abord être activé, ce qui permet d'activer les événements d'accessibilité et d'appeler onClickListener().

Les deux lignes suivantes augmentent 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 (désactivé, 1, 2 ou 3).

En fin de compte, la méthode invalidate() invalide l'ensemble de la vue, forçant un appel à onDraw() pour la reconstituer. Si quelque chose change dans votre vue personnalisée, quelle qu'en soit la raison, y compris l'interaction de l'utilisateur, et que la modification doit être affichée, appelez invalidate()..

  1. Exécutez l'application. Appuyez sur l'élément DialView pour désactiver l'indicateur. Le cadran doit passer au vert. À chaque pression, l'indicateur doit passer à la position suivante. Lorsque l'indicateur s'éteint, le cadran doit de nouveau devenir gris.

Cet exemple montre comment utiliser les attributs personnalisés avec votre vue personnalisée. Définissez des attributs personnalisés pour la classe DialView avec une couleur différente pour chaque position de ventilateur.

  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 <declare-styleable>, ajoutez trois éléments attr, un pour chaque attribut, avec un name et un format. format est un type. 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 affichées ci-dessous. Utilisez app: comme préface de l'attribut personnalisé (comme dans app:fanColor1) plutôt que android:, car ces attributs appartiennent à l'espace de noms schemas.android.com/apk/res/your_app_package_name et non à l'espace de noms android.
app:fanColor1="#FFEB3B"
app:fanColor2="#CDDC39"
app:fanColor3="#009688"

Pour utiliser les attributs dans votre classe DialView, vous devez les récupérer. Ils sont stockés dans un AttributeSet, qui est transmis à votre classe lors de sa création, le cas échéant. Vous récupérez les attributs dans init, puis attribuez leurs valeurs à 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 d'attribut.
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, puis vous définissez vos variables locales. Le fait d'importer withStyledAttributes importe é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 cadran en fonction de la vitesse actuelle du ventilateur. Remplacez la ligne où la couleur de la 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 cadran et le paramètre de couleur doit être différent pour chaque position, comme illustré ci-dessous.

Pour en savoir plus sur les attributs de la vue personnalisée, consultez l'article Créer une classe de vue.

L'accessibilité est un ensemble de techniques de conception, de mise en œuvre et de test qui permettent à tous les utilisateurs d'utiliser votre application, y compris celles ayant un handicap.

Les handicaps courants susceptibles d'affecter l'utilisation d'un appareil Android incluent la cécité, la déficience visuelle, le daltonisme, la surdité et les pertes auditives, ainsi que les capacités motrices limitées. Lorsque vous développez vos applications dans un souci d'accessibilité, vous contribuez à améliorer l'expérience utilisateur non seulement pour les personnes en situation de handicap, mais aussi pour tous les autres.

Android offre plusieurs fonctionnalités d'accessibilité par défaut dans les vues d'interface utilisateur standards telles que TextView et Button. Toutefois, lorsque vous créez une vue personnalisée, vous devez prendre en compte les fonctionnalités accessibles telles que les descriptions vocales du contenu à l'écran.

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

Étape 1 : Découvrir TalkBack

TalkBack est un lecteur d'écran intégré à Android. Lorsque TalkBack est activé, l'utilisateur peut interagir avec son appareil Android sans afficher l'écran, car Android décrit à voix haute les éléments de son écran. Les utilisateurs malvoyants peuvent utiliser TalkBack pour utiliser votre application.

Dans cette tâche, vous allez activer TalkBack pour comprendre le fonctionnement des lecteurs d'écran et la navigation dans les applications.

  1. Sur un appareil Android ou un émulateur, accédez à Settings & gt, Accessibility > TalkBack (Paramètres &gt, Accessibilité et TalkBack).
  2. Appuyez sur le bouton pour activer ou désactiver TalkBack.
  3. Appuyez sur OK pour confirmer les autorisations.
  4. Confirmez le mot de passe de votre appareil, si vous y êtes invité. Si vous utilisez TalkBack pour la première fois, 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 à l'avenir, accédez à Paramètres > Accessibilité et gt; TalkBack > Paramètres > Lancer le tutoriel TalkBack.
  6. Compilez et exécutez l'application CustomFanController, ou ouvrez-la à l'aide du bouton Aperçu ou Éléments 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 fournie sur l'état de la vue (paramètre actuel pour le cadran) ni l'action qui s'effectue lorsque vous appuyez sur la vue pour l'activer.

Étape 2. Ajouter des descriptions de contenu pour les libellés téléphoniques

La description du contenu décrit la signification et l'objectif des vues de 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 comme 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 de contenu.

Dans le cas de la vue personnalisée du ventilateur, vous devez mettre à jour de façon dynamique la description du contenu chaque fois que l'utilisateur clique sur la vue, afin d'indiquer le paramètre actuel.

  1. En bas de la classe DialView, déclarez une fonction updateContentDescription() sans argument 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 actuel (désactivée, 1, 2 ou 3). Ce sont les mêmes libellés que ceux utilisés dans onDraw() lorsque le numéro composé 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. Cette commande initialise la description du contenu lors de l'initialisation de la vue.
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 vérifiez que TalkBack est activé. Tapotez pour modifier le paramètre de la vue par cadran et notez que TalkBack annonce désormais le libellé actuel (désactivé, 1, 2, 3) ainsi que l'expression &appuyez deux fois dessus pour l'activer.

Étape 3. Ajouter davantage d'informations pour l'action de clic

Vous pouvez vous arrêter là, et votre vue pourra être utilisée dans TalkBack. Toutefois, il serait utile que votre vue puisse indiquer que vous pouvez l'activer (et appuyer deux fois pour l'activer), mais aussi que vous expliquerez ce qui se produira lorsque vous l'activerez (appuyez deux fois pour la modifier).

Pour cela, vous ajoutez des informations sur l'action de la vue (ici, un clic ou une pression) sur un objet d'informations sur le nœud d'accessibilité, à l'aide d'un délégué. Un délégué sur l'accessibilité vous permet de personnaliser les fonctionnalités d'accessibilité de votre application via la composition (au lieu de l'héritage).

Pour cette tâche, vous allez utiliser les classes d'accessibilité dans les bibliothèques Android Jetpack (androidx.*) pour assurer la rétrocompatibilité.

  1. Dans DialView.kt, dans le bloc init, définissez un délégué sur l'accessibilité dans la vue en tant que nouvel objet AccessibilityDelegateCompat. Importez androidx.core.view.ViewCompat et androidx.core.view.AccessibilityDelegateCompat lorsque vous le demandez. Cette stratégie offre 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 super&s. 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 possède une arborescence de nœuds d'accessibilité, qui peuvent correspondre ou non 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 (comme les descriptions de contenu pouvant être énoncées ou les actions possibles sur cette vue). Lorsque vous créez une vue personnalisée, il peut être nécessaire de remplacer les informations du 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 "view's".

  1. Dans onInitializeAccessibilityNodeInfo(), créez un objet AccessibilityNodeInfoCompat.AccessibilityActionCompat et attribuez-le à la variable customClick. Transmettez au constructeur la constante AccessibilityNodeInfo.ACTION_CLICK et une chaîne d'espace réservé. Importez AccessibilityNodeInfo à la demande.
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 l'obtention ou la perte du curseur, 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 (AccessibilityNodeInfo.ACTION_CLICK, ici) 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 en question, testez le débit actuel du ventilateur. Si la vitesse est actuellement de FanSpeed.HIGH, la chaîne est "Reset". Si la vitesse du ventilateur est différente, la chaîne est "Change.". Vous allez créer ces ressources de chaîne plus tard.
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 les parenthèses de fermeture 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" et "Reset" et réinitialisez-les.
<string name="change">Change</string>
<string name="reset">Reset</string>
  1. Compilez et exécutez l'application et assurez-vous que TalkBack est activé. Comme vous pouvez le constater, l'expression "Appuyer deux fois pour activer" est désormais associée à "Appuyer deux fois pour modifier" (si la vitesse du ventilateur est inférieure à 3) ou "Appuyer deux fois pour la réinitialiser". Notez que l'invite "Appuyer deux fois pour..." est fournie par le service TalkBack.

Téléchargez le code pour 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 ajustements 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-y une classe qui étend View.
  • Ignorez les méthodes View telles que onDraw() pour définir la forme et l'apparence de base de la vue.
  • Forcez l'affichage ou le dessin de la vue à l'aide de invalidate().
  • Pour optimiser les performances, attribuez des variables et attribuez-leur les valeurs de dessin et de peinture avant de les utiliser dans onDraw(), par exemple pour initialiser les variables de membre.
  • Ignorez performClick() au lieu de OnClickListener() de la vue personnalisée pour fournir le comportement interactif de la vue. Cela permet, à vous ou à vos autres développeurs Android, d'utiliser votre classe de vue personnalisée afin d'utiliser onClickListener() pour renforcer votre comportement.
  • 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 les attributs personnalisés. Vous pouvez ensuite utiliser les attributs personnalisés de 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 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.

Question 1

Pour calculer les positions, les dimensions et toute autre valeur lorsque la taille de l'affichage personnalisé est d'abord attribuée, quelle méthode ignorez-vous ?

onMeasure()

onSizeChanged()

invalidate()

onDraw()

Question 2

Pour indiquer que vous souhaitez redessiner votre vue avec onDraw(), quelle méthode appelez-vous à partir du thread UI après la modification d'une valeur d'attribut ?

▢ 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 obtenir des liens vers d'autres ateliers de programmation dans ce cours, consultez la page de destination "Avancé Android" dans les ateliers de programmation Kotlin.