В этом уроке рассматриваем элементы CheckBox и TextField. Им необходим State для своей работы.
В прошлых уроках мы рассмотрели простые Composable элементы - Text, Image, Icon. С ними все просто. Передаешь им данные, они их отображают. Но есть элементы, которые устроены сложнее. Рассмотрим два самых распространенных из них: CheckBox и TextField (в прошлом - EditText).
CheckBox
Добавим чекбокс на экран HomeScreen:
import androidx.compose.runtime.Composable
import androidx.compose.material.Checkbox
@Composable
fun HomeScreen() {
Checkbox(checked = true, onCheckedChange = {})
}
Мы сообщаем чекбоксу, что он должен быть включен, а нажатия пока игнорируем.
Результат:
Видим включенный чекбокс без текста.
Текста нет не потому, что мы его не передали. А потому что чекбокс в Compose в принципе не содержит такого атрибута. Если мы хотим рядом отобразить текст, то необходимо использовать уже знакомую нам функцию Text.
Добавим на экран Row, чтобы по горизонтали расположить чекбокс и текст:
import androidx.compose.runtime.Composable
import androidx.compose.material.Checkbox
import androidx.compose.foundation.layout.Row
import androidx.compose.material.Text
import androidx.compose.ui.Alignment.Companion.CenterVertically
import androidx.compose.ui.unit.sp
@Composable
fun HomeScreen() {
Row(verticalAlignment = CenterVertically) {
Checkbox(checked = true, onCheckedChange = {})
Text("Some checkbox text", fontSize = 18.sp)
}
}
Результат:
Так гораздо лучше
Осталось решить еще одну проблему: сделать так, чтобы чекбокс работал. Если сейчас нажимать на этот чекбокс, то он не будет менять свое состояние:
Поначалу это кажется странным поведением. Ведь мы привыкли, что чекбокс всегда сам хранил свое состояние и менял его по нажатию. Это работало с View, но в Compose механизм поменялся.
Давайте посмотрим на Checkbox с точки зрения Composable функции. Когда мы запустили приложение, Activity вызвало функцию HomeScreen, а та в свою очередь запустила все функции внутри себя, в том числе и CheckBox. Мы в коде явно указали, что checked = true. Поэтому чекбокс отображается включенным. Но он сам не управляет этим состоянием. Что мы ему передали, то он и отображает.
И теперь мы нажимаем на чекбокс. Что при этом происходит? Ничего. Ни одна Composable функция не перезапускается, потому что нет повода для этого. Следовательно, и на экране ничего не меняется.
Т.к. чекбокс сам не хранит свое состояние, нам надо сделать это за него. Схема очень похожа на ту, что мы использовали в счетчике кликов. Мы снаружи создаем State<Boolean>, который будет хранить значение чекбокса. А чекбокс будет слать нам события клика и мы сами будем менять State.
Добавляем в HomeScreen параметры для передачи State и лямбды-колбэка.
@Composable
fun HomeScreen(
checked: State<Boolean>,
onCheckedChange: (Boolean) -> Unit
) {
val checkedValue = checked.value
Row(verticalAlignment = CenterVertically) {
Checkbox(checked = checkedValue, onCheckedChange = onCheckedChange)
Text("Some checkbox text", fontSize = 18.sp)
}
}
HomeScreen читает значение из State. Значит, смена значения State приведет к тому, что HomeScreen перезапустится, возьмет из State новое значение и перезапустит функцию CheckBox с этим значением. В результате мы на экране увидим изменения.
А чтобы мы знали, когда менять State, мы чекбоксу даем лямбду, с помощью которой он нам будет сообщать о кликах. При этом чекбокс будет передавать нам Boolean значение, которое надо поместить в State.
В Activity создаем State для чекбокса и передаем его в HomeScreen:
class MainActivity : ComponentActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
val checked = mutableStateOf(true)
setContent {
HomeScreen(
checked = checked,
onCheckedChange = { newCheckedValue ->
checked.value = newCheckedValue
}
)
}
}
}
В onCheckedChange лямбде меняем значение State.
Результат:
Все работает.
Давайте еще раз проговорим механизм. Нажатие на чекбокс приводит к вызову onCheckedChange, в котором мы меняем значение State. Это приводит к перезапуску функции HomeScreen, которая перезапускает функцию CheckBox (с новым значением checked), которая выводит на экран чекбокс с актуальным состоянием.
Обратите внимание, что если мы сейчас попробуем кликать на текст около чекбокса, то ничего не будет происходить. Потому что текст никак не обрабатывает нажатия.
В качестве самостоятельной работы попробуйте исправить это. Надо повесить на текст обработчик нажатий и вызывать ту же лямбду, что вызывается по нажатию на чекбокс. В эту лямбду надо будет передавать Boolean значение в зависимости от текущего checked значения.
TextField
TextField - Composable функция, которая выводит на экран элемент для ввода текста. Нам он привычен под названием EditText.
Я в примерах буду использовать не сам TextField, а его более симпатичную версию - OutlinedTextField. Принцип работы у них одинаков, поэтому все, что будем сейчас обсуждать, актуально для обоих.
Добавим OutlinedTextField в HomeScreen:
import androidx.compose.material.OutlinedTextField
@Composable
fun HomeScreen() {
OutlinedTextField(value = "some text", onValueChange = {})
}
Передаем начальный текст и игнорируем пока изменения текста
Запускаем и пробуем изменить текст
Текст не меняется. Причина та же, что и с чекбоксом. TextField ждет от нас значение, которое он будет отображать. А мы все время передаем туда фиксированный текст "some text".
Чтобы это исправить, необходимо использовать State, в котором мы будем хранить значение TextField.
Используем State<String>:
@Composable
fun HomeScreen(
text: State<String>,
onValueChange: (String) -> Unit
) {
val textValue = text.value
OutlinedTextField(value = textValue, onValueChange = onValueChange)
}
HomeScreen читает значение из State. Значит, смена значения State приведет к тому, что HomeScreen перезапустится, возьмет из State новое значение и перезапустит функцию OutlinedTextField с этим значением. В результате мы на экране увидим новый текст.
А чтобы мы могли менять State по мере набора текста на клавиатуре, мы функции OutlinedTextField даем лямбду, с помощью которой она нам будет сообщать о том, какой текст надо отображать.
В Activity создаем State для хранения текста и передаем его в HomeScreen:
class MainActivity : ComponentActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
val text = mutableStateOf("some text")
setContent {
HomeScreen(
text = text,
onValueChange = { newText ->
text.value = newText
}
)
}
}
}
В onValueChange меняем значение State
Результат:
Вы наверно заметили, что в первом примере с OutlinedTextField, когда мы еще не добавили ему State, курсор все равно двигался по тексту, хоть и не обновлял его. Ну и то, что OutlinedTextField передает в onValueChange текст, который в итоге должен быть отображен, наводит на мысль, что OutlinedTextField внутри себя явно хранит какой-то свой state. На самом деле, так и есть. Вот хорошая статья на эту тему, которая поможет подробнее понять, как работает TextField под капотом.
Но в любом случае и CheckBox и TextField ожидают от нас данных, которые они будут отображать.
Присоединяйтесь к нам в Telegram:
- в канале StartAndroid публикуются ссылки на новые статьи с сайта startandroid.ru и интересные материалы с хабра, medium.com и т.п.
- в чатах решаем возникающие вопросы и проблемы по различным темам: Android, Compose, Kotlin, RxJava, Dagger, Тестирование, Performance
- ну и если просто хочется поговорить с коллегами по разработке, то есть чат Флудильня
Комментарии
Если вы об этом "onCheckedChange = onCheckedChange", то это не вызов лямбды, а просто ее передача элементу. А он уже сам внутри себя ее вызовет и передаст туда значение.
В этом уроке я рассмотрел пару UI элементов только чтобы показать, как они работают со State.
RSS лента комментариев этой записи