Компонент RadioButton представляет переключатель или радиокнопку и служит для создания группы радиокнопок, из которых одномоментно можно выбрать только один переключатель. Этот компонент имеет следующие параметры:
@Composable fun RadioButton( selected: Boolean, onClick: (() -> Unit)?, modifier: Modifier = Modifier, enabled: Boolean = true, colors: RadioButtonColors = RadioButtonDefaults.colors(), interactionSource: MutableInteractionSource? = null ): Unit
selected
: указывает, будет ли отмечена радиокнопка. Представляет значение Boolean
. Если равен true
,
то радиокнопка отмечена.
onClick
: представляет функцию типа () -> Unit
, которая выполняется при нажатия на радиокнопку.
modifier
: объект Modifier, который устанавливает для радиокнопки модификаторы
enabled
: указывает, будет ли доступна радиокнопка. Представляет значение Boolean
и по умолчанию равен true
(то есть радиокнопка будет доступна).
interactionSource
: объект MutableInteractionSource, который задает поток взаимодействий для радиокнопки.
По умолчанию равен remember { MutableInteractionSource() }
.
colors
: объект RadioButtonColors, который задает цвета для радиокнопки.
По умолчанию равен RadioButtonDefaults.colors()
.
Создадим группу из двух радиокнопок:
package com.example.helloapp import android.os.Bundle import androidx.activity.ComponentActivity import androidx.activity.compose.setContent import androidx.compose.foundation.layout.Column import androidx.compose.foundation.selection.selectableGroup import androidx.compose.material3.RadioButton import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember import androidx.compose.ui.Modifier class MainActivity : ComponentActivity() { override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContent { val state = remember { mutableStateOf(true) } Column(Modifier.selectableGroup()) { RadioButton( selected = state.value, onClick = { state.value = true } ) RadioButton( selected = !state.value, onClick = { state.value = false } ) } } } }
Для создании группы радиокнопок, которые рассматриваются именно как группа или единое целое, у контейнера - компонента Column или Row устанавливается модификатор Modifier.selectableGroup(). В данном случае радиокнопки помещаются в Column и соответственно будут располагаться в столбик:
Column(Modifier.selectableGroup())
Хотя также можно было бы расположить радиокнопки в строку, поместив в контейнер Row.
Для хранения состояния радиокнопок определяется переменная state
, которая представляет тип MutableState<Boolean>
:
val state = remember { mutableStateOf(true) }
С помощью свойства value
получаем хранимое в переменной значение (true
или false
) и передаем его параметру selected
радиокнопок:
selected = state.value
Но поскольку только одна радиокнопка одномоментно может быть выбрана, то другой радиокнопке передается противоположеное значение:
selected = !state.value
А в обработчике нажатия из параметра onClick
изменяем данное значение:
onClick = { state.value = true }
В примере выше мы видим, что для радиокнопок, как и для флажков, неопределяется никакой текстовой метки, которая несла бы самую минимальную информацию о радиокнопке. В этом случае необходимо самостоятельно комбинировать радиокнопку с текстовыми компонентами:
package com.example.helloapp import android.os.Bundle import androidx.activity.ComponentActivity import androidx.activity.compose.setContent import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.padding import androidx.compose.foundation.selection.selectableGroup import androidx.compose.material3.RadioButton import androidx.compose.material3.Text import androidx.compose.ui.tooling.preview.Preview import androidx.compose.runtime.Composable import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember import androidx.compose.ui.Modifier import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.sp class MainActivity : ComponentActivity() { override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContent { val state = remember { mutableStateOf(true) } Column { Text(text = if(state.value) {"Kotlin"} else {"Java"}, fontSize = 28.sp, modifier = Modifier.padding(10.dp)) Column(Modifier.selectableGroup()) { Row{ RadioButton( selected = state.value, onClick = { state.value = true } ) Text("Kotlin", fontSize = 28.sp, modifier = Modifier.padding(4.dp)) } Row{ RadioButton( selected = !state.value, onClick = { state.value = false } ) Text("Java", fontSize = 28.sp, modifier = Modifier.padding(4.dp)) } } } } } }
В данном случае, если state хранит true, то выбирается радиокнопка с языком Kotlin, если state хранит false, то выбирается радиокнока с языком Java.
Выше приведенный пример довольно прост в том плане, что у нас только две радиокнопки - когда у одной кнопки параметр selected
равен true
,
у другой равен false
. В этом плане довольно просто задать логику переключения между радиокнопками. Однако что если у нас 3 и более переключателей? Рассмотрим следующий пример:
package com.example.helloapp import android.os.Bundle import androidx.activity.ComponentActivity import androidx.activity.compose.setContent import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.padding import androidx.compose.foundation.selection.selectableGroup import androidx.compose.material3.RadioButton import androidx.compose.material3.Text import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.sp class MainActivity : ComponentActivity() { override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContent { val languages = listOf("Kotlin", "Java", "Javascript", "Rust") val (selectedOption, onOptionSelected) = remember { mutableStateOf(languages[0]) } Column { Text(text = selectedOption, fontSize = 28.sp, modifier = Modifier.padding(10.dp)) Column(Modifier.selectableGroup()) { languages.forEach { text -> Row( Modifier.fillMaxWidth(), verticalAlignment = Alignment.CenterVertically) { RadioButton( selected = (text == selectedOption), onClick = { onOptionSelected(text) } ) Text( text = text, fontSize = 24.sp ) } } } } } } }
В примере выше прежде всего все данные, которые будут представлять радиокнопки, помещаются в список languages
:
val languages = listOf("Kotlin", "Java", "Javascript", "Rust")
Здесь четыре элемента, соответственно мы будем создавать четыре радиокнопки для каждого из этих элементов.
Далее мы получаем объект MutableState<String>, который необходим для и отслеживания выбранного значения:
val (selectedOption, onOptionSelected) = remember { mutableStateOf(languages[0]) }
В функцию mutableStateOf()
передается первый элемент из списка, то есть по умолчанию будет выбран первый элемент списка languages.
Однако мы не просто берем объект MutableState<String>
, а раскладываем его на два компонента - selectedOption
и
onOptionSelected
. Значение selectedOption
будет представлять отслеживаемый объектом MutableState<String>
элемент списка languages. А onOptionSelected
- функция типа (String) -> Unit
, которая будет вызываться при изменении значения в MutableState<String>
и которая в качестве параметра будет получать новое значение.
Выбранный элемент из selectedOption
выводится в верхний компонент Text:
Text(text = selectedOption, fontSize = 28.sp, modifier = Modifier.padding(10.dp))
Как и в примерах выше, чтобы задать группу выбираемых компонентов, для контейнера (в данном случае компонента Column) устанавливается модификатор selectableGroup
:
Column(Modifier.selectableGroup()){ ........... }
Далее перебираем список languages с помощью функции forEach()
, в которую передается функция, вызываемая для каждого перебираемого
элемента:
languages.forEach { text -> Row( Modifier.fillMaxWidth(), verticalAlignment = Alignment.CenterVertically) { RadioButton( selected = (text == selectedOption), onClick = { onOptionSelected(text) } ) Text( text = text, fontSize = 24.sp ) } }
Фактически в данном случае за каждой строкой закрепляется определенный элемент из списка languages. И радиокнопка является выбранный,
если значение selectedOption
совпадает со значением элемента из списка languages, закрепленным за данным компонентом Row:
selected = (text == selectedOption)
При нажатии на компонент срабатывает функция из параметра onClick
, в которой вызывается функция onOptionSelected
:
onClick = { onOptionSelected(text) }
В функции onOptionSelected
передается закрепленный за компонентом Row элемент из списка languages, благодаря чему изменится выбранный элемент.
Пример выше прекрасно работает, однако имеет один недостаток: чтобы выбрать радиокноку, необходимо пальцем попасть в этот небольшой кружок, который представляет радиокнопку. Было бы гораздо лучше, если бы мы могли нажать на любой место в строке, например, на текстовую метку, и тем самым выбрать радиокноку. Для этого изменим код следующим образом:
package com.example.helloapp import android.os.Bundle import androidx.activity.ComponentActivity import androidx.activity.compose.setContent import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.padding import androidx.compose.foundation.selection.selectable import androidx.compose.foundation.selection.selectableGroup import androidx.compose.material3.RadioButton import androidx.compose.material3.Text import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.sp class MainActivity : ComponentActivity() { override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContent { val languages = listOf("Kotlin", "Java", "Javascript", "Rust") val (selectedOption, onOptionSelected) = remember { mutableStateOf(languages[0]) } Column { Text(text = selectedOption, fontSize = 28.sp, modifier = Modifier.padding(10.dp)) Column(Modifier.selectableGroup()) { languages.forEach { text -> Row( Modifier.fillMaxWidth() .selectable( selected = (text == selectedOption), onClick = { onOptionSelected(text) }), verticalAlignment = Alignment.CenterVertically) { RadioButton( selected = (text == selectedOption), onClick = { } ) Text( text = text, fontSize = 24.sp ) } } } } } } }
Ключевым моментом здесь является установка модификатора Modifier.selectable
:
Row( Modifier.selectable( selected = (text == selectedOption), onClick = { onOptionSelected(text) } ),
Модификатор Modifier.selectable()
делает компонент (в данном случае компонент Row) выделяемым.
То есть мы можем выбрать не просто радиокнопку, а всю строку. В примере выше компонент Row является выбранным, если значение selectedOption
совпадает со значением элемента из списка languages,
закрепленным за данным компонентом Row:
selected = (text == selectedOption)
При нажатии на компонент срабатывает функция из параметра onClick
, в которой вызывается функция onOptionSelected
:
onClick = { onOptionSelected(text) }
В функции onOptionSelected
передается закрепленный за компонентом Row элемент из списка languages, благодаря чему изменится выбранный элемент.
Кроме того, также надо настроить радиокнопки, которые выводятся в строке Row:
RadioButton( selected = (text == selectedOption), onClick = {} )
Для выбора радиокнопки действует тот же алгоритм, что и для контейнера Row: радиокнопка выбрана, если текущий элемент из languages совпадает со
значением selectedOption
.
И поскольку выбор элемента обрабатывается в родительском контейнере Row, то нет смысла обрабатывать выбор элемента в радиокнопке, поэтому ее параметру onClick
передается
значение {}
(по сути пустая функция).
Таким образом, внешне мы получим тот же визуальный интерфейс, только теперь для выделения радиокнопки достаточно нажать на любое место в строке.
За настройку цвета радиокнопок отвечает параметр colors компонента RadioButton
. Для установки этого параметра можно использовать встроенную функцию RadioButtonDefaults.colors()
,
которая имеет следующее определение:
@Composable public final fun colors( selectedColor: Color = RadioButtonTokens.SelectedIconColor.toColor(), unselectedColor: Color = RadioButtonTokens.UnselectedIconColor.toColor(), disabledSelectedColor: Color = RadioButtonTokens.DisabledSelectedIconColor .toColor() .copy(alpha = RadioButtonTokens.DisabledSelectedIconOpacity), disabledUnselectedColor: Color = RadioButtonTokens.DisabledUnselectedIconColor .toColor() .copy(alpha = RadioButtonTokens.DisabledUnselectedIconOpacity) ): RadioButtonColors
Таким образом, для отдельной радиокнопки мы можем установить 4 цвета:
selectedColor
: цвет, когда кнопка выбрана
unselectedColor
: цвет, когда кнопка не выбрана
disabledSelectedColor
: цвет, когда кнопка выбрана, но не доступна
disabledUnselectedColor
: цвет, когда кнопка не выбрана и не доступна
Например:
RadioButton( selected = (text == selectedOption), onClick = { }, colors = RadioButtonDefaults.colors(selectedColor = Color.DarkGray, unselectedColor = Color.LightGray) )