Rendre les vues personnalisées plus accessibles

Si votre application nécessite un composant de vue personnalisée, vous devez rendre la vue plus accessible. Les étapes suivantes peuvent améliorer l'accessibilité de votre vue personnalisée, comme décrit sur cette page :

  • Gérer les clics à l'aide d'un contrôleur directionnel
  • Implémenter les méthodes de l'API d'accessibilité
  • Envoyer des objets AccessibilityEvent spécifiques à la vue personnalisée
  • Renseigner AccessibilityEvent et AccessibilityNodeInfo pour votre vue

Gérer les clics à l'aide d'un contrôleur directionnel

Sur la plupart des appareils, un clic sur une vue à l'aide d'un contrôleur directionnel envoie un événement KeyEvent avec KEYCODE_DPAD_CENTER à la vue active. Toutes les vues Android standards gèrent déjà KEYCODE_DPAD_CENTER de manière appropriée. Lorsque vous créez une commande View personnalisée, assurez-vous que cet événement a le même effet que lorsque vous appuyez sur la vue sur l'écran tactile.

La commande personnalisée doit traiter l'événement KEYCODE_ENTER de la même manière que KEYCODE_DPAD_CENTER. Cela permet aux utilisateurs d'interagir plus facilement avec un clavier complet.

Implémenter les méthodes de l'API d'accessibilité

Les événements d'accessibilité sont des messages sur les interactions des utilisateurs avec les composants de l'interface visuelle de votre application. Ces messages sont gérés par des services d'accessibilité, qui utilisent les informations liées à ces événements pour générer des retours et des invites supplémentaires. Les méthodes d'accessibilité font partie des classes View et View.AccessibilityDelegate. Les méthodes sont les suivantes :

dispatchPopulateAccessibilityEvent()
Le système appelle cette méthode lorsque votre vue personnalisée génère un événement d'accessibilité. L'implémentation par défaut de cette méthode appelle onPopulateAccessibilityEvent() pour cette vue, puis la méthode dispatchPopulateAccessibilityEvent() pour chaque enfant de cette vue.
onInitializeAccessibilityEvent()
Le système appelle cette méthode pour obtenir des informations supplémentaires sur l'état de la vue au-delà du contenu textuel. Si votre vue personnalisée offre une commande d'interaction au-delà d'une simple méthode TextView ou Button, remplacez cette méthode et définissez les informations supplémentaires concernant la vue, telles que le type de champ de mot de passe, le type de case à cocher ou les états qui fournissent des interactions ou des retours utilisateur à l'événement, à l'aide de cette méthode. Si vous remplacez cette méthode, appelez sa super-implémentation et ne modifiez que les propriétés qui ne sont pas définies par la super-classe.
onInitializeAccessibilityNodeInfo()
Cette méthode fournit aux services d'accessibilité des informations sur l'état de la vue. L'implémentation par défaut de View comporte un ensemble standard de propriétés de vue. Toutefois, si votre vue personnalisée fournit une commande d'interaction au-delà d'un simple élément TextView ou Button, remplacez cette méthode et définissez les informations supplémentaires correspondant à votre vue dans l'objet AccessibilityNodeInfo géré par la méthode.
onPopulateAccessibilityEvent()
Cette méthode définit l'invite de texte vocale de l'événement AccessibilityEvent pour votre vue. Elle est également appelée si la vue est un enfant d'une vue qui génère un événement d'accessibilité.
onRequestSendAccessibilityEvent()
Le système appelle cette méthode lorsqu'un enfant de votre vue génère une AccessibilityEvent. Cette étape permet à la vue parent de modifier l'événement d'accessibilité avec des informations supplémentaires. Ne mettez en œuvre cette méthode que si la vue personnalisée peut avoir des vues enfants et que la vue parent peut fournir à l'événement d'accessibilité des informations contextuelles qui pourraient être utiles aux services d'accessibilité.
sendAccessibilityEvent()
Le système appelle cette méthode lorsqu'un utilisateur effectue une action sur une vue. L'événement est classé avec un type d'action utilisateur tel que TYPE_VIEW_CLICKED. En général, vous devez envoyer un événement AccessibilityEvent chaque fois que le contenu de la vue personnalisée change.
sendAccessibilityEventUnchecked()
Cette méthode est utilisée lorsque le code d'appel doit contrôler directement la vérification de l'activation de l'accessibilité sur l'appareil (AccessibilityManager.isEnabled()). Si vous implémentez cette méthode, vous devez effectuer l'appel comme si l'accessibilité était activée, quel que soit le paramètre système. Vous n'avez généralement pas besoin d'implémenter cette méthode pour une vue personnalisée.

Pour assurer l'accessibilité, remplacez et implémentez les méthodes d'accessibilité précédentes directement dans la classe de la vue personnalisée.

Implémentez au minimum les méthodes d'accessibilité suivantes pour la classe de votre vue personnalisée :

  • dispatchPopulateAccessibilityEvent()
  • onInitializeAccessibilityEvent()
  • onInitializeAccessibilityNodeInfo()
  • onPopulateAccessibilityEvent()

Pour en savoir plus sur la mise en œuvre de ces méthodes, consultez la section Renseigner les événements d'accessibilité.

Envoyer des événements d'accessibilité

En fonction des spécificités de votre vue personnalisée, il peut être nécessaire d'envoyer des objets AccessibilityEvent à différents moments ou pour des événements qui ne sont pas gérés par l'implémentation par défaut. La classe View fournit une implémentation par défaut pour ces types d'événements :

En général, vous devez envoyer un événement AccessibilityEvent chaque fois que le contenu de la vue personnalisée change. Par exemple, si vous mettez en œuvre un curseur personnalisé permettant à l'utilisateur de sélectionner une valeur numérique en appuyant sur les flèches vers la gauche ou vers la droite, la vue personnalisée doit émettre un événement de type TYPE_VIEW_TEXT_CHANGED chaque fois que la valeur du curseur change. L'exemple de code suivant illustre l'utilisation de la méthode sendAccessibilityEvent() pour signaler cet événement.

Kotlin

override fun onKeyUp(keyCode: Int, event: KeyEvent): Boolean {
    return when(keyCode) {
        KeyEvent.KEYCODE_DPAD_LEFT -> {
            currentValue--
            sendAccessibilityEvent(AccessibilityEvent.TYPE_VIEW_TEXT_CHANGED)
            true
        }
        ...
    }
}

Java

@Override
public boolean onKeyUp (int keyCode, KeyEvent event) {
    if (keyCode == KeyEvent.KEYCODE_DPAD_LEFT) {
        currentValue--;
        sendAccessibilityEvent(AccessibilityEvent.TYPE_VIEW_TEXT_CHANGED);
        return true;
    }
    ...
}

Renseigner les événements d'accessibilité

Chaque AccessibilityEvent possède un ensemble de propriétés obligatoires qui décrivent l'état actuel de la vue. Ces propriétés incluent des éléments tels que le nom de classe de la vue, la description du contenu et l'état (coché ou non). Les propriétés spécifiques à chaque type d'événement sont décrites dans la documentation de référence sur AccessibilityEvent.

L'implémentation de View fournit des valeurs par défaut pour ces propriétés obligatoires. La plupart de ces valeurs, y compris le nom de la classe et l'horodatage de l'événement, sont fournies automatiquement. Si vous créez un composant de vue personnalisée, vous devez indiquer des informations sur le contenu et les caractéristiques de la vue. Ces informations peuvent être un simple libellé de bouton et inclure les informations d'état supplémentaires que vous souhaitez ajouter à l'événement.

Utilisez les méthodes onPopulateAccessibilityEvent() et onInitializeAccessibilityEvent() pour renseigner ou modifier les informations dans un élément AccessibilityEvent. Utilisez la méthode onPopulateAccessibilityEvent() spécifiquement pour ajouter ou modifier le contenu textuel de l'événement, qui sera converti en invites audibles par des services d'accessibilité tels que TalkBack. Utilisez la méthode onInitializeAccessibilityEvent() pour renseigner des informations supplémentaires sur l'événement, telles que l'état de sélection de la vue.

En outre, implémentez la méthode onInitializeAccessibilityNodeInfo(). Les services d'accessibilité utilisent les objets AccessibilityNodeInfo renseignés par cette méthode pour examiner la hiérarchie des vues qui génère un événement d'accessibilité après sa réception et fournir des retours appropriés aux utilisateurs.

L'exemple de code suivant montre comment remplacer ces trois méthodes dans votre vue :

Kotlin

override fun onPopulateAccessibilityEvent(event: AccessibilityEvent?) {
    super.onPopulateAccessibilityEvent(event)
    // Call the super implementation to populate its text for the
    // event. Then, add text not present in a super class.
    // You typically only need to add the text for the custom view.
    if (text?.isNotEmpty() == true) {
        event?.text?.add(text)
    }
}

override fun onInitializeAccessibilityEvent(event: AccessibilityEvent?) {
    super.onInitializeAccessibilityEvent(event)
    // Call the super implementation to let super classes
    // set appropriate event properties. Then, add the new checked
    // property that is not supported by a super class.
    event?.isChecked = isChecked()
}

override fun onInitializeAccessibilityNodeInfo(info: AccessibilityNodeInfo?) {
    super.onInitializeAccessibilityNodeInfo(info)
    // Call the super implementation to let super classes set
    // appropriate info properties. Then, add the checkable and checked
    // properties that are not supported by a super class.
    info?.isCheckable = true
    info?.isChecked = isChecked()
    // You typically only need to add the text for the custom view.
    if (text?.isNotEmpty() == true) {
        info?.text = text
    }
}

Java

@Override
public void onPopulateAccessibilityEvent(AccessibilityEvent event) {
    super.onPopulateAccessibilityEvent(event);
    // Call the super implementation to populate its text for the
    // event. Then, add the text not present in a super class.
    // You typically only need to add the text for the custom view.
    CharSequence text = getText();
    if (!TextUtils.isEmpty(text)) {
        event.getText().add(text);
    }
}

@Override
public void onInitializeAccessibilityEvent(AccessibilityEvent event) {
    super.onInitializeAccessibilityEvent(event);
    // Call the super implementation to let super classes
    // set appropriate event properties. Then, add the new checked
    // property that is not supported by a super class.
    event.setChecked(isChecked());
}

@Override
public void onInitializeAccessibilityNodeInfo(AccessibilityNodeInfo info) {
    super.onInitializeAccessibilityNodeInfo(info);
    // Call the super implementation to let super classes set
    // appropriate info properties. Then, add the checkable and checked
    // properties that are not supported by a super class.
    info.setCheckable(true);
    info.setChecked(isChecked());
    // You typically only need to add the text for the custom view.
    CharSequence text = getText();
    if (!TextUtils.isEmpty(text)) {
        info.setText(text);
    }
}

Vous pouvez implémenter ces méthodes directement dans la classe de la vue personnalisée.

Fournir un contexte d'accessibilité personnalisé

Les services d'accessibilité peuvent inspecter la hiérarchie des vues associées à un composant d'interface utilisateur qui génère un événement d'accessibilité. Cela permet aux services d'accessibilité de fournir des informations contextuelles plus détaillées afin d'aider les utilisateurs.

Dans certains cas, les services d'accessibilité ne parviennent pas à obtenir des informations adéquates de la hiérarchie des vues. Imaginons une commande d'interface personnalisée comportant au moins deux zones cliquables distinctes (une commande d'agenda, par exemple). Dans ce cas, les services ne peuvent pas obtenir d'informations adéquates, car les sous-sections cliquables ne font pas partie de la hiérarchie des vues.

Figure 1 : Vue d'agenda personnalisée avec des éléments "jour" sélectionnables

Dans l'exemple de la figure 1, l'agenda complet est implémenté comme une vue unique. Par conséquent, les services d'accessibilité ne reçoivent pas suffisamment d'informations sur le contenu de la vue et sur la sélection de l'utilisateur dans la vue, sauf si le développeur fournit des informations supplémentaires. Par exemple, si un utilisateur clique sur le jour 17, le framework d'accessibilité reçoit uniquement les informations de description pour la commande de l'intégralité de l'agenda. Dans ce cas, le service d'accessibilité TalkBack annonce "Agenda" ou "Agenda d'avril", mais l'utilisateur ne sait pas quel jour est sélectionné.

Afin de fournir des informations contextuelles adéquates pour les services d'accessibilité dans de telles situations, le framework fournit un moyen de spécifier une hiérarchie des vues virtuelle. Une hiérarchie des vues virtuelle permet aux développeurs d'applications de fournir aux services d'accessibilité une hiérarchie de vues complémentaire qui correspond plus précisément aux informations affichées à l'écran. Cette approche permet aux services d'accessibilité de fournir des informations contextuelles plus utiles aux utilisateurs.

Autre exemple : une hiérarchie des vues virtuelle peut être nécessaire lorsqu'une interface utilisateur contient un ensemble de commandes View ayant des fonctions étroitement liées, et qu'une action sur une commande affecte le contenu d'un ou de plusieurs éléments (tel qu'un sélecteur de nombre avec des boutons "Haut" et "Bas" séparés). Dans ce cas, les services d'accessibilité ne peuvent pas obtenir d'informations adéquates, car une action sur une commande modifie le contenu d'une autre, et la relation entre ces commandes n'est pas forcément apparente au service.

Pour gérer cette situation, regroupez les commandes associées avec une vue conteneurisée et fournissez une hiérarchie de vues virtuelle à partir de ce conteneur afin de représenter clairement les informations et le comportement fournis par ces commandes.

Afin de fournir une hiérarchie virtuelle pour une vue, remplacez la méthode getAccessibilityNodeProvider() dans votre vue ou votre groupe de vues personnalisé, puis renvoyez une implémentation de AccessibilityNodeProvider. Vous pouvez implémenter une hiérarchie des vues virtuelle à l'aide de la bibliothèque Support avec la méthode ViewCompat.getAccessibilityNodeProvider(), et fournir une implémentation avec AccessibilityNodeProviderCompat.

Pour simplifier de nombreux aspects liés à l'envoi d'informations aux services d'accessibilité et à la gestion du ciblage de l'accessibilité, vous pouvez implémenter ExploreByTouchHelper à la place, qui fournit un élément AccessibilityNodeProviderCompat et qui peut être associé en tant qu'AccessibilityDelegateCompat d'une vue en appelant setAccessibilityDelegate. Pour obtenir un exemple, consultez ExploreByTouchHelperActivity. ExploreByTouchHelper est également utilisé par les widgets de framework tels que CalendarView via sa vue enfant SimpleMonthView.

Gérer les événements tactiles personnalisés

Les commandes d'affichage personnalisées peuvent nécessiter un comportement d'événement tactile non standard, comme illustré dans les exemples suivants.

Définir des actions basées sur les clics

Si votre widget utilise l'interface OnClickListener ou OnLongClickListener, le système gère les actions ACTION_CLICK et ACTION_LONG_CLICK pour vous. Si votre application utilise un widget plus personnalisé qui repose sur l'interface OnTouchListener, définissez des gestionnaires personnalisés pour les actions d'accessibilité basées sur les clics. Pour ce faire, appelez la méthode replaceAccessibilityAction() pour chaque action, comme indiqué dans l'extrait de code suivant :

Kotlin

override fun onCreate(savedInstanceState: Bundle?) {
    ...

    // Assumes that the widget is designed to select text when tapped, and selects
    // all text when tapped and held. In its strings.xml file, this app sets
    // "select" to "Select" and "select_all" to "Select all".
    ViewCompat.replaceAccessibilityAction(
        binding.textSelectWidget,
        ACTION_CLICK,
        getString(R.string.select)
    ) { view, commandArguments ->
        selectText()
    }

    ViewCompat.replaceAccessibilityAction(
        binding.textSelectWidget,
        ACTION_LONG_CLICK,
        getString(R.string.select_all)
    ) { view, commandArguments ->
        selectAllText()
    }
}

Java

@Override
protected void onCreate(Bundle savedInstanceState) {
    ...

    // Assumes that the widget is designed to select text when tapped, and select
    // all text when tapped and held. In its strings.xml file, this app sets
    // "select" to "Select" and "select_all" to "Select all".
    ViewCompat.replaceAccessibilityAction(
            binding.textSelectWidget,
            ACTION_CLICK,
            getString(R.string.select),
            (view, commandArguments) -> selectText());

    ViewCompat.replaceAccessibilityAction(
            binding.textSelectWidget,
            ACTION_LONG_CLICK,
            getString(R.string.select_all),
            (view, commandArguments) -> selectAllText());
}

Créer des événements de clic personnalisés

Une commande personnalisée peut utiliser la méthode d'écouteur onTouchEvent(MotionEvent) pour détecter les événements ACTION_DOWN et ACTION_UP, et déclencher un événement de clic spécial. Pour assurer la compatibilité avec les services d'accessibilité, le code qui gère cet événement de clic personnalisé doit effectuer les actions suivantes :

  1. Générer un événement AccessibilityEvent approprié pour l'action de clic interprétée
  2. Activer les services d'accessibilité pour effectuer l'action de clic personnalisé pour les utilisateurs qui ne peuvent pas utiliser d'écran tactile

Pour gérer efficacement ces exigences, le code doit remplacer la méthode performClick(), qui doit appeler sa super-implémentation, puis exécuter les actions requises par l'événement de clic. Lorsque l'action de clic personnalisé est détectée, ce code doit appeler la méthode performClick(). L'exemple de code suivant illustre ce schéma.

Kotlin

class CustomTouchView(context: Context) : View(context) {

    var downTouch = false

    override fun onTouchEvent(event: MotionEvent): Boolean {
        super.onTouchEvent(event)

        // Listening for the down and up touch events.
        return when (event.action) {
            MotionEvent.ACTION_DOWN -> {
                downTouch = true
                true
            }

            MotionEvent.ACTION_UP -> if (downTouch) {
                downTouch = false
                performClick() // Call this method to handle the response and
                // enable accessibility services to
                // perform this action for a user who can't
                // tap the touchscreen.
                true
            } else {
                false
            }

            else -> false  // Return false for other touch events.
        }
    }

    override fun performClick(): Boolean {
        // Calls the super implementation, which generates an AccessibilityEvent
        // and calls the onClick() listener on the view, if any.
        super.performClick()

        // Handle the action for the custom click here.

        return true
    }
}

Java

class CustomTouchView extends View {

    public CustomTouchView(Context context) {
        super(context);
    }

    boolean downTouch = false;

    @Override
    public boolean onTouchEvent(MotionEvent event) {
        super.onTouchEvent(event);

        // Listening for the down and up touch events
        switch (event.getAction()) {
            case MotionEvent.ACTION_DOWN:
                downTouch = true;
                return true;

            case MotionEvent.ACTION_UP:
                if (downTouch) {
                    downTouch = false;
                    performClick(); // Call this method to handle the response and
                                    // enable accessibility services to
                                    // perform this action for a user who can't
                                    // tap the touchscreen.
                    return true;
                }
        }
        return false; // Return false for other touch events.
    }

    @Override
    public boolean performClick() {
        // Calls the super implementation, which generates an AccessibilityEvent
        // and calls the onClick() listener on the view, if any.
        super.performClick();

        // Handle the action for the custom click here.

        return true;
    }
}

Le schéma précédent garantit que l'événement de clic personnalisé est compatible avec les services d'accessibilité. Pour ce faire, il utilise la méthode performClick() pour générer un événement d'accessibilité et fournir un point d'entrée permettant aux services d'accessibilité d'agir pour le compte d'un utilisateur afin d'exécuter cet événement de clic personnalisé.