Avatar ·

Как программно скрыть клавиатуру в Jetpack Compose при работе с TextField

Всем привет! Я начал изучать Jetpack Compose и столкнулся с такой проблемой: после того как пользователь ввел текст в TextField, клавиатура не закрывается автоматически. Мне нужно, чтобы она скрывалась по нажатию кнопки "Готово" или после отправки формы. В классическом View это делалось через InputMethodManager, но в Compose это не работает напрямую.

Вот мой текущий код:

@Composable
fun LoginScreen() {
    var text by remember { mutableStateOf("") }
    
    Column(
        modifier = Modifier.fillMaxSize().padding(16.dp)
    ) {
        OutlinedTextField(
            value = text,
            onValueChange = { text = it },
            label = { Text("Введите имя") },
            modifier = Modifier.fillMaxWidth()
        )
        
        Button(
            onClick = {
                // Здесь нужно скрыть клавиатуру
                Log.d("TAG", "Отправка: $text")
            }
        ) {
            Text("Отправить")
        }
    }
}

Я пробовал использовать LocalContext.current и получать InputMethodManager, но клавиатура не скрывается. Подскажите, как правильно это сделать в Compose? Нужно ли как-то по-особому получать контекст или есть другой подход?

Заранее спасибо за помощь!

<<>>

Решение: скрытие клавиатуры через LocalSoftwareKeyboardController

В Jetpack Compose есть встроенный способ управления клавиатурой — SoftwareKeyboardController. Его можно получить через LocalSoftwareKeyboardController в CompositionLocal. Этот способ работает нативно и не требует прямого доступа к InputMethodManager.

Вот исправленный код:

import androidx.compose.ui.platform.LocalSoftwareKeyboardController

@Composable
fun LoginScreen() {
    var text by remember { mutableStateOf("") }
    val keyboardController = LocalSoftwareKeyboardController.current
    
    Column(
        modifier = Modifier.fillMaxSize().padding(16.dp)
    ) {
        OutlinedTextField(
            value = text,
            onValueChange = { text = it },
            label = { Text("Введите имя") },
            modifier = Modifier.fillMaxWidth()
        )
        
        Button(
            onClick = {
                keyboardController?.hide() // Скрываем клавиатуру
                Log.d("TAG", "Отправка: $text")
            }
        ) {
            Text("Отправить")
        }
    }
}

Ключевые моменты:

  • LocalSoftwareKeyboardController — это CompositionLocal, который предоставляет контроллер клавиатуры для текущего Compose-контекста.
  • Метод hide() скрывает клавиатуру. Он безопасен для вызова, даже если клавиатура уже скрыта (просто ничего не произойдет).
  • Контроллер может быть null, если клавиатура недоступна (например, на устройстве без экранной клавиатуры). Поэтому используйте ?. для безопасного вызова.

Что делать, если клавиатура всё ещё не скрывается?

Иногда проблема может быть в том, что TextField теряет фокус после скрытия клавиатуры, но клавиатура остаётся. В таких случаях можно дополнительно сбросить фокус с поля ввода:

import androidx.compose.ui.focus.FocusRequester
import androidx.compose.ui.focus.focusRequester

@Composable
fun LoginScreen() {
    var text by remember { mutableStateOf("") }
    val keyboardController = LocalSoftwareKeyboardController.current
    val focusRequester = remember { FocusRequester() }
    
    Column(
        modifier = Modifier.fillMaxSize().padding(16.dp)
    ) {
        OutlinedTextField(
            value = text,
            onValueChange = { text = it },
            label = { Text("Введите имя") },
            modifier = Modifier
                .fillMaxWidth()
                .focusRequester(focusRequester)
        )
        
        Button(
            onClick = {
                keyboardController?.hide()
                focusRequester.freeFocus() // Снимаем фокус
                Log.d("TAG", "Отправка: $text")
            }
        ) {
            Text("Отправить")
        }
    }
}

Метод freeFocus() сбрасывает фокус с текущего элемента, что гарантирует скрытие клавиатуры в сложных сценариях (например, при использовании нескольких TextField).

Альтернативный способ: скрытие по нажатию "Done" на клавиатуре

Если нужно скрывать клавиатуру при нажатии кнопки "Готово" на самой клавиатуре, используйте KeyboardActions:

OutlinedTextField(
    value = text,
    onValueChange = { text = it },
    label = { Text("Введите имя") },
    keyboardActions = KeyboardActions(
        onDone = { keyboardController?.hide() }
    ),
    keyboardOptions = KeyboardOptions(imeAction = ImeAction.Done)
)

Этот подход удобен для полей ввода, где не требуется кнопка отправки.

<<>>

Использование InputMethodManager в Compose

Если по какой-то причине SoftwareKeyboardController не подходит (например, вы работаете с устаревшей версией Compose), можно использовать классический InputMethodManager через LocalContext:

@Composable
fun LoginScreen() {
    var text by remember { mutableStateOf("") }
    val context = LocalContext.current
    
    Button(
        onClick = {
            val inputMethodManager = context.getSystemService(Context.INPUT_METHOD_SERVICE) as InputMethodManager
            val view = (context as? Activity)?.currentFocus ?: View(context)
            inputMethodManager.hideSoftInputFromWindow(view.windowToken, 0)
            Log.d("TAG", "Отправка: $text")
        }
    ) {
        Text("Отправить")
    }
}

Важно: этот способ требует получения текущего View с фокусом. В Compose это может быть проблематично, так как нет прямого доступа к View. Поэтому предпочтительнее использовать SoftwareKeyboardController.

Дополнительный совет

Если вы используете TextField внутри LazyColumn или Dialog, убедитесь, что клавиатура не блокируется родительскими элементами. В таких случаях добавьте Modifier.imePadding() к корневому модификатору, чтобы Compose правильно обрабатывал отступы при появлении клавиатуры.