Слияние и очистка

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

Семантика слияния

При применении clickable модификатора к родительскому составному элементу Compose автоматически объединяет все дочерние элементы под ним. Чтобы понять, как интерактивные компоненты Compose Material и Foundation используют стратегии объединения по умолчанию, см. раздел «Интерактивные элементы» .

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

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

Группа элементов пользовательского интерфейса, включающая имя пользователя. Имя выделено.
Рисунок 1. Группа элементов пользовательского интерфейса, включающая имя пользователя. Имя выделено.

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

@Composable
private fun PostMetadata(metadata: Metadata) {
    // Merge elements below for accessibility purposes
    Row(modifier = Modifier.semantics(mergeDescendants = true) {}) {
        Image(
            imageVector = Icons.Filled.AccountCircle,
            contentDescription = null // decorative
        )
        Column {
            Text(metadata.author.name)
            Text("${metadata.date}${metadata.readTimeMinutes} min read")
        }
    }
}

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

Группа элементов пользовательского интерфейса, включающая имя пользователя. Все элементы выделены одновременно.
Рисунок 2. Группа элементов пользовательского интерфейса, включающая имя пользователя. Все элементы выделены одновременно.

Для каждого семантического свойства определена стратегия слияния. Например, свойство ContentDescription добавляет все дочерние значения ContentDescription в список. Стратегию слияния семантического свойства можно проверить, изучив его реализацию mergePolicy в файле SemanticsProperties.kt . Свойства могут принимать значение родительского или дочернего элемента, объединять значения в список или строку, запрещать слияние и вместо этого генерировать исключение, или использовать любую другую пользовательскую стратегию слияния.

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

Элемент списка, содержащий изображение, текст и значок закладки.
Рисунок 3. Элемент списка с изображением, текстом и значком закладки.

@Composable
private fun ArticleListItem(
    openArticle: () -> Unit,
    addToBookmarks: () -> Unit,
) {

    Row(modifier = Modifier.clickable { openArticle() }) {
        // Merges with parent clickable:
        Icon(
            painter = painterResource(R.drawable.ic_logo),
            contentDescription = "Article thumbnail"
        )
        ArticleDetails()

        // Defies the merge due to its own clickable:
        BookmarkButton(onClick = addToBookmarks)
    }
}

Когда пользователь нажимает на clickable элемент Row , открывается статья. Внутри неё находится BookmarkButton для добавления статьи в закладки. Эта вложенная кнопка отображается как необъединённая, в то время как остальное содержимое дочерних элементов внутри строки объединёно:

Объединенное дерево содержит несколько текстов в списке внутри узла Row. Необъединенное дерево содержит отдельные узлы для каждого составного текста.
Рисунок 4. Объединенное дерево содержит несколько текстов в списке внутри узла Row . Необъединенное дерево содержит отдельные узлы для каждого составного Text .

Некоторые компоненты, являющиеся составными элементами, по замыслу разработчиков, не объединяются автоматически под родительским элементом. Родительский элемент не может объединять свои дочерние элементы, если дочерние элементы также объединяются, либо путем явной установки параметра mergeDescendants = true , либо будучи компонентами, которые объединяются сами, например, кнопки или кликабельные элементы. Знание того, как определенные API объединяют или не допускают объединение, может помочь в отладке потенциально неожиданного поведения.

Используйте слияние, когда дочерние элементы образуют логичную и осмысленную группу под своим родителем. Но если вложенные дочерние элементы требуют ручной корректировки или удаления собственной семантики, другие API могут лучше соответствовать вашим потребностям (например, clearAndSetSemantics ).

Очистка и установка семантики

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

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

Обратите внимание, что при очистке с помощью пустой лямбда-функции очищенная семантика не отправляется ни одному потребителю, использующему эту информацию, например, функциям доступности, автозаполнения или тестирования. При перезаписи контента с помощью clearAndSetSemantics{/*semantic information*/} новая семантика заменяет всю предыдущую семантику элемента и его потомков.

Ниже приведён пример пользовательского компонента-переключателя, представленного интерактивной строкой со значком и текстом:

// Developer might intend this to be a toggleable.
// Using `clearAndSetSemantics`, on the Row, a clickable modifier is applied,
// a custom description is set, and a Role is applied.

@Composable
fun FavoriteToggle() {
    val checked = remember { mutableStateOf(true) }
    Row(
        modifier = Modifier
            .toggleable(
                value = checked.value,
                onValueChange = { checked.value = it }
            )
            .clearAndSetSemantics {
                stateDescription = if (checked.value) "Favorited" else "Not favorited"
                toggleableState = ToggleableState(checked.value)
                role = Role.Switch
            },
    ) {
        Icon(
            imageVector = Icons.Default.Favorite,
            contentDescription = null // not needed here

        )
        Text("Favorite?")
    }
}

Хотя иконка и текст содержат некоторую семантическую информацию, вместе они не указывают на то, что этот компонент является переключаемым. Объединения недостаточно, поскольку необходимо предоставить дополнительную информацию о компоненте.

Поскольку этот фрагмент кода создает пользовательский компонент-переключатель, вам необходимо добавить возможность переключения, а также stateDescription , toggleableState и семантику role . Таким образом, состояние компонента и связанное с ним действие будут доступны — например, TalkBack будет объявлять «Двойное касание для переключения» вместо «Двойное касание для активации».

Благодаря очистке исходной семантики и установке новой, более описательной, службы доступности теперь могут видеть, что это переключаемый компонент, который может менять свое состояние.

При использовании clearAndSetSemantics следует учитывать следующее:

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

Скрыть семантику

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

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

@Composable
fun WatermarkExample(
    watermarkText: String,
    content: @Composable () -> Unit,
) {
    Box {
        WatermarkedContent()
        // Mark the watermark as hidden to accessibility services.
        WatermarkText(
            text = watermarkText,
            color = Color.Gray.copy(alpha = 0.5f),
            modifier = Modifier
                .align(Alignment.BottomEnd)
                .semantics { hideFromAccessibility() }
        )
    }
}

@Composable
fun DecorativeExample() {
    Text(
        modifier =
        Modifier.semantics {
            hideFromAccessibility()
        },
        text = "A dot character that is used to decoratively separate information, like •"
    )
}

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

Анализ вариантов использования

Ниже приведено краткое описание вариантов использования, позволяющее четко различать ранее представленные API:

  • Когда контент не предназначен для использования службами обеспечения доступности:
    • Используйте hideFromAccessibility , когда контент может быть декоративным или избыточным, но при этом его необходимо протестировать.
    • Используйте clearAndSetSemantics{} с пустой лямбда-функцией, когда необходимо очистить семантику родительского и дочерних элементов для всех сервисов.
    • Используйте clearAndSetSemantics{/*content*/} с содержимым внутри лямбда-функции, когда семантику компонента необходимо установить вручную.
  • Когда контент следует рассматривать как единое целое, и необходимо, чтобы вся информация о его дочерних элементах была полной:
    • Используйте семантические потомки слияния.
Таблица с различными вариантами использования API.
Рисунок 5. Таблица с различными вариантами использования API.
{% verbatim %} {% endverbatim %} {% verbatim %} {% endverbatim %} ,

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

Семантика слияния

При применении clickable модификатора к родительскому составному элементу Compose автоматически объединяет все дочерние элементы под ним. Чтобы понять, как интерактивные компоненты Compose Material и Foundation используют стратегии объединения по умолчанию, см. раздел «Интерактивные элементы» .

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

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

Группа элементов пользовательского интерфейса, включающая имя пользователя. Имя выделено.
Рисунок 1. Группа элементов пользовательского интерфейса, включающая имя пользователя. Имя выделено.

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

@Composable
private fun PostMetadata(metadata: Metadata) {
    // Merge elements below for accessibility purposes
    Row(modifier = Modifier.semantics(mergeDescendants = true) {}) {
        Image(
            imageVector = Icons.Filled.AccountCircle,
            contentDescription = null // decorative
        )
        Column {
            Text(metadata.author.name)
            Text("${metadata.date}${metadata.readTimeMinutes} min read")
        }
    }
}

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

Группа элементов пользовательского интерфейса, включающая имя пользователя. Все элементы выделены одновременно.
Рисунок 2. Группа элементов пользовательского интерфейса, включающая имя пользователя. Все элементы выделены одновременно.

Для каждого семантического свойства определена стратегия слияния. Например, свойство ContentDescription добавляет все дочерние значения ContentDescription в список. Стратегию слияния семантического свойства можно проверить, изучив его реализацию mergePolicy в файле SemanticsProperties.kt . Свойства могут принимать значение родительского или дочернего элемента, объединять значения в список или строку, запрещать слияние и вместо этого генерировать исключение, или использовать любую другую пользовательскую стратегию слияния.

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

Элемент списка, содержащий изображение, текст и значок закладки.
Рисунок 3. Элемент списка с изображением, текстом и значком закладки.

@Composable
private fun ArticleListItem(
    openArticle: () -> Unit,
    addToBookmarks: () -> Unit,
) {

    Row(modifier = Modifier.clickable { openArticle() }) {
        // Merges with parent clickable:
        Icon(
            painter = painterResource(R.drawable.ic_logo),
            contentDescription = "Article thumbnail"
        )
        ArticleDetails()

        // Defies the merge due to its own clickable:
        BookmarkButton(onClick = addToBookmarks)
    }
}

Когда пользователь нажимает на clickable элемент Row , открывается статья. Внутри неё находится BookmarkButton для добавления статьи в закладки. Эта вложенная кнопка отображается как необъединённая, в то время как остальное содержимое дочерних элементов внутри строки объединёно:

Объединенное дерево содержит несколько текстов в списке внутри узла Row. Необъединенное дерево содержит отдельные узлы для каждого составного текста.
Рисунок 4. Объединенное дерево содержит несколько текстов в списке внутри узла Row . Необъединенное дерево содержит отдельные узлы для каждого составного Text .

Некоторые компоненты, являющиеся составными элементами, по замыслу разработчиков, не объединяются автоматически под родительским элементом. Родительский элемент не может объединять свои дочерние элементы, если дочерние элементы также объединяются, либо путем явной установки параметра mergeDescendants = true , либо будучи компонентами, которые объединяются сами, например, кнопки или кликабельные элементы. Знание того, как определенные API объединяют или не допускают объединение, может помочь в отладке потенциально неожиданного поведения.

Используйте слияние, когда дочерние элементы образуют логичную и осмысленную группу под своим родителем. Но если вложенные дочерние элементы требуют ручной корректировки или удаления собственной семантики, другие API могут лучше соответствовать вашим потребностям (например, clearAndSetSemantics ).

Очистка и установка семантики

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

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

Обратите внимание, что при очистке с помощью пустой лямбда-функции очищенная семантика не отправляется ни одному потребителю, использующему эту информацию, например, функциям доступности, автозаполнения или тестирования. При перезаписи контента с помощью clearAndSetSemantics{/*semantic information*/} новая семантика заменяет всю предыдущую семантику элемента и его потомков.

Ниже приведён пример пользовательского компонента-переключателя, представленного интерактивной строкой со значком и текстом:

// Developer might intend this to be a toggleable.
// Using `clearAndSetSemantics`, on the Row, a clickable modifier is applied,
// a custom description is set, and a Role is applied.

@Composable
fun FavoriteToggle() {
    val checked = remember { mutableStateOf(true) }
    Row(
        modifier = Modifier
            .toggleable(
                value = checked.value,
                onValueChange = { checked.value = it }
            )
            .clearAndSetSemantics {
                stateDescription = if (checked.value) "Favorited" else "Not favorited"
                toggleableState = ToggleableState(checked.value)
                role = Role.Switch
            },
    ) {
        Icon(
            imageVector = Icons.Default.Favorite,
            contentDescription = null // not needed here

        )
        Text("Favorite?")
    }
}

Хотя иконка и текст содержат некоторую семантическую информацию, вместе они не указывают на то, что этот компонент является переключаемым. Объединения недостаточно, поскольку необходимо предоставить дополнительную информацию о компоненте.

Поскольку этот фрагмент кода создает пользовательский компонент-переключатель, вам необходимо добавить возможность переключения, а также stateDescription , toggleableState и семантику role . Таким образом, состояние компонента и связанное с ним действие будут доступны — например, TalkBack будет объявлять «Двойное касание для переключения» вместо «Двойное касание для активации».

Благодаря очистке исходной семантики и установке новой, более описательной, службы доступности теперь могут видеть, что это переключаемый компонент, который может менять свое состояние.

При использовании clearAndSetSemantics следует учитывать следующее:

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

Скрыть семантику

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

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

@Composable
fun WatermarkExample(
    watermarkText: String,
    content: @Composable () -> Unit,
) {
    Box {
        WatermarkedContent()
        // Mark the watermark as hidden to accessibility services.
        WatermarkText(
            text = watermarkText,
            color = Color.Gray.copy(alpha = 0.5f),
            modifier = Modifier
                .align(Alignment.BottomEnd)
                .semantics { hideFromAccessibility() }
        )
    }
}

@Composable
fun DecorativeExample() {
    Text(
        modifier =
        Modifier.semantics {
            hideFromAccessibility()
        },
        text = "A dot character that is used to decoratively separate information, like •"
    )
}

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

Анализ вариантов использования

Ниже приведено краткое описание вариантов использования, позволяющее четко различать ранее представленные API:

  • Когда контент не предназначен для использования службами обеспечения доступности:
    • Используйте hideFromAccessibility , когда контент может быть декоративным или избыточным, но при этом его необходимо протестировать.
    • Используйте clearAndSetSemantics{} с пустой лямбда-функцией, когда необходимо очистить семантику родительского и дочерних элементов для всех сервисов.
    • Используйте clearAndSetSemantics{/*content*/} с содержимым внутри лямбда-функции, когда семантику компонента необходимо установить вручную.
  • Когда контент следует рассматривать как единое целое, и необходимо, чтобы вся информация о его дочерних элементах была полной:
    • Используйте семантические потомки слияния.
Таблица с различными вариантами использования API.
Рисунок 5. Таблица с различными вариантами использования API.
{% verbatim %} {% endverbatim %} {% verbatim %} {% endverbatim %} ,

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

Семантика слияния

При применении clickable модификатора к родительскому составному элементу Compose автоматически объединяет все дочерние элементы под ним. Чтобы понять, как интерактивные компоненты Compose Material и Foundation используют стратегии объединения по умолчанию, см. раздел «Интерактивные элементы» .

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

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

Группа элементов пользовательского интерфейса, включающая имя пользователя. Имя выделено.
Рисунок 1. Группа элементов пользовательского интерфейса, включающая имя пользователя. Имя выделено.

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

@Composable
private fun PostMetadata(metadata: Metadata) {
    // Merge elements below for accessibility purposes
    Row(modifier = Modifier.semantics(mergeDescendants = true) {}) {
        Image(
            imageVector = Icons.Filled.AccountCircle,
            contentDescription = null // decorative
        )
        Column {
            Text(metadata.author.name)
            Text("${metadata.date}${metadata.readTimeMinutes} min read")
        }
    }
}

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

Группа элементов пользовательского интерфейса, включающая имя пользователя. Все элементы выделены одновременно.
Рисунок 2. Группа элементов пользовательского интерфейса, включающая имя пользователя. Все элементы выделены одновременно.

Для каждого семантического свойства определена стратегия слияния. Например, свойство ContentDescription добавляет все дочерние значения ContentDescription в список. Стратегию слияния семантического свойства можно проверить, изучив его реализацию mergePolicy в файле SemanticsProperties.kt . Свойства могут принимать значение родительского или дочернего элемента, объединять значения в список или строку, запрещать слияние и вместо этого генерировать исключение, или использовать любую другую пользовательскую стратегию слияния.

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

Элемент списка, содержащий изображение, текст и значок закладки.
Рисунок 3. Элемент списка с изображением, текстом и значком закладки.

@Composable
private fun ArticleListItem(
    openArticle: () -> Unit,
    addToBookmarks: () -> Unit,
) {

    Row(modifier = Modifier.clickable { openArticle() }) {
        // Merges with parent clickable:
        Icon(
            painter = painterResource(R.drawable.ic_logo),
            contentDescription = "Article thumbnail"
        )
        ArticleDetails()

        // Defies the merge due to its own clickable:
        BookmarkButton(onClick = addToBookmarks)
    }
}

Когда пользователь нажимает на clickable элемент Row , открывается статья. Внутри неё находится BookmarkButton для добавления статьи в закладки. Эта вложенная кнопка отображается как необъединённая, в то время как остальное содержимое дочерних элементов внутри строки объединёно:

Объединенное дерево содержит несколько текстов в списке внутри узла Row. Необъединенное дерево содержит отдельные узлы для каждого составного текста.
Рисунок 4. Объединенное дерево содержит несколько текстов в списке внутри узла Row . Необъединенное дерево содержит отдельные узлы для каждого составного Text .

Некоторые компоненты, являющиеся составными элементами, по замыслу разработчиков, не объединяются автоматически под родительским элементом. Родительский элемент не может объединять свои дочерние элементы, если дочерние элементы также объединяются, либо путем явной установки параметра mergeDescendants = true , либо будучи компонентами, которые объединяются сами, например, кнопки или кликабельные элементы. Знание того, как определенные API объединяют или не допускают объединение, может помочь в отладке потенциально неожиданного поведения.

Используйте слияние, когда дочерние элементы образуют логичную и осмысленную группу под своим родителем. Но если вложенные дочерние элементы требуют ручной корректировки или удаления собственной семантики, другие API могут лучше соответствовать вашим потребностям (например, clearAndSetSemantics ).

Очистка и установка семантики

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

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

Обратите внимание, что при очистке с помощью пустой лямбда-функции очищенная семантика не отправляется ни одному потребителю, использующему эту информацию, например, функциям доступности, автозаполнения или тестирования. При перезаписи контента с помощью clearAndSetSemantics{/*semantic information*/} новая семантика заменяет всю предыдущую семантику элемента и его потомков.

Ниже приведён пример пользовательского компонента-переключателя, представленного интерактивной строкой со значком и текстом:

// Developer might intend this to be a toggleable.
// Using `clearAndSetSemantics`, on the Row, a clickable modifier is applied,
// a custom description is set, and a Role is applied.

@Composable
fun FavoriteToggle() {
    val checked = remember { mutableStateOf(true) }
    Row(
        modifier = Modifier
            .toggleable(
                value = checked.value,
                onValueChange = { checked.value = it }
            )
            .clearAndSetSemantics {
                stateDescription = if (checked.value) "Favorited" else "Not favorited"
                toggleableState = ToggleableState(checked.value)
                role = Role.Switch
            },
    ) {
        Icon(
            imageVector = Icons.Default.Favorite,
            contentDescription = null // not needed here

        )
        Text("Favorite?")
    }
}

Хотя иконка и текст содержат некоторую семантическую информацию, вместе они не указывают на то, что этот компонент является переключаемым. Объединения недостаточно, поскольку необходимо предоставить дополнительную информацию о компоненте.

Поскольку этот фрагмент кода создает пользовательский компонент-переключатель, вам необходимо добавить возможность переключения, а также stateDescription , toggleableState и семантику role . Таким образом, состояние компонента и связанное с ним действие будут доступны — например, TalkBack будет объявлять «Двойное касание для переключения» вместо «Двойное касание для активации».

Благодаря очистке исходной семантики и установке новой, более описательной, службы доступности теперь могут видеть, что это переключаемый компонент, который может менять свое состояние.

При использовании clearAndSetSemantics следует учитывать следующее:

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

Скрыть семантику

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

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

@Composable
fun WatermarkExample(
    watermarkText: String,
    content: @Composable () -> Unit,
) {
    Box {
        WatermarkedContent()
        // Mark the watermark as hidden to accessibility services.
        WatermarkText(
            text = watermarkText,
            color = Color.Gray.copy(alpha = 0.5f),
            modifier = Modifier
                .align(Alignment.BottomEnd)
                .semantics { hideFromAccessibility() }
        )
    }
}

@Composable
fun DecorativeExample() {
    Text(
        modifier =
        Modifier.semantics {
            hideFromAccessibility()
        },
        text = "A dot character that is used to decoratively separate information, like •"
    )
}

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

Анализ вариантов использования

Ниже приведено краткое описание вариантов использования, позволяющее четко различать ранее представленные API:

  • Когда контент не предназначен для использования службами обеспечения доступности:
    • Используйте hideFromAccessibility , когда контент может быть декоративным или избыточным, но при этом его необходимо протестировать.
    • Используйте clearAndSetSemantics{} с пустой лямбда-функцией, когда необходимо очистить семантику родительского и дочерних элементов для всех сервисов.
    • Используйте clearAndSetSemantics{/*content*/} с содержимым внутри лямбда-функции, когда семантику компонента необходимо установить вручную.
  • Когда контент следует рассматривать как единое целое, и необходимо, чтобы вся информация о его дочерних элементах была полной:
    • Используйте семантические потомки слияния.
Таблица с различными вариантами использования API.
Рисунок 5. Таблица с различными вариантами использования API.
{% verbatim %} {% endverbatim %} {% verbatim %} {% endverbatim %}