Site icon AppTractor

Вопросы доступности при использовании кастомного макета стопки карт

Пару недель назад я опубликовала статью «Кастомные макеты в 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«.

Более крупный шрифт и увеличенный размер экрана

Поскольку в приложении не так много текста, увеличение размера шрифта работает довольно хорошо, и текст масштабируется. Кроме того, макет гибко настраивается по размеру, поэтому он работает с увеличенными размерами дисплея. Вот скриншот, когда размер шрифта установлен на самый большой, а размер дисплея — на самый большой:

Заключение

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

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

Узнали ли вы что-то новое из этой статьи? Улучшили ли вы каким-то образом доступность пользовательских макетов? Пожалуйста, поделитесь!

Источник

Exit mobile version