2. CAS D'USAGE (iOS ET ANDROID)
Table des matières |
---|
Cette section présente les différents cas d’utilisation de la plupart des contrôles offerts sur les plateformes Android de Google et iOS d’Apple.
La plupart du temps, le cas d’usage s’appliquera aux deux plateformes; sinon, l’utilisateur en sera avisé.
2.1 Navigation
Des cas d’usage portant sur la navigation courants dans les applications de Radio-Canada.
2.1.1 Barre de navigation
Cette section décrit les changements qui ont dus être apportés à la barre de navigation (TopBar) de ICI Tou Tv V4 afin de la conformer aux normes décrites à la section 1.1.2.
a)Bouton “Retour”
Le bouton qui permet de retourner à l’écran parent doit avoir un libellé précis. Voici comment nous y sommes pris pour l’implémenter sur ICI Tou Tv V4.
IOS
À venir
ANDROID
Il a fallut d'abord définir un format de libellé pour le bouton Retour:
<string name="a11y_top_bar_back_button_label_format">Retour à l'ecran %s</string>
Ensuite, il a fallut récupérer le titre de la page parent
@Bindable val title = ObservableField<String>() @Bindable val parentTitle = ObservableField<String>()
fun updateTitle(title: String?) { parentTitle.set(this.title.get() ?: resourceService.getString(R.string.menu_home)) this.title.set(title) }
Et pour finir il suffisait de construire le libellé dynamiquement à l'aide du format et du titre de la page parent:
<ImageButton android:id="@+id/back_button" android:layout_width="48dp" android:layout_height="?attr/actionBarSize" android:background="@color/transparent" android:contentDescription="@{@string/a11y_top_bar_back_button_label_format(vmToolbar.parentTitle)}" android:onClick="@{_ -> vmToolbar.onBackPressed()}" android:src="@drawable/rc_ui_arrow_w" android:tint="@color/white" android:visibility="@{Visibility.goneWhenNot(vmToolbar.isBackButtonVisible())}" app:layout_constraintStart_toStartOf="parent" app:layout_constraintTop_toTopOf="@+id/movable_guide_top"/>
b)Fonction de “Recherche” (bouton et menu)
La fonctionnalité de recherche est très courante et importante pour rendre une application accessible. Voici comment nous y sommes pris pour la rendre accessible sur ICI Tou Tv V4.
IOS
À venir
ANDROID
Il est indispensable de définir et d'attribuer un libellé au bouton qui permet de lancer la recherche
<string name="a11y_search_button_label">Recherche</string>
<ImageView android:id="@+id/toolbar_search_view_button" android:layout_width="wrap_content" android:layout_height="match_parent" android:layout_marginEnd="15dp" android:paddingLeft="@dimen/search_button_padding" android:paddingRight="@dimen/search_button_padding" android:contentDescription="@string/a11y_search_button_label" android:onClick="@{_ -> vmToolbar.onSearchButtonClicked()}" android:scaleType="fitCenter" android:src="@drawable/rc_icon_search" android:visibility="@{Visibility.goneWhenNot(vmToolbar.isSearchVisible())}" app:layout_constraintEnd_toEndOf="parent" app:layout_constraintTop_toTopOf="parent"/>
Il faut également intituler le bouton qui permet d'effacer le terme de recherche ou de fermer le menu de recherche:
@SuppressLint("PrivateResource") fun SearchQueryTextChangeListener.setupA11y(newText: String) { searchView.findViewById(R.id.search_close_btn)?.let { it.contentDescription = resources?.getString( if (newText.isEmpty()) R.string.a11y_search_close_button_label else R.string.abc_searchview_description_clear ) } }
Une fois les résultats de la recherche disponibles, il faut soit annoncer qu'aucun résultat n'a été trouvé, soit indiquer le nombre de résultats:
onNewResultsAvailableCallback = object : Observable.OnPropertyChangedCallback() { override fun onPropertyChanged(observable: Observable?, p1: Int) { (observable as? ObservableField<*>) ?.takeIf { it.get() is SearchFilterResult } ?.let { val filterResult = it.get() as? SearchFilterResult filterResult ?.takeIf { it.items.none() } ?.let { announcementService.announce(resourceService.getString(R.string.search_no_results)) } filterResult ?.takeIf { it.items.any() && it.previousItems?.none() == true } ?.let { announcementService.announce("${it.items.size} ${resourceService.getPlural(R.plurals.a11y_search_result_item_label, it.items.size)}") } } } }
2.1.2 Barre de tabulation
Cette section décrit les changements qui ont dus être apportés à la barre de tabulation (BottomBar) de ICI Tou Tv V4 afin de la conformer aux normes décrites à la section 1.1.3.
IOS
À venir
ANDROID
Indiquer l'onglet sélectionné: afin d'indiquer la section sélectionnée aux utilisateurs des services d'accessibilité, l'état de sélection de l'item a été spécifié au AccessbilityNodeInfo
info.isSelected = this@setupA11y.selectedItemId == host.id
Indiquer qu'il s'agit d'un menu
info.text = this@setupA11y.context.getString(R.string.a11y_menu_label)
Également, pour indiquer le nombre d'éléments dans le menu , le CollectionInfo (le parent) et CollectionItemInfo (les menu items) on étés spécifiés:
info.collectionInfo = AccessibilityNodeInfo.CollectionInfo.obtain(1, this@setupA11y.menu.size(), false)
info.collectionItemInfo = AccessibilityNodeInfo.CollectionItemInfo.obtain(0, 1, menu.findItem(host.id).order, 1, false, info.isSelected)
Il a fallut également indiquer que le parent des éléments du menu est important pour l'accessibilité et configurer son delegate
//Attach the delegate on the parent only once if (index == 0) { viewGroup.importantForAccessibility = View.IMPORTANT_FOR_ACCESSIBILITY_YES viewGroup.setAccessibilityDelegate(menuItemParentDelegate) }
Et pour finir, le libellé du dernier élément du menu contient la phrase : 'fin du menu'
//When the current menu item is the last menu item menu.getItem(menu.size() - 1) ?.takeIf { it.itemId == host.id } ?.let { lastItem -> info.contentDescription = "%s (%s)".format(Locale.ROOT, lastItem.title, this@setupA11y.context.getString(R.string.a11y_end_of_menu_message)) }
Voici le code complet
fun BottomNavigationView.setupA11y() { A11yHelper.setupBottomBarA11y(this, object : View.AccessibilityDelegate() { override fun onInitializeAccessibilityNodeInfo(host: View?, info: AccessibilityNodeInfo?) { super.onInitializeAccessibilityNodeInfo(host, info) info ?: return host ?: return val menu = this@setupA11y.menu info.isSelected = this@setupA11y.selectedItemId == host.id info.collectionItemInfo = AccessibilityNodeInfo.CollectionItemInfo.obtain(0, 1, menu.findItem(host.id).order, 1, false, info.isSelected) //When the current menu item is the last menu item menu.getItem(menu.size() - 1) ?.takeIf { it.itemId == host.id } ?.let { lastItem -> info.contentDescription = "%s (%s)".format(Locale.ROOT, lastItem.title, this@setupA11y.context.getString(R.string.a11y_end_of_menu_message)) } } }, object : View.AccessibilityDelegate() { override fun onInitializeAccessibilityNodeInfo(host: View?, info: AccessibilityNodeInfo?) { super.onInitializeAccessibilityNodeInfo(host, info) info ?: return info.text = this@setupA11y.context.getString(R.string.a11y_menu_label) info.collectionInfo = AccessibilityNodeInfo.CollectionInfo.obtain(1, this@setupA11y.menu.size(), false) } }) } fun setupBottomBarA11y(viewGroup: ViewGroup?, menuItemDelegate: View.AccessibilityDelegate?, menuItemParentDelegate: View.AccessibilityDelegate?) { viewGroup?.let { vg -> (0 until vg.childCount) .map { vg.getChildAt(it) } .forEachIndexed { index: Int, child: View -> if (child is MenuView.ItemView) { child.setAccessibilityDelegate(menuItemDelegate) //Attach the delegate on the parent only once if (index == 0) { viewGroup.importantForAccessibility = View.IMPORTANT_FOR_ACCESSIBILITY_YES viewGroup.setAccessibilityDelegate(menuItemParentDelegate) } } else if (child is ViewGroup) { setupBottomBarA11y(child, menuItemDelegate, menuItemParentDelegate) } } } }
2.2 Commandes
Regroupe les cas d’usages les plus répandus des contrôles utilisés pour permettre à l’utilisateur d’interagir avec le contenu de la page d’une application mobile.
En plus de celles qui s’appliquent à tous les commandes, un contrôle interactif doit avoir les particularités suivantes :
Il est parcouru par les services d’accessibilité (même s’il est désactivé).
L’état du contrôle est annoncé par le service vocal d’accessibilité (activé, désactivé, etc.).
2.2.1 Boutons
Les boutons sont les contrôles interactifs par excellence. Leur utilité première est de permettre à l’utilisateur de déclencher une action.
En plus de celles qui s’appliquent à tous les contrôles interactifs, un bouton doit avoir les particularités suivantes :
Il est parcouru par les services d’accessibilité (même s’il est désactivé).
En plus d’être nommée, l’action liée au bouton est décrite.
Cette section présente quelques cas d’usage de boutons.
a)Un bouton qui démarre de l’audio
Un bouton qui sert à démarrer de l’audio devrait avoir les particularités suivantes en plus de celles que tous les boutons devraient avoir.
La description de l’action associée au bouton doit commencer ou terminer par : “Écouter l’audio“
La description de l’action associée au bouton doit contenir le titre du média audio qui sera démarré
b)Un bouton qui démarre de la vidéo
Un bouton qui sert à démarrer de l’audio devrait avoir les particularités suivantes en plus de celles que tous les boutons devraient avoir.
La description de l’action associée au bouton doit commencer ou terminer par : “Écouter la vidéo“
La description de l’action associée au bouton doit contenir le titre du média audio qui sera démarré
c)Un bouton dans une liste de boutons
Suivre les recommandations faites à la section Collections d’éléments.
2.3 Contrôles non interactifs
Regroupe les cas d’usages les plus répandus des contrôles présents pour des besoins indicatifs ou décoratifs.En plus de celles qui s’appliquent à tous les contrôles natifs, un contrôle non interactif doit avoir la particularité suivante :
Il est ignoré par les services d’accessibilité.
ANDROID
Exemple de contrôle non interactif
Le logo de l'application ainsi que le titre de la page précédente qui apparaissent dans la barre de navigation sur Android et sur iOS
Définition
<ViewSwitcher android:id="@+id/titlesSwitcher" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_gravity="start" android:importantForAccessibility="noHideDescendants"> <ca.rc_cbc.mob.androidfx.widgets.textviews.CustomTextView android:id="@+id/appNameTextView" android:layout_width="match_parent" android:layout_height="match_parent" android:gravity="start" android:layout_gravity="start" android:textColor="@color/color_FFFFFF" android:textSize="30sp" app:textFontNameId="@string/app_ico_moon" /> <ca.rc_cbc.mob.androidfx.widgets.textviews.CustomTextView android:id="@+id/back_arrow_label" android:layout_width="match_parent" android:layout_height="match_parent" android:ellipsize="end" android:maxLines="1" android:textColor="@color/color_FFFFFF" android:textSize="@dimen/h1_text_size" android:gravity="start" app:textFontNameId="@string/app_font_name" /> </ViewSwitcher>
Explication
Grâce à la propriété importantForAccessibility, il est possible de demander aux services d'accessibilité d'ignorer le groupe de vue qui abrite le logo de l'application ICI PREMIÈRE ( appNameTextView) ainsi que le titre de la page précédente ( back_arrow_label) qui apparaît lorsque l'utilisateur effectue une navigation.
Résultat
Pour voir le résultat, activer la fonction TalkBack sur l'application ICI Première de Radio-Canada et observer le comportement suivant :
Le logo de l'application ne reçoit jamais le focus.
Le logo de l'application n'est jamais lu par la fonction TalkBack.