Avatar ·

Как программно установить фокус на TextField в Jetpack Compose?

Всем привет! Я только начал осваивать Jetpack Compose и столкнулся с проблемой, которая в классическом Android View решалась просто вызовом `requestFocus()`. Мне нужно, чтобы при открытии экрана или при нажатии на кнопку фокус автоматически переходил на определённое поле ввода (TextField). Например, есть экран поиска, где пользователь сразу должен начать вводить текст, не нажимая на поле ввода. Вот мой текущий код: ```kotlin @Composable fun SearchScreen() { var query by remember { mutableStateOf("") } TextField( value = query, onValueChange = { query = it }, label = { Text("Поиск") } ) Button(onClick = { /* нужно установить фокус обратно на TextField */ }) { Text("Сбросить и сфокусироваться") } } ``` Пробовал использовать `Modifier.focusRequester()` и `FocusRequester`, но у меня ничего не выходит — фокус не устанавливается. В документации написано что-то про `focusable()`, но я не понимаю, как это правильно применить. Подскажите, как правильно реализовать программное управление фокусом в Jetpack Compose? Желательно с рабочим примером для моей задачи: автофокус при запуске и возврат фокуса по кнопке. <<>>

Правильное управление фокусом через FocusRequester

В Jetpack Compose для программного управления фокусом используется механизм FocusRequester. Это аналог requestFocus() из View-системы, но с важными нюансами. Давайте разберём вашу задачу по шагам.

Базовое решение для вашего примера

@Composable
fun SearchScreen() {
    var query by remember { mutableStateOf("") }
    val focusRequester = remember { FocusRequester() }
    
    // Важно: FocusRequester нужно привязать через модификатор
    TextField(
        value = query,
        onValueChange = { query = it },
        label = { Text("Поиск") },
        modifier = Modifier.focusRequester(focusRequester)
    )
    
    Button(onClick = {
        query = ""
        focusRequester.requestFocus() // Возвращаем фокус
    }) {
        Text("Сбросить и сфокусироваться")
    }
    
    // Автофокус при первом появлении
    LaunchedEffect(Unit) {
        focusRequester.requestFocus()
    }
}

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

  • remember { FocusRequester() } создаёт объект, который живёт всё время существования компонента
  • Modifier.focusRequester() связывает этот объект с конкретным TextField
  • LaunchedEffect(Unit) — корутина, которая выполняется один раз при входе в композицию
  • Вызов requestFocus() должен происходить после того, как TextField уже отрисован

Почему у вас не работало?

Самая частая ошибка — попытка вызвать requestFocus() до того, как элемент появился на экране. Compose — декларативный, и фокус можно запрашивать только когда элемент уже в иерархии. LaunchedEffect гарантирует, что вызов произойдёт в правильный момент жизненного цикла.

Продвинутый вариант с обработкой ошибок

Если фокус может не установиться (например, элемент скрыт), используйте безопасный вызов:

@Composable
fun SafeSearchScreen() {
    var query by remember { mutableStateOf("") }
    var isVisible by remember { mutableStateOf(true) }
    val focusRequester = remember { FocusRequester() }
    
    Column {
        if (isVisible) {
            TextField(
                value = query,
                onValueChange = { query = it },
                label = { Text("Поиск") },
                modifier = Modifier
                    .focusRequester(focusRequester)
                    .focusable() // Явно указываем, что элемент может получать фокус
            )
        }
        
        Button(onClick = { isVisible = !isVisible }) {
            Text(if (isVisible) "Скрыть поле" else "Показать поле")
        }
        
        // Запрос фокуса только когда поле видимо
        LaunchedEffect(isVisible) {
            if (isVisible) {
                // Задержка для гарантии отрисовки
                delay(100)
                focusRequester.requestFocus()
            }
        }
    }
}

Работа с клавиатурой

Часто вместе с фокусом нужно показать клавиатуру. Для этого используйте SoftwareKeyboardController:

val keyboardController = LocalSoftwareKeyboardController.current

// Внутри обработчика кнопки:
focusRequester.requestFocus()
keyboardController?.show()

Важные замечания

  • Не используйте focusRequester внутри LaunchedEffect с бесконечным циклом — это приведёт к бесконечной перерисовке
  • Для нескольких полей создавайте отдельные FocusRequester для каждого
  • В Compose Multiplatform (Desktop, Web) SoftwareKeyboardController может быть недоступен
  • Если фокус не работает, проверьте, не перехватывает ли его другой элемент (например, модальное окно)

Теперь ваш код будет работать как ожидалось: поле поиска автоматически получит фокус при открытии экрана, а кнопка вернёт фокус после сброса текста.

<<>>

Альтернативный подход: FocusManager

Иногда удобнее использовать FocusManager вместо FocusRequester, особенно когда нужно перемещать фокус между несколькими полями или сбрасывать его.

@Composable
fun AlternativeSearchScreen() {
    var query by remember { mutableStateOf("") }
    val focusManager = LocalFocusManager.current
    
    TextField(
        value = query,
        onValueChange = { query = it },
        label = { Text("Поиск") },
        modifier = Modifier
            .onFocusChanged { state ->
                if (state.isFocused) {
                    println("Поле получило фокус")
                }
            }
    )
    
    Button(onClick = {
        query = ""
        // Очищаем фокус со всех полей
        focusManager.clearFocus()
    }) {
        Text("Сбросить и убрать фокус")
    }
    
    // Для автофокуса используем FocusRequester, 
    // но FocusManager удобен для навигации
    Button(onClick = {
        // Перемещение фокуса на следующее поле
        focusManager.moveFocus(FocusDirection.Next)
    }) {
        Text("Следующее поле")
    }
}

Когда использовать FocusManager:

  • Для навигации между полями (Tab, стрелки)
  • Для программного сброса фокуса
  • В формах с множеством полей ввода

Обратите внимание: FocusManager не умеет напрямую устанавливать фокус на конкретное поле — для этого всё равно нужен FocusRequester. Но в паре они дают полный контроль над фокусом.

Совет: для автофокуса при запуске всегда используйте LaunchedEffect — это самый надёжный способ, проверенный тысячами разработчиков.