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 есть изображение (см. ниже), которое показывает все три состояния.

Для чего нужен этот компонент? В основном он предназначен для использования в качестве родительского флажка, который отображает состояние всех дочерних флажков:
- Когда все дочерние флажки сняты → родительский
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}")
}
}
}
}
Вот как это выглядит в действии:

Забавный факт: composable-функция Checkbox является оберткой для composable TriStateCheckbox, при этом игнорируется третье, неопределенное состояние.
2. SegmentedButton
SegmentedButton — это компонент, который позволяет выбрать от двух до пяти вариантов. Она может содержать значки, текст или и то, и другое. Существует два варианта SegmentedButton: с одиночным или множественным выбором. Они реализуются как разные компоненты.
SegmentedButton с одиночным выбором
SegmentedButton с одиночным выбором реализуется в виде composable SingleChoiceSegmentedButtonRow, к которой можно добавить несколько 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 с множественным выбором реализуется в виде composable MultiChoiceSegmentedButtonRow, к которой можно добавить несколько SegmentedButton.
Реализация аналогична SingleChoiceSegmentedButtonRow, с некоторыми незначительными отличиями в API, позволяющими одновременно отмечать несколько кнопок.

Вот пример реализации двух 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
)
Вот как это выглядит в действии.

3. 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()
)
}
}
Можно перетаскивать каждый бегунок, чтобы изменить выбранный диапазон. Два бегунка не могут пересекаться друг с другом.

4. Badge
Бейдж (Badge) представляет собой уведомление и предназначен для привлечения внимания к элементу, информируя пользователя о наличии ожидающих запросов или действий.
Он также может отображать определенное количество ожидающих запросов или короткий текст.
Обычно используется в нижней панели навигации на одном из навигационных элементов.
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 = {}
)
}
}
Приведенный пример показывает бейдж над значком, кастомизированный бейдж над текстом и бейджи внутри панели навигации.

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






