Jetpack Compose Material 3 предлагает богатый набор UI-компонентов. Но помимо Button, TextField и Card существует множество менее известных компонентов, которые помогут сэкономить время и обеспечить лучший пользовательский опыт. В этой статье рассмотрим некоторые из них и узнаем, когда и как их использовать. Примечание: примеры в этой статье должны работать во всех версиях Compose Material 3: продакшен-версии (1.3.2), бета (1.4.0-beta02) и альфа (1.5.0-alpha04), так как API не менялся. Реклама 1. TriStateCheckbox Обычный флажок (чекбокс) поддерживает два состояния: включено и выключено. TriStateCheckbox добавляет третье состояние: неопределенное. Оно используется в ситуации, когда что-то не полностью отмечено и не полностью снято. В документации по TriStateCheckbox есть изображение (см. ниже), которое показывает все три состояния. Источник: https://developer.android.com/images/reference/androidx/compose/material3/indeterminate-checkbox.png Для чего нужен этот компонент? В основном он предназначен для использования в качестве родительского флажка, который отображает состояние всех дочерних флажков: Когда все дочерние флажки сняты → родительский TriStateCheckbox находится в состоянии «выключено». Когда дочерние флажки отмечены не все (часть снята) → родительский TriStateCheckbox переходит в неопределенное состояние (ни отмечено, ни снято). Когда все дочерние флажки отмечены → родительский TriStateCheckbox находится в состоянии «включено». Пример трех состояний TriStateCheckbox Вот как мы можем реализовать описанный выше выбор. Еще одна важная особенность заключается в том, что щелчок по TriStateCheckbox изменяет состояние всех дочерних флажков. @Composable private fun TriStateCheckboxSample() {     var childStates by remember { mutableStateOf(List(5) { false }) }     // Определение родительского состояния     val parentState = when {         childStates.all { it } -> ToggleableState.On         childStates.none { it } -> ToggleableState.Off         else -> ToggleableState.Indeterminate     }     Column(Modifier.padding(16.dp)) {         // Родительский чекбокс         Row(verticalAlignment = Alignment.CenterVertically) {             TriStateCheckbox(                 state = parentState,                 onClick = {                     val newState = parentState != ToggleableState.On                     childStates = List(childStates.size) { newState }                 }             )             Text("Options")         }         Spacer(Modifier.height(8.dp))         // Дочерние чекбоксы         childStates.forEachIndexed { index, checked ->             Row(verticalAlignment = Alignment.CenterVertically, modifier = Modifier.padding(horizontal = 16.dp)) {                 Checkbox(                     checked = checked,                     onCheckedChange = { newValue ->                         childStates = childStates.toMutableList().apply {                             this[index] = newValue                         }                     }                 )                 Text("Option ${index + 1}")             }         }     } } Вот как это выглядит в действии: Видео, демонстрирующее работу TriStateCheckbox Забавный факт: composable-функция Checkbox является оберткой для composable TriStateCheckbox, при этом игнорируется третье, неопределенное состояние. 2. SegmentedButton SegmentedButton — это компонент, который позволяет выбрать от двух до пяти вариантов. Она может содержать значки, текст или и то, и другое. Существует два варианта SegmentedButton: с одиночным или множественным выбором. Они реализуются как разные компоненты. SegmentedButton с одиночным выбором SegmentedButton с одиночным выбором реализуется в виде composable SingleChoiceSegmentedButtonRow, к которой можно добавить несколько SegmentedButton. Может использоваться значок по умолчанию (галочка), также доступен вариант предоставления собственного значка. Пример SegmentedButton с одиночным выбором Пример реализации двух SegmentedButton, показанных выше: var selectedIndex by remember { mutableIntStateOf(0) }
SingleChoiceSegmentedButtonRow(
    modifier = Modifier.fillMaxWidth()
) {
    (0..2).forEach { index ->
        SegmentedButton(
            selected = selectedIndex == index,
            onClick = { selectedIndex = index },
            shape = SegmentedButtonDefaults.itemShape(index, 3),
        ) {
            Text("Option ${index + 1}")
        }
    }
}

Text(
    text = "Selected Option: ${selectedIndex + 1}",
    style = MaterialTheme.typography.bodySmall
)

Spacer(Modifier.height(16.dp))

var selectedIndex1 by remember { mutableIntStateOf(0) }
SingleChoiceSegmentedButtonRow(
    modifier = Modifier.fillMaxWidth()
) {
    (0..4).forEach { index ->
        SegmentedButton(
            selected = selectedIndex1 == index,
            onClick = { selectedIndex1 = index },
            shape = SegmentedButtonDefaults.itemShape(index, 5),
            icon = {
                SegmentedButtonDefaults.Icon(selectedIndex1 == index, activeContent = {
                    Icon(Icons.Default.Favorite, null)
                })
            }
        ) {
            Text("${index + 1}")
        }
    }
}

Text(
    text = "Selected Option: ${selectedIndex1 + 1}",
    style = MaterialTheme.typography.bodySmall
) Вот как это выглядит в действии: Пример использования SegmentedButton с одиночным выбором SegmentedButton с множественным выбором SegmentedButton с множественным выбором реализуется в виде composable MultiChoiceSegmentedButtonRow, к которой можно добавить несколько SegmentedButton. Реализация аналогична SingleChoiceSegmentedButtonRow, с некоторыми незначительными отличиями в API, позволяющими одновременно отмечать несколько кнопок. Пример SegmentedButton с множественным выбором Вот пример реализации двух SegmentedButton, показанных выше. val selectedOptions = remember { mutableStateListOf<Int>() }
MultiChoiceSegmentedButtonRow(
    modifier = Modifier.fillMaxWidth()
) {
    (0..4).forEach { index ->
        SegmentedButton(
            checked = index in selectedOptions,
            onCheckedChange = {
                if (index in selectedOptions) selectedOptions.remove(index) else selectedOptions.add(
                    index
                )
            },
            shape = SegmentedButtonDefaults.itemShape(index, 5),
        ) {
            Text("${index + 1}")
        }
    }
}

Text(
    text = "Selected Options: ${selectedOptions.map { it + 1 }.joinToString()}",
    style = MaterialTheme.typography.bodySmall
) Вот как это выглядит в действии. Пример использования SegmentedButton с множественным выбором 3. RangeSlider RangeSlider (диапазонный слайдер) основан на концепции обычного слайдера, но с ключевым отличием: он позволяет пользователю выбрать два значения. Эти два значения образуют диапазон, где одно значение представляет минимум, а другое — максимум. Пример, где можно использовать RangeSlider — фильтрация по цене, позволяющая пользователю выбрать ценовой диапазон и показывающая результаты, которые ему соответствуют. Пример RangeSlider Приведем краткий пример использования RangeSlider. API похож на API обычного Slider. Нужно передать выбранный диапазон значений, допустимый диапазон, определяющий минимальное и максимальное значение, и количество шагов. В этом примере у нас диапазон от 1 до 100 с 9 шагами (плюс один, который всегда присутствует); это означает, что каждый шаг представляет значение 10. @OptIn(ExperimentalMaterial3Api::class)
@Composable
private fun RangeSliderExample() {
    var selectedValue by remember { mutableStateOf(0f..100f) }

    Column {
        Text(
            text = "Selected range: ${selectedValue.start.toInt()} - ${selectedValue.endInclusive.toInt()}",
            style = MaterialTheme.typography.bodyLarge
        )

        RangeSlider(
            value = selectedValue,
            onValueChange = { newRange -> selectedValue = newRange },
            valueRange = 1f..100f,
            steps = 9,
            modifier = Modifier.fillMaxWidth()
        )
    }
} Можно перетаскивать каждый бегунок, чтобы изменить выбранный диапазон. Два бегунка не могут пересекаться друг с другом. Пример использования RangeSlider 4. Badge Бейдж (Badge) представляет собой уведомление и предназначен для привлечения внимания к элементу, информируя пользователя о наличии ожидающих запросов или действий. Он также может отображать определенное количество ожидающих запросов или короткий текст. Обычно используется в нижней панели навигации на одном из навигационных элементов. Источник: https://developer.android.com/images/reference/androidx/compose/material3/badge.png BadgedBox — это компонент, оборачивающий элемент, к которому мы хотим прикрепить бейдж. Он принимает две composable-функции в качестве входных аргументов: одну для содержимого и одну для бейджа. Затем он закрепляет бейдж в правом верхнем углу содержимого. Badge также можно настраивать, применяя различные фоны и цвета текста. @Composable
private fun BadgeExample() {
    Row(
        horizontalArrangement = Arrangement.spacedBy(16.dp),
        modifier = Modifier
    ) {
        BadgedBox(
            badge = {
                Badge {
                    Text("5")
                }
            }
        ) {
            Icon(
                imageVector = Icons.Default.Email,
                contentDescription = "Messages",
                modifier = Modifier.padding(8.dp)
            )
        }

        BadgedBox(
            badge = {
                Badge(
                    containerColor = Color.Gray,
                    contentColor = Color.Yellow
                ) {
                    Text(500.toString())
                }
            }
        ) {
            Text("Inbox", modifier = Modifier.padding(8.dp))
        }
    }

    // Пример навигационной панели 
    NavigationBar {
        NavigationBarItem(
            icon = {
                Icon(Icons.Filled.Home, contentDescription = "Home")
            },
            selected = true,
            onClick = {}
        )

        NavigationBarItem(
            icon = {
                BadgedBox(
                    badge = {
                        Badge()
                    }
                ) {
                    Icon(Icons.AutoMirrored.Filled.List, contentDescription = "List")
                }
            },
            selected = false,
            onClick = {}
        )

        NavigationBarItem(
            icon = {
                BadgedBox(
                    badge = {
                        Badge()
                        {
                            Text(3.toString())
                        }
                    }
                ) {
                    Icon(Icons.Filled.Person, contentDescription = "Profile")
                }
            },
            selected = false,
            onClick = {}
        )
    }
} Приведенный пример показывает бейдж над значком, кастомизированный бейдж над текстом и бейджи внутри панели навигации. Примеры использования Badge и BadgedBox 5. Tooltip Более подробную информацию о компоненте Tooltip вы можете найти в отдельной статье. Заключение Мы подробно рассмотрели некоторые из менее известных компонентов Compose Material 3. Теперь можете уверенно добавлять эти незаслуженно обойденные вниманием компоненты в свой набор инструментов Compose, чтобы ваши приложения стали более проработанными и интерактивными. Читайте также: Оптимизация кэширования в TrendNow: объединение OkHttp Cache и базы данных Room. Часть 7 Роль Fragments в современной разработке приложений для Android Автоматизация скриншот-тестирования предварительных просмотров Compose с использованием отражения Читайте нас в Telegram, VK и Дзен Перевод статьи Domen Lanišnik: Exploring 5 Lesser-Known Compose Components
==============
В статье рассматриваются менее известные компоненты Compose Material 3, такие как TriStateCheckbox, SegmentedButton, RangeSlider и Badge. TriStateCheckbox позволяет отображать три состояния: включено, выключено и неопределенное, отображая состояние всех дочерних флажков. SegmentedButton позволяет выбирать от двух до пяти вариантов, предоставляя гибкие возможности для пользовательского интерфейса. RangeSlider предоставляет диапазон для выбора, полезный для фильтрации, например, по ценам. Badge используется для отображения уведомлений или ожидающих запросов, с возможностью настройки цвета и содержимого. Приведены примеры использования каждого компонента, включая варианты кастомизации и интеграции в навигационную панель.