Всем привет! Меня зовут Марина. Я Android-разработчик в VK. В этой статье я расскажу о нашей совместной работе с дизайнерами: как мы встали на сторону людей с ограниченными возможностями, которые потребляют наш контент с помощью скринридера, проанализировали фичи нашей команды и попытались сделать функциональность доступной.

Accessibility — это доступность (в тексте или разговорах часто сокращают это слово до a11y).

Задача веб‑ и мобильного контента — сделать его доступным для всех, включая пользователей с ограниченными возможностями. Раньше термин «пользователи с ограниченными возможностями» использовали в отношении людей с ограничениями в здоровье, но сегодня этот термин обозначает нечто большее. Это может быть и мама, которая одной рукой держит малыша, а другой чатится в телефоне. Или студент на паре, где нельзя смотреть ролики со звуком, но можно — с субтитрами. Так или иначе, каждый из нас ежедневно выполняет роль пользователя с ограниченными возможностями. И задача разработчиков — сделать контент доступным.

Существуют популярные способы сделать контент доступным:

  • Цвет. Цвет НЕ должен быть единственным способом передачи информации. Например, ссылка должна быть не просто выделена цветом, но и подчеркнута. Таким образом, для человека с нарушением восприятия цвета должно быть понятно, что в тексте есть ссылка.

  • При наличии аудио и видео контента должно присутствовать его текстовое или аудио описание.

  • Субтитры должны быть синхронизированы с видео.

  • Все изображения, кнопки с изображениями, карты, графики и прочее, должны иметь соответствующий эквивалентный текст. Если у изображения нет какой-то смысловой нагрузки, тогда стоит задуматься, нужен ли вообще alt-текст и стоит ли устанавливать фокус на этот элемент? Ведь если изображение не несет никакой нагрузки — это просто информационный шум.

Например, на экране есть картинка с изображением логотипа компании. Если пользователь с помощью скринридера будет изучать эту страницу и фокус попадет на это изображение, которое имеет описание «логотип компании», — это не даст ему большего представления о странице. Поэтому можно убрать фокус с этого изображения.

Одна из наших фич — Индекс качества. Она доступна владельцам сообществ.

Рис. 2                                                          Рис. 3                                                           Рис. 4
Рис. 2 Рис. 3 Рис. 4

На рис. 2 можно увидеть, как владелец сообщества видит блок с Индексом качества на главной странице сообщества. В этом блоке есть изображение — стрелочка вперед, которая означает, что при клике на блок или на эту стрелку, откроется раздел «Индекс качества». Однако для пользователя, который перемещается по странице с помощью скринридера, стоит добавить описание для этой кнопки: «Перейти в раздел Индекс качества».

Теперь мы попадаем на страницу с индексом качества (рис. 3, рис. 4). Здесь есть награды за активность, которые также с помощью цвета сигнализируют пользователю, какие награды доступны, а какие — нет (доступные награды цветные, а недоступные награды представлены в черно‑белом цвете). На этой странице есть задания, которые с помощью цвета и с помощью галочек передают пользователю, какие задания выполненны, а какие — нет. Чтобы пользователь при перемещании по странице с помощью скринридера понимал, какие задания и награды ему доступны, а какие — нет, мы добавили описание (content description): «Доступные награды», «Недоступные награды», «Выполненные задания», «Невыполненные задания».

Отзывы на товары и отзывы на сообщества

Эта фича доступна из многих экранов. Соответственно, наша задача заключалась в том, чтобы сделать поведение одинаково доступным, когда нет такого, что один и тот же функционал на разных страницах для пользователей с ограниченными возможностями имеет разное флоу взаимодействия с пользователем.

Табы. На рис. 5 мы видим, что есть два таба, активность которых передается цветом и жирным шрифтом. Но цвет не должен быть единственным способом передачи информации. Поэтому, чтобы пользователь верно ориентировался на странице, для активного таба скринридер озвучивает: «Отзывы на сообщества», а для неактивного озвучивает: «Товары. Показать».

Порядковый номер отзыва. Когда открывается страница со списком отзывов, мы договорились с дизайнерами, что скринридер должен зачитывать порядковый номер отзыва. Фокус. Ранее, при попадании фокуса на аватарку, скринридер озвучивал Фамилию и Имя пользователя, оставившего отзыв. Но для пользователя со скринридером это может быть неинформативно, поэтому мы убрали фокус с аватарки. Таким образом, сама по себе аватарка на странице осталась, пропал лишь фокус на нее при включенном скринридере.

Рис. 5                                                                                           Рис. 6
Рис. 5 Рис. 6

Хочется больше внимания уделить фокусу.

  • Фокус должен быть виден при работе со страницей. Если скринридер зачитывает какой‑то элемент, то фокус обязательно должен быть виден и быть на этом элементе (иначе это баг!).

  • Если по веб‑странице можно перемещаться последовательно, и эта последовательность влияет на смысл или выполнение задач, то фокус при прочтении должен перемещаться в такой последовательности, при которой сохраняется и смысл, и возможность управления.

  • Единообразная навигация — фокус движется сверху‑вниз слева‑направо (рис. 7). И это единообразие должно соблюдаться во всем приложении. Если на какой‑то странице эта навигация работает по‑другому (и это ваше осознанное решение), то функциональность не должна сломаться для пользователя со скринридером.

Рис. 7
Рис. 7

Наглядным примером единообразия поведения фокуса может служить рисунок 8, где я цифрами пометила очередность движения фокуса. Боттомшит открылся, первый фокус попадает на кнопку «Закрыть», далее свайпаем вправо — фокус перемещается на заголовок страницы и так далее.

Рис. 8
Рис. 8

Также не забываем про озвучивание спец символов. На рисунке 8: «Ваша оценка*». Если не добавить нужный contentDescription, то скринридер озвучит как: «Ваша оценка. Звездочка», а нужно: «Ваша оценка. Обязательное поле».

Шпаргалка при работе с фокусом в Android

Если к вам пришла задача об отсутствии фокуса на каком‑то элементе на странице, то в первую очередь стоит:

  1. Проверить, есть ли в разметке атрибут android:focusable="true"? 90 % задач решаются добавлением этого атрибута.

    <TextView
       android:id="@+id/button"
       android:layout_width="wrap_content"
       android:layout_height="wrap_content"
       android:focusable="true"/>
  2. В коде при инициализации элемента установить фокус с помощью метода requestFocus():

    codeEditTextLayout?.requestFocus()

    при этом с помощью метода clearFocus() можно удалить фокус:

    codeEditText?.clearFocus()
  3. Если метод requestFocus() не помогает, можно попробовать вызвать этот метод внутри конструкции post { }:

    sendButton.post{
       sendButton.requestFocus()
    }

    Здесь блок post { } нужен, чтобы убедиться, что view готов принять фокус.

  4. Если задача стоит на установление последовательности движения фокуса на странице при зачитывании скринридером элементов на экране, то можно воспользоваться методамиnextFocusUpId, nextFocusDownId, nextFocusLeftId, nextFocusRightId. Или установить в разметке android:nextFocusUp, android:nextFocusDown, android:nextFocusLeft, android:nextFocusRight.

    <TextView
       android:id="@+id/title"
       android:layout_width="wrap_content"
       android:layout_height="wrap_content"
       android:focusable="true"
       android:nextFocusDown="@+id/subtitle"
       android:text="@string/title"/>

    Таким образом мы задаём для TextView, что при включении скринридера фокус после попадания на текущий элемент перейдет на вью с id = "subtle".

  5. Бывают задачи, когда нужно убрать фокус со view для пользователя с ограниченными возможностями, но для остальных пользователей этот view активный, на него можно нажимать и клик по нему открывает новый экран. Для реализации этого кейса может помочь атрибут importantForAccessibility(no/auto).

    1. Если мы хотим явно указать, что view не является важной для доступности, используем importantForAccessibility ="no". Этот атрибут следует указать в разметке:

      <ImageView
         android:id="@+id/image"
         android:layout_width="16dp"
         android:layout_height="16dp"
         android:importantForAccessibility="no" />
    2. Если же необходимо, чтобы система автоматически определяла, является view важным для доступности или нет, необходимо в разметке указать importantForAccessibility ="auto":

      <ImageView
         android:id="@+id/image"
         android:layout_width="16dp"
         android:layout_height="16dp"
         android:importantForAccessibility="auto" />

      А также в коде activity или фрагмента добавить:

      val accessibilityManager = context.getSystemService(Context.ACCESSIBILITY_SERVICE) as AccessibilityManager
      
      if (accessibilityManager.isTouchExplorationEnabled) {
         ViewCompat.setImportantForAccessibility(view, ViewCompat.IMPORTANT_FOR_ACCESSIBILITY_NO)
      } else {
         ViewCompat.setImportantForAccessibility(view, ViewCompat.IMPORTANT_FOR_ACCESSIBILITY_AUTO)
      }
  6. При работаете с compose можно попробовать установить фокус с помощью:

    1. функции focusProperties:

      Toolbar(
      	title = stringResource(id = “Ваш отзыв”),
      	modifier = Modifier
      		.focusProperties {canFocus = false}
      )
    2. или задать внутри semantics{}:

      Toolbar(
      	title = stringResource(id = “Ваш отзыв”),
      	modifier = Modifier
      		.semantics{
      			this.focused = false
      			this.contentDescription = “”
      }
      )
    3. функции focusable:

      modifier = Modifier
      .wrapContentSize()
         	.focusable(enabled = false)

Совместная работа с командой дизайнеров помогла сделать функциональность доступнее. Следующим шагом было отдать на проверку наши фичи команде цифровой доступности, которая также оценила проделанный труд.

Эта работа была очень продуктивной, каждый пытался встать на сторону инклюзивности. Оказалось, это не так уж трудно, стоит только проработать детали на этапе дизайна фичи. Буду рада, если эта статья вдохновит и вас внедрять доступность в разработку приложений.

Комментарии (4)


  1. kranid
    19.02.2025 12:33

    Если задача стоит на установление последовательности движения фокуса на > странице при зачитывании скринридером элементов на экране, то можно > воспользоваться методамиnextFocusUpId, nextFocusDownId, nextFocusLeftId, > > nextFocusRightId.

    Вроде бы эти методы отвечают за фокус клавиатуры, а для скринридера используются traversal before/after?

    А зачем нужна проверка if (accessibilityManager.isTouchExplorationEnabled)?


    1. aqua_marina Автор
      19.02.2025 12:33

      Методы nextFocusUpId, nextFocusDownId, nextFocusLeftId, nextFocusRightId отвечают за фокус. Фокус можно получить, использовав клавиатуру с помощью стрелок или Tab, в случае с мобильными девайсами фокус устанавливается с помощью свайпа. То есть пользователь включает скринридер, на странице появляется фокус на первом элементе, далее с помощью свайпа фокус перемещается по элементам. Таким образом эти методы используются для управления очередности фокуса в Android.


      Важность или неважность установления фокуса на элемент для скринридера можно указать в разметке с помощью  android:importantForAccessibility="no", или в самом коде

      if (accessibilityManager.isTouchExplorationEnabled) {

         ViewCompat.setImportantForAccessibility(view, ViewCompat.IMPORTANT_FOR_ACCESSIBILITY_NO)

      } else {}


  1. ris58h
    19.02.2025 12:33

    Например, ссылка должна быть не просто выделена цветом, но и подчеркнута.

    В ВК следуют этому правилу?


    1. aqua_marina Автор
      19.02.2025 12:33

      С каждым днем все больше команд внедряют в свое флоу разработки поддержку accessibility. Новые фичи прорабатываются с поддержкой accessibility, старые будут дорабатываться в процессе