Разработка
Вопросы доступности при использовании кастомного макета стопки карт
Мы обсудили управление кнопками, клавиатурой, голосовой доступ и считыватель экрана, а также ориентацию экрана, увеличение размера шрифта и размера дисплея, а также когнитивные соображения.
Пару недель назад я опубликовала статью «Кастомные макеты в Compose — стопка карточек с кошками». У макета есть некоторые проблемы с доступностью, и в этой статье я опишу то, как постаралась исправить некоторые из них. Есть несколько проблем, которые я не буду устранять из-за объема этой статьи; вместо этого я обсужу проблемы, которые они вызывают, и возможные решения.
В этот раз я также укажу на некоторые положительные моменты в макете. Мне кажется, что я часто говорю только о проблемах и пытаюсь их найти, но в этот раз я поделюсь и тем, что работает хорошо.
Прежде чем мы погрузимся в различные аспекты, которые я выбрала для этой статьи, предупреждаю сразу. Этот список не является обширным и не содержит всех возможных проблем с доступностью, которые могут возникнуть у такого рода макета. Я не тестировала все возможные вспомогательные технологии и настройки. Моей целью был прогресс, а не совершенство, и я считаю, что модификации, которые я перечислю, уже значительно улучшают доступность этого макета.
Switch Access
Первая вспомогательная технология, о которой я расскажу и для которой я улучшу приложение, — это Switch Access. Это сервис, позволяющий пользователю управлять телефоном с помощью одной или нескольких физических кнопок. Если вы хотите узнать больше, в созданном мной контрольном списке доступности Android есть раздел о Switch Access — «Тестирование с помощью Switch Access«.
При тестировании Switch Access я обнаружила одну проблему. При удалении карты из стопки фокус никуда не девался. Он просто исчезал. Причина в том, что компонент, на котором находился фокус, удалялся, и служба доступа теряла фокус.
Один из способов решить эту проблему — перенести событие щелчка на компонент стека, а не на саму карточку. Мы можем сделать это, добавив семантику-модификатор с лямбдой onClick
к модификатору-свойству CardStack
. Также переместим lastItem
за пределы CardStack
, чтобы использовать его в функции, которую мы добавили:
// MainScreen.kt val lastItem = catIds.value.last() CardStack( Modifier.semantics { onClick { deleteCat(lastItem) true } }, ) { ... }
Функция onClick
возвращает булево значение, потому что под капотом это AccessibilityAction
. Действия доступности возвращают булево значение, указывающее, было ли действие успешно обработано.
Это решение не идеально — но это прогресс. Таким образом, фокус остается на месте, и пользователю не нужно возвращаться к стопке, чтобы удалить следующую карточку.
Голосовой доступ
Одна вещь, которую можно было бы улучшить для использования голосового управления — это расширение содержимого меток для кнопок удаления. Даже если отключить кнопки под самой верхней карточкой, остается две кнопки удаления: на самой верхней карточке и на единственной карточке в верхней части экрана.
Почему это является проблемой? Когда пользователь с Voice Access говорит «Нажмите Delete», есть два варианта удаления. Работа Voice Access заключается в том, что, когда есть несколько доступных действий с одним и тем же ярлыком, он отображает номера для каждого действия, а затем пользователь может выбрать одно из них. Таким образом, в данном случае, чтобы удалить карточку из стопки, пользователю нужно сказать что-то вроде «Нажмите Delete. Два».
Все это работает и не кажется большой проблемой, но если вы целыми днями пользуетесь голосом, то любой способ сэкономить на его использовании был бы предпочтительнее. Честно говоря, я не уверен, есть ли хороший способ решить эту проблему для данного конкретного случая, и является ли ее решение приоритетным (по сравнению с некоторыми более очевидными случаями), но это полезно иметь в виду.
Устройство для чтения с экрана
У пользователей программ для чтения с экрана возникло несколько проблем. Наиболее очевидная из них заключается в том, что приложение отображает изображения, а описания содержимого ни для одного из них не доступно. Поскольку приложение использует изображения из API, ответственность за написание описаний контента должна лежать на разработчиках API, а приложение должно просто использовать эти описания.
Конечно, если бы мы хотели улучшить опыт, мы могли бы подумать о некоторых AI-решениях для написания текстовых альтернатив для изображений. Такое решение может стать улучшением, особенно если текст будет более описательным, чем «Может быть изображением животного», как это сделал (делает? я больше не на FB) Facebook*, автоматизируя подписи. Однако всегда есть риск — человек не контролирует сгенерированные тексты, и в итоге они могут оказаться какими угодно. Вот почему я выступаю за то, чтобы ответственность за написание текстовых альтернатив для изображений лежала на тех, кто их предоставляет.
Другая проблема для пользователей скринридеров связана с тем же, что и для пользователей голосового доступа — кнопки удаления и их обозначения. Сейчас, особенно без описаний контента, у пользователей, читающих с экрана, просто есть две кнопки удаления, без какого-либо контекста, что именно они удаляют. Честно говоря, этот пользовательский интерфейс сейчас (без описаний содержимого) действительно отстой для тех, кто не видит и полагается на программу чтения с экрана для навигации.
Еще одна проблема, которую я обнаружила — та же, что и в Switch Access. Фокус исчезал после нажатия кнопки Delete. К счастью, этот же код устраняет проблему и для пользователей скринридеров, так что мы можем отметить эту проблему как решенную.
Последнее улучшение для пользователей программ чтения с экрана — добавление семантической информации для заголовков, чтобы помочь с навигацией. Для этого мы добавим модификатор семантики в компонент Title:
// MainScreen.kt @Composable fun Title(text: String) { Text( modifier = Modifier.semantics { heading() }, text = text, style = MaterialTheme.typography.titleLarge, ) }
Таким образом, заголовок будет обозначен как заголовок.
Клавиатура
Следующее соображение, касающееся доступности, относится к навигации с клавиатурой. При тестировании приложения с помощью клавиатуры я обнаружила две проблемы. Во-первых, все кнопки удаления карточек в стопке были фокусируемыми, и при удалении карточки фокус терялся.
Прежде чем продолжить, следует сделать замечание: Если вам интересно, почему я снова упоминаю проблему фокуса, то причина в том, что фокус по-разному работает на клавиатуре и в сервисах доступности, таких как программы чтения с экрана и управлении кнопками. Поэтому проблему нужно решать дважды.
Фокус только на видимых кнопках
Во-первых, мы должны сделать фокусируемой только кнопку удаления самой верхней карточки и убрать фокусировку с остальных в стопке. Для этого нам понадобится несколько вещей. Во-первых, карта должна знать, является ли она самой верхней, и мы должны установить focusProperties
, основываясь на этом.
Передадим CatCard
последний id
в стеке, проверим, является ли текущая карта последней, и установим фокусируемость, исходя из этого:
// CatCard.kt @Composable fun CatCard( ... lastIdOnStack: String, ... ) { val isLastOnStackPerIds = remember(id, lastIdOnStack) { id == lastIdOnStack } ElevatedCard(...) { IconButton( modifier = Modifier .focusProperties { canFocus = isLastOnStackPerIds }, ... ) { ... }
Таким образом, мы устраняем фокусировку на кнопках удаления под самой верхней карточкой, поскольку они не фокусируются, если id
не совпадает с последней карточкой.
Восстановление фокуса после удаления
Второе, что нам нужно сделать, — это исправить исчезновение фокуса после удаления карточки. Решение кажется простым. Нужно сделать кнопку удаления предыдущей карточки фокусируемой и переместить на нее фокус. В коде это требует немного больше работы.
Нам понадобится функция для обработки удаления карточки. После вызова функции удаления во ViewModel нам нужно будет немного подождать, чтобы второй элемент в стеке стал фокусным. После этого нам нужно будет использовать FocusManager
, чтобы переместить фокус на предыдущий элемент. В коде это будет выглядеть следующим образом:
// MainScreen.kt val scope = rememberCoroutineScope() val focusManager = LocalFocusManager.current fun deleteCat(id: String) { scope.launch { viewModel.deleteCat(id) delay(1000) focusManager.moveFocus(FocusDirection.Previous) } }
Нам нужно переключиться на использование этой функции вместо вызова функции viewModel
, как это было раньше:
// MainScreen.kt CardStack { ... AnimatedCatCard(...) { deleteCat(id = id) } }
После этих изменений фокус переходит к предыдущему элементу после удаления карточки.
Когнитивные соображения
Несмотря на простоту приложения, в нем есть некоторые когнитивные аспекты, связанные с доступностью. Я заметил, что кнопка удаления имеет только значок, а не какой-либо текст. Не все понимают или помнят, что значок X означает удаление (или закрытие) чего-либо. Увидев такой значок, они могут растеряться и не понять, что с ним делать или как удалить элемент из стопки карточек.
Мы могли бы улучшить ситуацию, введя настройки доступности приложения и добавив переключатель, чтобы показывать ярлыки с иконками. Я написала статью в блоге о том, как это сделать — «Переключаемые ярлыки и иконки — персонализация доступности«.
Ориентация экрана
Я также протестировала приложение в ландшафтном режиме, что является важной особенностью доступности. Поскольку я не привязывала ориентацию к портретному режиму, а данные хранятся в хранилище данных, а не в состоянии внутри composable, все работает хорошо, как вы можете видеть на видео:
Если вы хотите узнать больше о поддержке ландшафтного и портретного режимов в вашем приложении, я написала статью в своем блоге — «Не блокируйте ориентацию экрана! Обработка ориентации в Compose«.
Более крупный шрифт и увеличенный размер экрана
Поскольку в приложении не так много текста, увеличение размера шрифта работает довольно хорошо, и текст масштабируется. Кроме того, макет гибко настраивается по размеру, поэтому он работает с увеличенными размерами дисплея. Вот скриншот, когда размер шрифта установлен на самый большой, а размер дисплея — на самый большой:
Заключение
В этой статье мы рассмотрели способы улучшения доступности раскладки карт в стопке. Мы обсудили управление кнопками, клавиатурой, голосовой доступ и считыватель экрана, а также ориентацию экрана, увеличение размера шрифта и размера дисплея, а также когнитивные соображения.
Этот коммит содержит все изменения, упомянутые в этой записи блога. Он также содержит некоторые другие незначительные изменения, поскольку в приложении есть не только сложенные карты, и мне пришлось изменить другие аспекты, чтобы они также работали с этими изменениями.
Узнали ли вы что-то новое из этой статьи? Улучшили ли вы каким-то образом доступность пользовательских макетов? Пожалуйста, поделитесь!