Перейти к содержимому
Все записи

Доступность на Android в 2026 году: чек-лист по TalkBack и семантике Compose, который реально работает

Практический чек-лист по доступности Android на 2026 год — TalkBack, семантика Compose, размер зон касания и тестовый прогон, который я делаю на каждом экране перед релизом.

MFKAPPS 4 мин чтения

К доступности на Android обычно относятся как к пункту предрелизного чек-листа, втиснутому уже после того, как UI «готов». Такой порядок неверен, и это заметно: экран, собранный без мысли о семантике, потом переделывается часами, а тот же экран, изначально собранный с семантикой, почти не требует дополнительных затрат. Вот чек-лист, который я реально применяю — не программный документ, а список того, что я делаю прямо во время написания UI на Compose, плюс тестовый прогон перед любым релизом.

Почему доделывать потом дорого, а закладывать сразу — нет

TalkBack, экранный диктор Android, читает не пиксели — он читает дерево семантики, которое Compose генерирует параллельно с вашим UI. Если вы ни разу не думаете об этом дереве во время написания экрана, в итоге TalkBack начинает зачитывать сырые детали реализации: кнопка только с иконкой объявляется как «Кнопка», декоративное изображение — как «Изображение», а строка из трёх отдельных composable-компонентов объявляется как три отдельные остановки вместо одной фразы. Исправлять это потом означает возвращаться к каждому composable и спрашивать «а что это на самом деле должно говорить», что медленнее, чем задать тот же вопрос сразу.

Решение — относиться к семантике как к части API компонента, а не как к запоздалой мысли, прицепленной в конце цепочки Modifier.

Чек-лист

1. У каждого нетекстового элемента управления есть contentDescription — либо его нет вовсе

Если у кнопки есть видимый текст, не добавляйте избыточное описание — TalkBack и так прочитает этот текст, а дублирующее описание заставит прочитать его дважды. Если кнопка состоит только из иконки, описание обязательно:

IconButton(onClick = { onDelete(item.id) }) {
    Icon(
        imageVector = Icons.Default.Delete,
        contentDescription = stringResource(R.string.delete_item, item.name)
    )
}

Чисто декоративные изображения явно получают contentDescription = null. Изображение вовсе без описания всё равно объявляется как «изображение без подписи», а это хуже тишины.

2. Группируйте связанный контент через mergeDescendants

Карточка с заголовком, подзаголовком и бейджем по умолчанию читается как три отдельные остановки TalkBack — то есть три свайпа, чтобы услышать одну строку. Оберните группу так, чтобы она объявлялась как единое целое:

Row(
    modifier = Modifier.semantics(mergeDescendants = true) {}
) {
    Text(title)
    Text(subtitle)
    Badge { Text(status) }
}

Проверяйте это, включив TalkBack и один раз проведя пальцем по экрану. Если для одной логической строки требуется больше одного свайпа, её нужно объединить.

3. Зоны касания — 48dp, а не визуальный размер иконки

Иконка 24dp без отступов даёт зону касания 24dp, что нарушает и рекомендации по доступности, и базовое удобство использования для человека со сниженной моторикой. Modifier.minimumInteractiveComponentSize() в текущем Compose решает это без ручных расчётов отступов — применяйте его к каждой нажимаемой иконке, а не только к тем, что кажутся тесными при код-ревью.

4. Не передавайте состояние только цветом

Красная рамка у невалидного поля невидима для пользователя с нарушением цветовосприятия и никак не объявляется для пользователя TalkBack. Сопровождайте каждый сигнал, зависящий только от цвета, изменением текста или иконки: сообщение об ошибке под полем, значок ошибки рядом с меткой. Это заодно и лучший UX для зрячего пользователя, бросающего быстрый взгляд, — цвет сам по себе требует больше внимания на интерпретацию, чем слово.

5. Живые регионы для контента, меняющегося без клика

Если сумма обновляется после того, как пользователь ввёл число, TalkBack этого не заметит, пока composable не помечен как живой регион:

Text(
    text = "Итого: $formattedTotal",
    modifier = Modifier.semantics { liveRegion = LiveRegionMode.Polite }
)

Polite дожидается окончания текущего объявления; Assertive прерывает его. Используйте Assertive экономно — для ошибки, требующей немедленного внимания, а не для меняющейся суммы.

Тестовый прогон

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

  1. TalkBack, свайп по всему экрану с выключенным дисплеем, только на слух. Если по одному объявлению не понятно, что делает элемент управления, ему нужно описание получше.
  2. Accessibility Scanner (отдельное приложение Google) на размер зон касания и контрастность — он автоматически отмечает оба параметра и указывает точный composable.
  3. Масштаб шрифта 200% в системных настройках. Текст, который обрезается, накладывается или клипуется при таком масштабе, — это баг, а не крайний случай: значительная доля реальных пользователей постоянно держит увеличенный размер шрифта.

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

Вывод

Работа над доступностью, сделанная в конце проекта, обходится дорого, потому что она корректирующая — кто-то должен заметить пробел, а потом вернуться и исправлять каждый экран по отдельности. Работа над доступностью, сделанная попутно с написанием каждого composable, почти бесплатна, потому что на вопросы («как это объявляется», «это одна остановка или три», «зона касания реально достаточная») уходят секунды, пока вы и так смотрите на код. Относитесь к чек-листу как к привычке для новых экранов, а не как к авральной проверке релизной недели — и тогда он вообще перестаёт быть чек-листом.

// По теме

Ещё из журнала

MFKAPPS 4 мин чтения

In-App Review API в Android: как просить оценку, не раздражая пользователя

Практическое руководство по In-App Review API от Google — как он на самом деле работает, когда его запускать и почему обычный попап «оцените нас» незаметно портит вашу оценку в Play Store.

#android #engineering #kotlin
MFKAPPS 5 мин чтения

Ярлыки приложений Android и плитка быстрых настроек: логирование воды без открытия приложения

Практическое руководство по динамическому ShortcutManager API и TileService в Android — как позволить действию в один тап полностью обойти приложение, и о подводных камнях, на которых спотыкается большинство реализаций.

#android #engineering #kotlin
MFKAPPS 4 мин чтения

Типы foreground-сервисов в Android в 2026 году: как выбрать тот, что реально подходит вашей функции

Практическое руководство по ограничениям типов foreground-сервисов Android — dataSync, mediaPlayback, specialUse, shortService — и как выбрать правильный, не получив ни принудительной остановки, ни отказа в проверке.

#android #engineering #kotlin