Основные понятия и реализация Jetpack Compose
Чтобы помочь пользователям с особыми потребностями, фреймворк Android позволяет создавать специальные возможности, которые могут показывать контент из приложений и управлять ими от имени пользователей.
В Android есть несколько системных сервисов специальных возможностей, в том числе:
- TalkBack помогает людям с нарушениями зрения. Она озвучивает контент синтезированным голосом и выполняет действия в приложении в ответ на жесты пользователя.
- Switch Access помогает людям с двигательными нарушениями. Он выделяет интерактивные элементы и выполняет действия в ответ на нажатие кнопки пользователем. Она позволяет управлять устройством с помощью всего одной или двух кнопок.
Чтобы пользователи с особыми потребностями могли успешно работать с вашим приложением, оно должно соответствовать рекомендациям, описанным на этой странице и в статье Как сделать приложение более доступным.
Элементы Label
Важно, чтобы у каждого интерактивного элемента интерфейса в приложении был ярлык с описанием его назначения. Программы чтения с экрана, такие как TalkBack, могут озвучивать эти ярлыки для пользователей.
В большинстве случаев описание элемента интерфейса указывается в файле ресурса разметки, который содержит этот элемент. Обычно ярлыки добавляются с помощью атрибута contentDescription, как описано в руководстве по улучшению специальных возможностей в приложениях. Другие способы добавления меток описаны в следующих разделах.
Редактируемые элементы
При добавлении ярлыков к редактируемым элементам, например объектам EditText, полезно показывать текст с примером допустимого ввода в самом элементе, а также делать этот текст доступным для программ чтения с экрана. В таких случаях можно использовать атрибут android:hint, как показано в следующем фрагменте кода:
<!-- The hint text for en-US locale would be "Apartment, suite, or building". --> <EditText android:id="@+id/addressLine2" android:hint="@string/aptSuiteBuilding" ... />
В этом случае у объекта View должен быть атрибут android:labelFor, значение которого соответствует идентификатору элемента EditText. Подробная информация приведена в разделе ниже.
Пары элементов, в которых один описывает другой.
Обычно у элемента EditText есть соответствующий объект View, который описывает, что пользователи должны ввести в элемент EditText. Для этого нужно задать атрибут android:labelFor объекта View.
Пример разметки таких пар элементов приведен в следующем фрагменте кода:
<!-- Label text for en-US locale would be "Username:" --> <TextView android:id="@+id/usernameLabel" ... android:text="@string/username" android:labelFor="@+id/usernameEntry" /> <EditText android:id="@+id/usernameEntry" ... /> <!-- Label text for en-US locale would be "Password:" --> <TextView android:id="@+id/passwordLabel" ... android:text="@string/password android:labelFor="@+id/passwordEntry" /> <EditText android:id="@+id/passwordEntry" android:inputType="textPassword" ... />
элементов в подборке
При добавлении ярлыков к элементам коллекции каждый ярлык должен быть уникальным. Это позволит специальным возможностям системы ссылаться на один элемент на экране при озвучивании метки. Это соответствие позволяет пользователям знать, когда они перемещаются по интерфейсу или когда они перемещают фокус на элемент, который они уже обнаружили.
В частности, добавьте дополнительный текст или контекстную информацию в элементы в повторно используемых макетах, например в объекты RecyclerView, чтобы каждый дочерний элемент был уникально идентифицирован.
Для этого задайте описание контента в реализации адаптера, как показано в следующем фрагменте кода:
Kotlin
data class MovieRating(val title: String, val starRating: Integer) class MyMovieRatingsAdapter(private val myData: Array<MovieRating>): RecyclerView.Adapter<MyMovieRatingsAdapter.MyRatingViewHolder>() { class MyRatingViewHolder(val ratingView: ImageView) : RecyclerView.ViewHolder(ratingView) override fun onBindViewHolder(holder: MyRatingViewHolder, position: Int) { val ratingData = myData[position] holder.ratingView.contentDescription = "Movie ${position}: " + "${ratingData.title}, ${ratingData.starRating} stars" } }
Java
public class MovieRating { private String title; private int starRating; // ... public String getTitle() { return title; } public int getStarRating() { return starRating; } } public class MyMovieRatingsAdapter extends RecyclerView.Adapter<MyAdapter.MyRatingViewHolder> { private MovieRating[] myData; public static class MyRatingViewHolder extends RecyclerView.ViewHolder { public ImageView ratingView; public MyRatingViewHolder(ImageView iv) { super(iv); ratingView = iv; } } @Override public void onBindViewHolder(MyRatingViewHolder holder, int position) { MovieRating ratingData = myData[position]; holder.ratingView.setContentDescription("Movie " + position + ": " + ratingData.getTitle() + ", " + ratingData.getStarRating() + " stars") } }
Группы связанного контента
Если в приложении есть несколько элементов интерфейса, которые образуют естественную группу, например сведения о песне или атрибуты сообщения, расположите эти элементы в контейнере, который обычно является подклассом ViewGroup. Задайте для атрибута android:screenReaderFocusable объекта-контейнера значение true, а для атрибута android:focusable каждого внутреннего объекта – значение false. В этом случае сервисы специальных возможностей смогут озвучить описания контента внутренних элементов одно за другим в одном объявлении.
Такое объединение связанных элементов помогает пользователям технологий специальных возможностей эффективнее находить информацию на экране.
В следующем фрагменте кода содержатся связанные друг с другом элементы контента. Поэтому для элемента-контейнера, экземпляра ConstraintLayout, атрибут android:screenReaderFocusable имеет значение true, а для внутренних элементов TextView атрибут android:focusable имеет значение false:
<!-- In response to a single user interaction, accessibility services announce both the title and the artist of the song. --> <ConstraintLayout android:id="@+id/song_data_container" ... android:screenReaderFocusable="true"> <TextView android:id="@+id/song_title" ... android:focusable="false" android:text="@string/my_song_title" /> <TextView android:id="@+id/song_artist" android:focusable="false" android:text="@string/my_songwriter" /> </ConstraintLayout>
Поскольку сервисы специальных возможностей озвучивают описания внутренних элементов за один раз, важно, чтобы каждое описание было как можно короче, но при этом передавало смысл элемента.
Примечание. Как правило, не следует создавать описание контента для группы, объединяя текст ее дочерних элементов. Это делает описание группы хрупким, и когда текст дочернего элемента изменяется, описание группы может больше не соответствовать видимому тексту.
В списке или сетке программа чтения с экрана может объединять текст дочерних текстовых узлов элемента списка или сетки. Рекомендуем не изменять это объявление.
Вложенные группы
Если в интерфейсе приложения представлена многомерная информация, например список событий фестиваля по дням, используйте атрибут android:screenReaderFocusable для внутренних контейнеров групп. Такая схема обеспечивает хороший баланс между количеством объявлений, необходимых для обнаружения контента на экране, и длиной каждого объявления.
В следующем фрагменте кода показан один из способов маркировки групп внутри более крупных групп:
<!-- In response to a single user interaction, accessibility services announce the events for a single stage only. --> <ConstraintLayout android:id="@+id/festival_event_table" ... > <ConstraintLayout android:id="@+id/stage_a_event_column" android:screenReaderFocusable="true"> <!-- UI elements that describe the events on Stage A. --> </ConstraintLayout> <ConstraintLayout android:id="@+id/stage_b_event_column" android:screenReaderFocusable="true"> <!-- UI elements that describe the events on Stage B. --> </ConstraintLayout> </ConstraintLayout>
Заголовки в тексте
В некоторых приложениях используются заголовки, которые обобщают группы текстов на экране. Если определенный элемент View представляет собой заголовок, вы можете указать его назначение для сервисов специальных возможностей, задав для атрибута android:accessibilityHeading элемента значение true.
Пользователи специальных возможностей могут перемещаться между заголовками, а не между абзацами или словами. Такая гибкость улучшает навигацию по тексту.
Заголовки панелей специальных возможностей
В Android 9 (уровень API 28) и более поздних версиях можно добавлять специальные названия для панелей экрана. С точки зрения специальных возможностей панель – это визуально различимая часть окна, например содержимое фрагмента. Чтобы специальные возможности могли распознавать поведение панели, похожее на поведение окна, дайте панелям приложения описательные названия. Это позволяет сервисам специальных возможностей предоставлять пользователям более подробную информацию об изменениях в панели.
Чтобы указать название панели, используйте атрибут android:accessibilityPaneTitle, как показано в следующем фрагменте кода:
<!-- Accessibility services receive announcements about content changes that are scoped to either the "shopping cart view" section (top) or "browse items" section (bottom) --> <MyShoppingCartView android:id="@+id/shoppingCartContainer" android:accessibilityPaneTitle="@string/shoppingCart" ... /> <MyShoppingBrowseView android:id="@+id/browseItemsContainer" android:accessibilityPaneTitle="@string/browseProducts" ... />
Декоративные элементы
Если элемент в интерфейсе существует только для визуального разделения или оформления, задайте для его атрибута android:importantForAccessibility значение "no".
Добавьте действия специальных возможностей
Важно, чтобы пользователи сервисов специальных возможностей могли легко выполнять все пути пользователя в приложении. Например, если в списке можно провести по элементу, это действие также должно быть доступно в сервисах специальных возможностей, чтобы у пользователей был альтернативный способ выполнить тот же путь пользователя.
Как сделать все действия доступными
Пользователям TalkBack, Voice Access и Switch Access могут потребоваться альтернативные способы выполнения определенных путей пользователя в приложении. Например, действия, связанные с жестами, такими как перетаскивание или пролистывание, можно сделать доступными для пользователей сервисов специальных возможностей.
Используя действия специальных возможностей, приложение может предоставить пользователям альтернативные способы выполнения действия.
Например, если в вашем приложении можно провести по элементу, вы можете добавить специальное действие, которое будет выполнять ту же функцию.
Kotlin
ViewCompat.addAccessibilityAction( // View to add accessibility action itemView, // Label surfaced to user by an accessibility service getText(R.id.archive) ) { _, _ -> // Same method executed when swiping on itemView archiveItem() true }
Java
ViewCompat.addAccessibilityAction( // View to add accessibility action itemView, // Label surfaced to user by an accessibility service getText(R.id.archive), (view, arguments) -> { // Same method executed when swiping on itemView archiveItem(); return true; } );
With the custom accessibility action implemented, users can access the action through the actions menu.
Make available actions understandable
When a view supports actions such as touch & hold, an accessibility service such as TalkBack announces it as "Double tap and hold to long press."
This generic announcement doesn't give the user any context about what a touch & hold action does.
To make this announcement more descriptive, you can replace the accessibility action’s announcement like so:
Kotlin
ViewCompat.replaceAccessibilityAction( // View that contains touch & hold action itemView, AccessibilityNodeInfoCompat.AccessibilityActionCompat.ACTION_LONG_CLICK, // Announcement read by TalkBack to surface this action getText(R.string.favorite), null )
Java
ViewCompat.replaceAccessibilityAction( // View that contains touch & hold action itemView, AccessibilityNodeInfoCompat.AccessibilityActionCompat.ACTION_LONG_CLICK, // Announcement read by TalkBack to surface this action getText(R.string.favorite), null );
This results in TalkBack announcing "Double tap and hold to favorite," helping users understand the purpose of the action.
Extend system widgets
Note: When you design your app's UI, use or extend
system-provided widgets that are as far down Android's class hierarchy as
possible. System-provided widgets that are far down the hierarchy already
have most of the accessibility capabilities your app needs. It's easier
to extend these system-provided widgets than to create your own from the more
generic View,
ViewCompat,
Canvas, and
CanvasCompat
classes.
If you must extend View or Canvas directly, which
might be necessary for a highly customized experience or a game level, see
Make custom views more
accessible.
This section uses the example of implementing a special type of
Switch called TriSwitch while following
best practices around extending system widgets. A TriSwitch
object works similarly to a Switch object, except that each instance of
TriSwitch allows the user to toggle among three possible states.
Extend from far down the class hierarchy
The Switch object inherits from several framework UI classes in its hierarchy:
View ↳ TextView ↳ Button ↳ CompoundButton ↳ Switch
Лучше всего, чтобы новый класс TriSwitch расширял класс Switch напрямую. Таким образом, фреймворк специальных возможностей Android предоставляет большинство функций специальных возможностей, необходимых классу TriSwitch:
- Действия специальных возможностей. Информация для системы о том, как сервисы специальных возможностей могут эмулировать каждый возможный ввод пользователя, выполняемый на объекте
TriSwitch. (Унаследовано отView.) - События специальных возможностей. Информация для сервисов специальных возможностей обо всех возможных способах изменения внешнего вида объекта
TriSwitchпри обновлении экрана. (Унаследовано отView.) - Характеристики. Сведения о каждом объекте
TriSwitch, например текст, который он показывает. (Унаследовано отTextView.) - Информация о состоянии. Описание текущего состояния объекта
TriSwitch, например "выбрано" или "не выбрано". (Унаследовано отCompoundButton.) - Текстовое описание состояния. Текстовое объяснение того, что представляет собой каждое состояние. (Унаследовано от
Switch.)
Это поведение Switch и его надклассов почти такое же, как и для объектов TriSwitch. Поэтому вы можете сосредоточиться на расширении количества возможных состояний с двух до трех.
Как определить специальные события
Расширяя системный виджет, вы, скорее всего, меняете способ взаимодействия пользователей с ним. Важно определить эти изменения взаимодействия, чтобы специальные возможности могли обновлять виджет вашего приложения так, как если бы пользователь взаимодействовал с виджетом напрямую.
Общее правило заключается в том, что для каждого переопределяемого обратного вызова на основе представления необходимо также переопределить соответствующее действие специальных возможностей, переопределив ViewCompat.replaceAccessibilityAction().
В тестах приложения вы можете проверить поведение этих переопределенных действий, вызвав ViewCompat.performAccessibilityAction().
Как этот принцип может работать для объектов TriSwitch
В отличие от обычного объекта Switch, при нажатии на объект TriSwitch происходит переключение между тремя возможными состояниями. Поэтому необходимо обновить соответствующее действие специальных возможностей ACTION_CLICK:
Kotlin
class TriSwitch(context: Context) : Switch(context) { // 0, 1, or 2 var currentState: Int = 0 private set init { updateAccessibilityActions() } private fun updateAccessibilityActions() { ViewCompat.replaceAccessibilityAction(this, ACTION_CLICK, action-label) { view, args -> moveToNextState() }) } private fun moveToNextState() { currentState = (currentState + 1) % 3 } }
Java
public class TriSwitch extends Switch { // 0, 1, or 2 private int currentState; public int getCurrentState() { return currentState; } public TriSwitch() { updateAccessibilityActions(); } private void updateAccessibilityActions() { ViewCompat.replaceAccessibilityAction(this, ACTION_CLICK, action-label, (view, args) -> moveToNextState()); } private void moveToNextState() { currentState = (currentState + 1) % 3; } }
Дополнительные ресурсы
Чтобы узнать больше о том, как сделать приложение более доступным, ознакомьтесь со следующими дополнительными ресурсами: