Реализация поддержки TalkBack для Android-приложения
TalkBack — screen reader для Android, аналог iOS VoiceOver. Включается через Settings → Accessibility → TalkBack или Volume Up + Volume Down долгим нажатием на большинстве устройств. При включённом TalkBack одиночный тап — фокус и озвучивание элемента, двойной тап — активация. Свайп вправо/влево — переход между элементами.
Что ломается без проработки
ContentDescription и ImportantForAccessibility
android:contentDescription — эквивалент accessibilityLabel на iOS. Для ImageView, ImageButton — обязателен. Для TextView TalkBack читает text автоматически. Декоративные изображения: android:importantForAccessibility="no" (XML) или ViewCompat.setImportantForAccessibility(view, ViewCompat.IMPORTANT_FOR_ACCESSIBILITY_NO) — TalkBack пропускает.
Частая ошибка в Compose: Icon(painter = painterResource(R.drawable.ic_close), contentDescription = null) — иконка недостижима для TalkBack, если она не завёрнута в кликабельный элемент с явным описанием. IconButton с contentDescription у Icon — правильно.
Группировка элементов
ViewGroup с android:focusable="true" и android:importantForAccessibility="yes" — TalkBack читает все дочерние элементы как один: contentDescription контейнера. Для карточки товара (изображение + название + цена + кнопка) — выгоднее сделать карточку одним accessible элементом с составным contentDescription через ViewCompat.setAccessibilityDelegate.
В Jetpack Compose: Modifier.semantics(mergeDescendants = true) { contentDescription = "Товар: $name, цена: $price" } — объединяет все дочерние элементы в один для TalkBack.
AccessibilityDelegate и кастомные действия
TalkBack по умолчанию объявляет стандартные действия: «Double-tap to activate». Кастомные действия (свайп по карточке → удалить, долгое нажатие → меню) нужно регистрировать явно:
ViewCompat.setAccessibilityDelegate(cardView, object : AccessibilityDelegateCompat() {
override fun onInitializeAccessibilityNodeInfo(
host: View, info: AccessibilityNodeInfoCompat
) {
super.onInitializeAccessibilityNodeInfo(host, info)
info.addAction(
AccessibilityNodeInfoCompat.AccessibilityActionCompat(
AccessibilityNodeInfoCompat.ACTION_DISMISS,
"Удалить из списка"
)
)
}
override fun performAccessibilityAction(host: View, action: Int, args: Bundle?): Boolean {
if (action == AccessibilityNodeInfoCompat.ACTION_DISMISS) {
removeItem()
return true
}
return super.performAccessibilityAction(host, action, args)
}
})
Live Regions для динамического контента
Счётчик корзины, таймер обратного отсчёта, статус загрузки — контент меняется без действия пользователя. android:accessibilityLiveRegion="polite" — TalkBack озвучит изменение, когда пользователь не занят. "assertive" — перебьёт текущее озвучивание. В Compose: Modifier.semantics { liveRegion = LiveRegionMode.Polite }.
RecyclerView и фокус
TalkBack линейно обходит RecyclerView по элементам. Если RecyclerView вложен в ScrollView — фокус может застрять. RecyclerView не должен быть вложен в ScrollView: стандартная рекомендация Material Design и она же необходима для доступности.
Элемент RecyclerView с несколькими кликабельными зонами (например, карточка + кнопка «Добавить» внутри): убедиться, что TalkBack фокусируется на каждой зоне отдельно, а не только на корневом view. descendantFocusability="blocksDescendants" на корне ломает доступность дочерних кнопок.
Процесс аудита
Включаем TalkBack на реальном устройстве (не эмулятор — Samsung One UI и stock Android ведут себя по-разному). Проходим основные user flow. Фиксируем: элементы без описания, недостижимые зоны, неправильный порядок фокуса, отсутствие live regions для динамических данных.
Инструменты: Accessibility Scanner (Google Play) — визуально подсвечивает проблемы прямо поверх приложения. Android Studio Layout Inspector с Accessibility tab. Espresso с AccessibilityChecks для автоматизированного регрессионного тестирования:
@Before
fun setUp() {
AccessibilityChecks.enable().setRunChecksFromRootView(true)
}
Срок: 3-5 дней. На Samsung устройствах нужно дополнительно проверять — One UI добавляет поверх TalkBack собственные жесты, которые конфликтуют с кастомными gesture recognizer'ами.







