Введение
«Что такое contentDescription и зачем он нужен?» — вопрос, который открывает тему доступности почти на каждом собеседовании. И тут же выясняется, что большинство кандидатов с опытом 1–2 года ставили этот атрибут «чтобы линт не ругался», не понимая, что за ним стоит целая система.
Доступность (accessibility) в Android — это механизм, который позволяет людям с особенностями зрения, слуха или моторики пользоваться приложением: через экранную озвучку, управление голосом, переключатели. По данным Всемирного банка, ту или иную форму инвалидности имеет около 15% населения мира — это не «три пользователя с Pixel», а огромная аудитория, которая просто уходит из неудобного приложения.
В этой статье разберём, как устроена доступность в Android, зачем нужны описания элементов, что такое фокус и порядок навигации, какие есть правила контраста и размеров и как всё это проверять. Материал закроет и базовые вопросы собеседования, и реальную работу с приложением.
Что такое доступность и как она работает
В основе всего лежит понятие accessibility-сервиса — системного компонента, который «читает» интерфейс приложения и помогает пользователю с ним взаимодействовать. Приложение не знает, какой сервис подключён, — оно просто описывает свой UI, а сервис решает, как его представить.
Главные системные сервисы Android, которые стоит знать по именам:
- TalkBack — для людей с низким зрением или слепых. Озвучивает содержимое экрана синтезированным голосом и выполняет действия по жестам пользователя. Именно его описания вы настраиваете через
contentDescription. - Switch Access — для людей с нарушениями моторики. Подсвечивает интерактивные элементы и позволяет управлять устройством одной-двумя кнопками.
- Voice Access — управление приложением голосовыми командами.
Главная идея для собеседования
Android-фреймворк позволяет создавать accessibility-сервисы, которые представляют контент приложения пользователю и управляют приложением от его имени. Задача разработчика — сделать так, чтобы интерфейс был «читаемым» для этих сервисов: у каждого значимого элемента есть описание, роль и состояние.
Из этой модели следует практический вывод: доступность — это не отдельная страница настроек, а качество описания UI. Экран читается не «пикселями», а семантикой: что это за элемент (кнопка, переключатель, заголовок), что он делает, в каком он состоянии. Чем лучше вы описали эти свойства, тем лучше сервис справляется.
Описания элементов: contentDescription и семантика
Когда TalkBack наводит фокус на элемент, он озвучивает его описание. Для текстовых элементов всё просто: система читает сам текст. Поэтому для TextView или Text в Compose отдельное описание не нужно — TalkBack и так озвучит содержимое.
А вот всё, что не является текстом, требует описания вручную. В Views за это отвечает атрибут android:contentDescription, в Compose — параметр contentDescription у Icon, Image и им подобных.
activity_main.xml
<ImageButton
android:id="@+id/btn_share"
android:layout_width="48dp"
android:layout_height="48dp"
android:contentDescription="@string/action_share"
android:src="@drawable/ic_share" />ShareButton.kt
@Composable
fun ShareButton(onClick: () -> Unit) {
IconButton(onClick = onClick) {
Icon(
imageVector = Icons.Filled.Share,
contentDescription = stringResource(R.string.label_share)
)
}
}Здесь есть несколько правил, по которым видно «понимающего» кандидата:
- Описание передаёт назначение, а не внешний вид. Не «синяя иконка стрелки», а «Поделиться». TalkBack и так сообщит тип элемента через роль, поэтому «Кнопка поделиться» — избыточно: достаточно «Поделиться».
- Каждое описание уникально. Если в списке у всех элементов одинаковое описание, пользователь не поймёт, где он находится. Для элементов в
RecyclerView/LazyColumnописание задаётся в адаптере и учитывает данные позиции. - Декоративные элементы помечаются как невидимые. Картинка, которая нужна только для красоты, не должна озвучиваться: в Views —
android:importantForAccessibility="no", в Compose —contentDescription = null.
BannerImage.kt
Image(
painter = painterResource(R.drawable.banner),
contentDescription = null // чисто декоративный элемент
)«Я всем Image поставил contentDescription с текстом вроде „иконка стрелки“». Это шум: TalkBack будет озвучивать мусор вместо смысла. Либо осмысленное описание действия, либо null/importantForAccessibility="no" для декора. Третьего не дано.
Отдельно про пары «поле + подпись»: если подпись к EditText лежит отдельным TextView, свяжите их атрибутом android:labelFor — тогда TalkBack озвучит подпись при фокусе на поле, даже если визуально это два разных элемента.
Группы, заголовки и порядок фокуса
TalkBack двигается по экрану элемент за элементом в порядке фокуса. Проблема в том, что по умолчанию фокусируемым становится каждый компонент, и экран «разваливается» на десятки мелких кусков. Правильная навигация — это когда сервис озвучивает осмысленные группы.
Классический пример: карточка песни с названием и исполнителем. Озвучивать их по отдельности неудобно — пользователь хочет услышать «Название, Исполнитель» одним сообщением. В Views для этого собирают элементы в группу: контейнеру — android:screenReaderFocusable="true", внутренним элементам — android:focusable="false".
item_song.xml
<LinearLayout
android:id="@+id/song_container"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical"
android:screenReaderFocusable="true">
<TextView
android:id="@+id/song_title"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:focusable="false" />
<TextView
android:id="@+id/song_artist"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:focusable="false" />
</LinearLayout>В Compose аналог — Modifier.semantics { mergeDescendants = true } на контейнере: дочерние описания объединяются в одно объявление. И правило из официальных принципов доступности: когда элементы сгруппированы, интерактивным делают только родителя, а не каждого ребёнка.
Ещё два механизма, которые любят спрашивать на собеседовании:
- Заголовки. Пользователь TalkBack может навигировать по заголовкам, как по оглавлению. В Views —
android:accessibilityHeading="true", в Compose —Modifier.semantics { heading() }. - Pane titles. На Android 9 (API 28+) экран можно разбить на логические «панели» (например, корзина и каталог) и дать каждой название через
android:accessibilityPaneTitle(в Compose —paneTitleв семантике). Тогда сервис понимает, что произошло изменение в конкретной панели.
Как отвечать про фокус
Ключевое слово — «семантика». TalkBack не «видит» экран как картинку: он обходит семантическое дерево элементов в порядке фокуса. Наша задача — сделать это дерево осмысленным: описать элементы, сгруппировать связанные, пометить декоративное, расставить заголовки. Если кандидат говорит это своими словами — тема закрыта.
Контраст, цели касания и не только цвет
Доступность — это не только экранные дикторы. Есть жёсткие визуальные правила, которые проверяются автоматически и которые стоит знать точно.
Контраст текста. Отношение контраста между цветом текста и фоном должно быть минимум 4,5:1 для обычного текста (меньше 18sp, или жирного меньше 14sp) и 3:1 для крупного. Это не «рекомендация дизайнера», а порог из гайдлайнов — его можно проверить любым онлайн-калькулятором или приложением Accessibility Scanner.
Цель касания. Минимальная область касания для интерактивного элемента — 48 × 48 dp. «Больше — лучше», и на маленьком экране легко сделать так, что кнопка визуально 32 dp, а её цель касания 48 dp за счёт невидимой области. В Compose это делается просто:
TouchTarget.kt
@Composable
fun RatingIcon(onClick: () -> Unit) {
Box(
modifier = Modifier
.size(48.dp) // минимальная цель касания
.clip(CircleShape)
.clickable(onClick = onClick)
)
}Не только цвет. Если два состояния элемента отличаются только цветом (активная вкладка, выбранный пункт), пользователь с дальтонизмом их не различит. Гайдлайн гласит: используйте в дополнение к цвету форму, текст, паттерн или тактильный отклик. На собеседовании стоит упомянуть, что вы кодируете состояние не только цветом.
- Контраст текста: минимум 4,5:1 для обычного, 3:1 для крупного текста.
- Цель касания: минимум 48 × 48 dp.
- Состояние элемента нельзя кодировать только цветом — добавьте форму, текст или паттерн.
- Медиа: у видео нужны субтитры и контролы, у аудио — транскрипт, если контент критичен.
Как проверять доступность
Хорошая новость: многое проверяется автоматически, и это отдельная тема для собеседования. Если кандидат рассказывает, как он проверял доступность, — это сильный сигнал.
- Accessibility Scanner — приложение Google, которое сканирует открытый экран и находит проблемы: маленькие цели касания, низкий контраст, отсутствующие описания. Запускается поверх любого приложения.
- Android Lint — часть сборки. Часть accessibility-проблем (например, отсутствие
contentDescriptionуImageView) ловит статический анализ. - TalkBack вручную — включите TalkBack и пройдите весь сценарий жестами с выключенным экраном. Это самый честный тест: вы сразу услышите, где навигация «плывёт».
- Автотесты — в Compose можно проверять семантику (
SemanticsNodeInteraction, утилиты изandroidx.compose.ui.test), в Views — accessibility-события черезAccessibilityService-тесты.
Самый быстрый способ понять ценность доступности — включить TalkBack и попробовать купить что-нибудь в своём же приложении с закрытыми глазами. Пять минут такого «теста» дают больше понимания, чем месяц чтения документации. Если после этого вы расскажете на собеседовании, где именно сломался ваш сценарий и как вы это чинили, — это запомнят.
Меня зовут Рустем Бикбулатов, я senior Android-разработчик и ментор Яндекс Практикума, провожу технические собеседования. За время менторства у меня больше 100 учеников и разборов. Тема доступности — из тех, что большинство кандидатов с опытом 1–2 года пропускает целиком, а зря: на собеседовании она отлично отделяет тех, кто «писал экраны», от тех, кто думал о пользователе.
Хотите прокачаться перед собеседованием?
Вопросы про UI, доступность и качество интерфейсов — это не «скучная обязаловка», а способ выделиться на фоне остальных кандидатов. Бесплатная диагностика: разберу ваши пробелы и составлю план роста за 30–40 минут, без обязательств.
Итоги
Доступность в Android — это не набор атрибутов, а система описания интерфейса для accessibility-сервисов. Поймите её, и вопросы «зачем contentDescription» перестанут быть страшными.
- Сервисы — TalkBack, Switch Access, Voice Access «читают» семантику вашего UI и помогают пользователю. Ваша задача — описать элементы, а не «нарисовать» их.
- Описания —
contentDescriptionпередаёт назначение, а не внешний вид; декоративное помечается как невидимое; описания уникальны. - Навигация — фокус движется по семантическому дереву: группы (
screenReaderFocusable/mergeDescendants), заголовки, pane titles. - Визуальные правила — контраст 4,5:1, цель касания 48×48 dp, состояние не только цветом.
Что запомнить
Одна фраза для собеседования: «Доступность — это качество семантики UI: каждый элемент описан, сгруппирован и логически упорядочен, чтобы TalkBack мог озвучить и выполнить сценарий без зрительного контакта». Добавьте пример проверки через Accessibility Scanner и TalkBack — и ответ станет запоминающимся.
Если вы узнали себя в «ставил contentDescription, чтобы линт молчал» — это нормальная точка роста. Напишите в Telegram, разберём вашу ситуацию и составим план подготовки.