В этом уроке рассматриваем элементы 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", то это не вызов лямбды, а просто ее передача элементу. А он уже сам внутри себя ее вызовет и передаст туда значение.
RSS лента комментариев этой записи