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
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-classeView
(telle queButton
ouEditText
). - 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éthodesView
telles queonDraw()
etonMeasure()
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
- 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 estcom.example.android.customfancontroller
. - Ouvrez
activity_main.xml
dans l'onglet Text (Texte) pour modifier le code XML. - 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"/>
- 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"/>
- Extrayez les ressources de chaîne et de dimension dans les deux éléments d'interface utilisateur.
- 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
- Créez une classe Kotlin appelée
DialView
. - Modifiez la définition de la classe pour étendre
View
. Importezandroid.view.View
lorsque vous y êtes invité. - Cliquez sur
View
, puis sur l'ampoule rouge. Choisissez Add Android View constructeurs using '@JvmOverloads'. Android Studio ajoute le constructeur à partir de la classeView
. 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) {
- Au-dessus de la définition de la classe
DialView
, juste en dessous des importations, ajoutez unenum
de premier niveau pour représenter la vitesse de ventilateur disponible. Notez que ceenum
est de typeInt
, 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);
}
- 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
- Dans la classe
DialView
, définissez plusieurs variables dont vous avez besoin pour dessiner la vue personnalisée. Importezandroid.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érationFanSpeed
. Par défaut, cette valeur estOFF
. - 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.
- Toujours dans la définition de la classe
DialView
, initialisez un objetPaint
avec quelques styles de base. Importezandroid.graphics.Paint
etandroid.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)
}
- 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 objetCanvas
stylisé par un objetPaint
. - 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 deonDraw()
à 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:
- Dessinez du texte avec
drawText()
. Spécifiez la police de caractères en appelantsetTypeface()
et la couleur du texte en appelantsetColor()
. - Dessinez des formes primitives en utilisant
drawRect()
,drawOval()
etdrawArc()
. Indiquez si les formes sont remplies, tracées ou les deux en appelantsetStyle()
. - Dessinez des bitmaps à l'aide de
drawBitmap()
.
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
- Dans la classe
DialView
, sous les initialisations, ignorez la méthodeonSizeChanged()
de la classeView
pour calculer la taille du numéro de la vue personnalisée. Importezkotlin
.math.min
sur demande.
La méthodeonSizeChanged()
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. IgnorezonSizeChanged()
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 utilisezonSizeChanged()
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()
}
- Sous
onSizeChanged()
, ajoutez ce code pour définir une fonction d'extensioncomputeXYForSpeed()
pour la classePointF
. Importezkotlin.math.cos
etkotlin.math.sin
lorsque vous le demandez. Cette fonction d'extension de la classePointF
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 positionFanSpeed
actuelle et du rayon du cadran. Vous l'utiliserez dansonDraw().
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
}
- Remplacez la méthode
onDraw()
pour afficher la vue à l'écran par les classesCanvas
etPaint
. Importezandroid.graphics.Canvas
lorsque vous le demandez. Voici le remplacement du squelette:
override fun onDraw(canvas: Canvas) {
super.onDraw(canvas)
}
- 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. Importezandroid.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
- 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éswidth
etheight
sont membres de la super-classeView
et indiquent les dimensions actuelles de la vue.
// Draw the dial.
canvas.drawCircle((width / 2).toFloat(), (height / 2).toFloat(), radius, paint)
- 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 utilisePointF
.Méthode de l'extensioncomputeXYforSpeed()
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)
- 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'objetpointPosition
chaque fois pour éviter les allocations. UtilisezdrawText()
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.
- Dans
activity_main.xml
, remplacez la baliseImageView
dudialView
parcom.example.android.customfancontroller.DialView
, puis supprimez l'attributandroid:background
.DialView
et l'ImageView
d'origine héritent des attributs standards de la classeView
. Vous n'avez donc pas besoin de modifier les autres attributs. Le nouvel élémentDialView
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" />
- 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 surtrue
. Votre vue personnalisée peut ainsi répondre aux clics. - Implémentez les
View
de la classeperformClick
()
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éthodeonDraw()
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.
- Dans
DialView.kt
, dans l'énumérationFanSpeed
, ajoutez une fonction d'extensionnext()
qui passe la vitesse du ventilateur actuel à la vitesse suivante dans la liste (deOFF
àLOW
,MEDIUM
etHIGH
), 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
}
}
- Dans la classe
DialView
, juste avant la méthodeonSizeChanged()
, ajoutez un blocinit()
. Définir la propriétéisClickable
de la vue sur "true" permet à cette vue d'accepter l'entrée utilisateur.
init {
isClickable = true
}
- En dessous de
init(),
, remplacez la méthodeperformClick()
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().
.
- 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.
- Créez et ouvrez
res/values/attrs.xml
. - Dans
<resources>
, ajoutez un élément de ressource<declare-styleable>
. - Dans l'élément
<declare-styleable>
, ajoutez trois élémentsattr
, un pour chaque attribut, avec unname
et unformat
.format
est un type. Dans ce cas, il s'agit decolor
.
<?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>
- Ouvrez le fichier de mise en page
activity_main.xml
. - Dans
DialView
, ajoutez des attributs pourfanColor1
,fanColor2
etfanColor3
, puis définissez leurs valeurs sur les couleurs affichées ci-dessous. Utilisezapp:
comme préface de l'attribut personnalisé (comme dansapp:fanColor1
) plutôt queandroid:
, car ces attributs appartiennent à l'espace de nomsschemas.android.com/apk/res/
your_app_package_name
et non à l'espace de nomsandroid
.
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.
- Ouvrez le fichier de classe
DialView.kt
. - 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
- Dans le bloc
init
, ajoutez le code suivant à l'aide de la fonction d'extensionwithStyledAttributes
. Vous fournissez les attributs et la vue, puis vous définissez vos variables locales. Le fait d'importerwithStyledAttributes
importe également la fonctiongetColor()
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)
}
- 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
- 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.
- Sur un appareil Android ou un émulateur, accédez à Settings & gt, Accessibility > TalkBack (Paramètres >, Accessibilité et TalkBack).
- Appuyez sur le bouton pour activer ou désactiver TalkBack.
- Appuyez sur OK pour confirmer les autorisations.
- 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.
- 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.
- 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 vueDialView
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.
- En bas de la classe
DialView
, déclarez une fonctionupdateContentDescription()
sans argument ni type de retour.
fun updateContentDescription() {
}
- 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 dansonDraw()
lorsque le numéro composé est affiché à l'écran.
fun updateContentDescription() {
contentDescription = resources.getString(fanSpeed.label)
}
- 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()
}
- Ajoutez un autre appel à
updateContentDescription()
dans la méthodeperformClick()
, juste avantinvalidate()
.
override fun performClick(): Boolean {
if (super.performClick()) return true
fanSpeed = fanSpeed.next()
updateContentDescription()
invalidate()
return true
}
- 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é.
- Dans
DialView.kt
, dans le blocinit
, définissez un délégué sur l'accessibilité dans la vue en tant que nouvel objetAccessibilityDelegateCompat
. Importezandroidx.core.view.ViewCompat
etandroidx.core.view.AccessibilityDelegateCompat
lorsque vous le demandez. Cette stratégie offre la plus grande rétrocompatibilité dans votre application.
ViewCompat.setAccessibilityDelegate(this, object : AccessibilityDelegateCompat() {
})
- Dans l'objet
AccessibilityDelegateCompat
, remplacez la fonctiononInitializeAccessibilityNodeInfo()
par un objetAccessibilityNodeInfoCompat
et appelez la méthode super&s. Importezandroidx.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".
- Dans
onInitializeAccessibilityNodeInfo()
, créez un objetAccessibilityNodeInfoCompat.AccessibilityActionCompat
et attribuez-le à la variablecustomClick
. Transmettez au constructeur la constanteAccessibilityNodeInfo.ACTION_CLICK
et une chaîne d'espace réservé. ImportezAccessibilityNodeInfo
à 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.
- 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 deFanSpeed.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)
)
}
})
- Après les parenthèses de fermeture de la définition
customClick
, utilisez la méthodeaddAction()
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)
}
})
- 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>
- 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.
- Pour créer une vue personnalisée qui hérite de l'apparence et du comportement d'une sous-classe
View
, telle queEditText
, 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 queonDraw()
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 deOnClickListener
() 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'utiliseronClickListener()
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 dossiervalues
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:
- Créer des vues personnalisées
@JvmOverloads
- Composants personnalisés
- Comment Android dessine des vues
onMeasure()
onSizeChanged()
onDraw()
Canvas
Paint
drawText()
setTypeface()
setColor()
drawRect()
drawOval()
drawArc()
drawBitmap()
setStyle()
invalidate()
- Afficher
- Événements d'entrée
- Peinture
- Bibliothèque d'extensions Kotlin android-ktx
withStyledAttributes
- Documentation sur Android KTX
- Annonce d'origine Android KTX
- Rendre les vues personnalisées plus accessibles
AccessibilityDelegateCompat
AccessibilityNodeInfoCompat
AccessibilityNodeInfoCompat.AccessibilityActionCompat
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.