API по умолчанию

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

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

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

Минимальные размеры сенсорных целей

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

Компоненты материала, такие как Checkbox , RadioButton , Switch , Slider и Surface устанавливают этот минимальный размер внутри себя, но только тогда, когда компонент может получать действия пользователя. Например, если для параметра onCheckedChange Checkbox установлено значение, отличное от NULL, флажок включает в себя отступы, чтобы ширина и высота составляли не менее 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 , вы обычно переносите интерактивное поведение в родительский контейнер, устанавливая для обратного вызова щелчка на составном элементе значение null и добавляя 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. Флажок с кликабельным поведением.

Если размер интерактивного составного объекта меньше минимального размера сенсорного объекта, Compose все равно увеличивает размер сенсорного объекта. Это достигается за счет расширения размера цели касания за пределы компонуемого объекта.

Следующий пример содержит очень маленький кликабельный 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. Очень маленькое интерактивное поле, которое расширяется до более крупной сенсорной цели.

Чтобы предотвратить возможное перекрытие сенсорных областей разных составных элементов, всегда используйте достаточно большой минимальный размер для составного объекта. В данном примере это будет означать использование модификатора 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. Цель касания в виде более крупного прямоугольника.

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

Когда вы определяете компонуемое 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 создают элементы пользовательского интерфейса, с которыми пользователи могут взаимодействовать с помощью clickable и toggleable API-модификаторов. Поскольку интерактивные компоненты могут состоять из нескольких элементов, clickable и toggleable по умолчанию объединяют семантику своих дочерних элементов, так что компонент рассматривается как один логический объект.

Например, 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 , сделают это за вас. Это связано с тем, что логика слияния гарантирует, что самая внешняя метка модификатора и действие будут предприняты для имеющейся информации.

В предыдущем примере действие щелчка openArticle() передается глубоко вниз NestedArticleListItem автоматически в его clickable семантику и может быть оставлено нулевым во втором действии модификатора семантики. Однако метка клика взята из второго модификатора семантики onClick(label = "Open this article") , поскольку она не присутствовала в первом.

Вы можете столкнуться со сценариями, в которых вы ожидаете, что дочерняя семантика будет объединена с родительской, но этого не происходит. См. раздел «Слияние и очистка» для получения более подробной информации.

Пользовательские компоненты

Для пользовательских компонентов, как правило, посмотрите на реализацию аналогичного компонента в библиотеке материалов или других библиотеках Compose и имитируйте или измените его поведение доступности, где это разумно.

Например, если вы заменяете Checkbox «Материал» своей собственной реализацией, просмотр существующей реализации флажка напомнит вам о необходимости добавить модификатор triStateToggleable , который обрабатывает свойства доступности для этого компонента.

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

Вы также можете найти пример пользовательского компонента-переключателя в разделе «Очистить и установить семантику» , а более подробную информацию о том, как поддерживать специальные возможности в пользовательских компонентах, можно найти в рекомендациях по API .

{% дословно %} {% дословно %} {% дословно %} {% дословно %}