Значения по умолчанию в API

В Material, Compose UI и Foundation API по умолчанию реализованы многие функции, обеспечивающие доступность. Они содержат встроенную семантику, соответствующую их роли и функции. Это означает, что большинство функций специальных возможностей предоставляются без дополнительных усилий.

Использование подходящих API для подходящих целей означает, что компоненты обычно поставляются с предопределенными функциями доступности, которые охватывают стандартные сценарии использования. Однако всегда проверяйте, соответствуют ли эти настройки вашим потребностям. Если нет, в Compose есть способы удовлетворить более специфические требования.

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

Минимальные размеры областей прикосновения

Любой элемент на экране, с которым можно взаимодействовать, должен быть достаточно большим, чтобы пользователь мог без проблем нажимать на него или касаться его. При определении размера этих элементов убедитесь, что минимальный размер составляет 48 dp, чтобы соблюдать руководство по обеспечению доступности Material Design.

Минимальный размер для компонентов Material, таких как Checkbox, RadioButton, Switch, Slider и Surface, задается автоматически, но только если компонент может получать действия пользователя. Например, если для параметра onCheckedChange элемента Checkbox задано ненулевое значение, то вокруг флажка добавляется отступ, чтобы его ширина и высота составляли не менее 48 dp.

@Composable
private fun CheckableCheckbox() {
    Checkbox(checked = true, onCheckedChange = {})
}

Поле для флажка с отступами по умолчанию, шириной и высотой 48 dp.
Рисунок 1. Флажок с отступами по умолчанию.

Если параметру onCheckedChange присвоено значение null, отступ не добавляется, поскольку с компонентом нельзя взаимодействовать напрямую.

@Composable
private fun NonClickableCheckbox() {
    Checkbox(checked = true, onCheckedChange = null)
}

Флажок без полей.
Рисунок 2. Флажок без отступов.

При реализации элементов управления выбором, таких как Switch, RadioButton или Checkbox, обычно интерактивность переносится на родительский контейнер. Для этого в composable-функцию добавляется обратный вызов клика null, а в родительскую composable-функцию – модификатор toggleable или selectable.

@Composable
private fun CheckableRow() {
    MaterialTheme {
        var checked by remember { mutableStateOf(false) }
        Row(
            Modifier
                .toggleable(
                    value = checked,
                    role = Role.Checkbox,
                    onValueChange = { checked = !checked }
                )
                .padding(16.dp)
                .fillMaxWidth()
        ) {
            Text("Option", Modifier.weight(1f))
            Checkbox(checked = checked, onCheckedChange = null)
        }
    }
}

Флажок рядом с текстом "Вариант", который устанавливается и снимается.
Рисунок 3. Флажок с возможностью выбора.

Если размер composable-функции, по которой можно кликнуть, меньше минимального размера области прикосновения, Compose все равно увеличивает область прикосновения. Это происходит за счет увеличения области прикосновения за пределами границ composable-функции.

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

@Composable
private fun SmallBox() {
    var clicked by remember { mutableStateOf(false) }
    Box(
        Modifier
            .size(100.dp)
            .background(if (clicked) Color.DarkGray else Color.LightGray)
    ) {
        Box(
            Modifier
                .align(Alignment.Center)
                .clickable { clicked = !clicked }
                .background(Color.Black)
                .size(1.dp)
        )
    }
}

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

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

@Composable
private fun LargeBox() {
    var clicked by remember { mutableStateOf(false) }
    Box(
        Modifier
            .size(100.dp)
            .background(if (clicked) Color.DarkGray else Color.LightGray)
    ) {
        Box(
            Modifier
                .align(Alignment.Center)
                .clickable { clicked = !clicked }
                .background(Color.Black)
                .sizeIn(minWidth = 48.dp, minHeight = 48.dp)
        )
    }
}

Размер очень маленького поля из предыдущего примера увеличен, чтобы создать более крупную область прикосновения.
Рисунок 5. Большая область прикосновения.

Графические элементы

Когда вы определяете composable-функцию Image или Icon, фреймворк Android не может автоматически понять, что именно отображает приложение. Вам нужно передать текстовое описание графического элемента.

Представьте экран, на котором пользователь может поделиться текущей страницей с друзьями. На этом экране есть значок "Поделиться", на который можно нажать:

Полоса с четырьмя кликабельными значками, на которой выделен значок "Поделиться".
Рисунок 6. Ряд значков, на которые можно нажать. Выбран значок "Поделиться".

Только по значку фреймворк Android не может описать его пользователю с нарушением зрения. Для фреймворка Android требуется дополнительное текстовое описание значка.

Параметр contentDescription описывает графический элемент. Используйте локализованную строку, поскольку она будет видна пользователю.

@Composable
private fun ShareButton(onClick: () -> Unit) {
    IconButton(onClick = onClick) {
        Icon(
            imageVector = Icons.Filled.Share,
            contentDescription = stringResource(R.string.label_share)
        )
    }
}

Некоторые графические элементы носят чисто декоративный характер, и вы можете не захотеть сообщать о них пользователю. Если для параметра contentDescription задано значение null, фреймворк Android понимает, что у этого элемента нет связанных действий или состояний.

@Composable
private fun PostImage(post: Post, modifier: Modifier = Modifier) {
    val image = post.imageThumb ?: painterResource(R.drawable.placeholder_1_1)

    Image(
        painter = image,
        // Specify that this image has no semantic meaning
        contentDescription = null,
        modifier = modifier
            .size(40.dp, 40.dp)
            .clip(MaterialTheme.shapes.small)
    )
}

contentDescription в основном используется для графических элементов, таких как изображения. Материальные компоненты, такие как Button или Text, и интерактивные элементы, такие как clickable или toggleable, имеют другие предопределенные семантические свойства, описывающие их поведение, которые можно изменить с помощью других API Compose.

Интерактивные элементы

API Material и Foundation Compose позволяют создавать элементы интерфейса, с которыми пользователи могут взаимодействовать с помощью API модификаторов clickable и toggleable. Поскольку интерактивные компоненты могут состоять из нескольких элементов, clickable и toggleable по умолчанию объединяют семантику дочерних элементов, чтобы компонент воспринимался как единый логический объект.

Например, элемент Material Button может состоять из дочернего значка и текста. Вместо того чтобы рассматривать дочерние элементы как отдельные объекты, Material Button по умолчанию объединяет их семантику, чтобы сервисы специальных возможностей могли сгруппировать их соответствующим образом:

Кнопки с семантикой не объединенных и объединенных дочерних элементов.
Рисунок 7. Кнопки с семантикой объединенных и необъединенных дочерних элементов.

Аналогично, использование модификатора clickable приводит к тому, что компонуемый объект объединяет семантику своих потомков в единый объект, который отправляется в сервисы специальных возможностей с соответствующим представлением действия:

Row(
    // Uses `mergeDescendants = true` under the hood
    modifier = Modifier.clickable { openArticle() }
) {
    Icon(
        painter = painterResource(R.drawable.ic_logo),
        contentDescription = "Open",
    )
    Text("Accessibility in Compose")
}

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

Row(
    modifier = Modifier
        .clickable(onClickLabel = "Open this article") {
            openArticle()
        }
) {
    Icon(
        painter = painterResource(R.drawable.ic_logo),
        contentDescription = "Open"
    )
    Text("Accessibility in Compose")
}

Например, если использовать TalkBack, этот модификатор clickable и его ярлык нажатия позволят TalkBack показывать подсказку "Дважды нажмите, чтобы открыть эту статью", а не стандартную подсказку "Дважды нажмите, чтобы активировать".

Отзыв зависит от типа действия. При долгом нажатии TalkBack подсказывает: "Нажмите дважды и удерживайте, чтобы…", а затем называет ярлык:

Row(
    modifier = Modifier
        .combinedClickable(
            onLongClickLabel = "Bookmark this article",
            onLongClick = { addToBookmarks() },
            onClickLabel = "Open this article",
            onClick = { openArticle() },
        )
) {}

В некоторых случаях у вас может не быть прямого доступа к модификатору clickable (например, если он задан на более низком вложенном уровне), но вы все равно хотите изменить ярлык объявления по умолчанию. Для этого разделите настройку clickable и изменение объявления, используя модификатор semantics и задав ярлык клика, чтобы изменить представление действия:

@Composable
private fun ArticleList(openArticle: () -> Unit) {
    NestedArticleListItem(
        // Clickable is set separately, in a nested layer:
        onClickAction = openArticle,
        // Semantics are set here:
        modifier = Modifier.semantics {
            onClick(
                label = "Open this article",
                action = {
                    // Not needed here: openArticle()
                    true
                }
            )
        }
    )
}

Не нужно передавать действие при клике дважды. Существующие API Compose, такие как clickable или Button, обрабатывают это за вас. Логика объединения проверяет, что для имеющейся информации используются внешняя метка модификатора и действие. В примере выше NestedArticleListItem автоматически передает действие нажатия openArticle() в семантику clickable. Во втором модификаторе семантики можно оставить действие при нажатии пустым. Однако ярлык клика берется из второго семантического модификатора onClick(label = "Open this document"), поскольку в первом его нет.

Иногда семантика дочернего элемента не объединяется с семантикой родительского элемента. Подробнее о слиянии и очистке…

Специальные компоненты

При создании собственного компонента изучите реализацию похожего компонента в библиотеке Material или других библиотеках Compose. Затем воспроизведите или измените его поведение в соответствии с требованиями специальных возможностей. Например, если вы замените Material Checkbox собственной реализацией, то, посмотрев на существующую реализацию Checkbox, вы вспомните, что нужно добавить модификатор triStateToggleable, который обрабатывает свойства доступности для компонента. Кроме того, активно используйте модификаторы Foundation, поскольку они включают встроенные функции доступности и существующие методы Compose, описанные в этом разделе.

Пример компонента переключателя можно найти в разделе о том, как задавать и очищать семантику. Более подробная информация о том, как обеспечить доступность специальных компонентов, приведена в руководстве по API.