В этом уроке рассматриваем элементы 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 

- ну и если просто хочется поговорить с коллегами по разработке, то есть чат Флудильня




Комментарии   

# Чек бокаМаксим 15.01.2023 01:02
у чек бокса и анимация изменилась, точнее ее просто не стало, сразу видно, что animated drawable под капотом больше нет и если хочется старое поведение придется что-то ваять самостоятельно(
# А еще можно повесить обработчик не на текст, а сразу на контейнер, куда входит чекбокс и сам текстkilg 01.02.2023 20:11
А еще можно повесить обработчик не на текст, а на контейнер, и тогда клик будет обрабатываться и по чекбоксу, и по тексту.
# TextСергей 28.03.2023 15:35
Так даже лучше, только ещё надо элементу Checkbox передать параметр onCheckedChange =null
# Вызов лямбдыЕвгений 03.02.2023 15:26
Не ясно почему мы вызываем лямбду принимающую аргумент без аргумента (в случае checkbox - boolean, в случае textfield - string), что приходит в значение state после перезапуска composable? Или эти данные зашиты во внутреннем state, а мы просто тригерим перезапуск?
# RE: Вызов лямбдыDmitry Vinogradov 03.02.2023 19:02
Подскажите, в каком примере мы вызываем лямбду без аргумента?
Если вы об этом "onCheckedChange = onCheckedChange", то это не вызов лямбды, а просто ее передача элементу. А он уже сам внутри себя ее вызовет и передаст туда значение.
# Вызов лямбдыЕвгений 03.02.2023 22:32
Понял, то есть параметр функции Checkbox типа (boolean)->unit. Ну да, при вызове бы () или invoke() было бы. Дмитрий, спасибо за ответ и вообще за уроки
# Обработка ввода в TextFieldИлья 07.10.2023 11:45
У EditText сложный механизм обработки ввода текста: есть колбеки OnTextChanged, есть фильтры. Как в TextField с этим дело обстоит? Как реализовать масочный ввод, например?
# RE: Обработка ввода в TextFieldDmitry Vinogradov 13.10.2023 13:33
Вопрос слишком специфический. Я беру общие темы и не планирую разбирать нюансы отдельных UI элементов, иначе уроков получится слишком много.

В этом уроке я рассмотрел пару UI элементов только чтобы показать, как они работают со State.
# "Вот хорошая статья"Over 11.02.2024 15:35
Ссылка "Вот хорошая статья" не открывается в России - заблокирована.

Language

Автор сайта

Дмитрий Виноградов

Подробнее можно посмотреть или почитать.

Никакие другие люди не имеют к этому сайту никакого отношения и просто занимаются плагиатом.

Социальные сети

 

В канале я публикую ссылки на интересные и полезные статьи по Android

В чате можно обсудить вопросы и проблемы, возникающие при разработке



Группа ВКонтакте



Поддержка проекта

Яндекс
410011180491924

WebMoney
R248743991365
Z551306702056

Paypal