在Jetpack Compose中请求TextField的焦点

91

如何在Jetpack Compose中自动对焦文本字段。当我点击文本字段并开始输入时,同时当我点击返回按钮,然后尝试再次点击文本字段时,不会发生任何事情。

如何在Jetpack Compose中自动聚焦文本框,在单击文本框并开始键入时,若此时按下返回按钮,则在尝试再次点击文本框时,将无法获得焦点。

val textState = remember { mutableStateOf(TextFieldValue()) }
TextField(
    modifier = Modifier.fillMaxWidth(),
    value = textState.value,
    onValueChange = { value : TextFieldValue ->
        textState.value = value
    }
)
7个回答

120
发布更新的答案回答这个问题(API在Compose Beta中更名)。
@Composable
fun AutoFocusingText() {
    var value by mutableStateOf("Enter Text")
    val focusRequester = remember { FocusRequester() }
    TextField(
        value = value, 
        onValueChange = { value = it },
        modifier = Modifier.focusRequester(focusRequester)
    )
    
    LaunchedEffect(Unit) {
        focusRequester.requestFocus()
    }
}

6
这个方法有效,但会将光标设置在单词的开头。如何在请求焦点时将光标设置在单词的结尾? - Charles Woodson
3
@CharlesWoodson,您需要在TextField的值中使用TextFieldValue。您可以在TextFieldValue中设置选定内容。 - dumbfingers
8
如果你仍然想要把光标设置到文本框的末尾,那么以下是做法: 首先定义一个可变状态的变量textFieldValue,代码如下: val textFieldValue = remember { mutableStateOf( TextFieldValue(someString, TextRange(someString.length)) ) } 然后把TextField的value属性设为textFieldValue.value,代码如下: TextField( value = textFieldValue.value ) 这样就可以使文本框被预填充为someString并且光标位置在字符串的末尾。 - Anudeep Ananth
1
使用LaunchedEffect不是比DisposableEffect更好吗? - Anigif
@AnudeepAnanth,使用您的代码,我们无法像默认行为一样移动光标,编辑文本时会出现问题。 - Mohd Qasim
为了解决这个问题,我们检查一下我下面的答案。 - Mohd Qasim

38
您可以使用FocusRequesterSideEffect一起使用。
例如:
var text by remember { mutableStateOf("text") }

// initialize focus reference to be able to request focus programmatically
val focusRequester = FocusRequester()

Column {
    TextField(
        value = text,
        onValueChange = {
            text = it
        },
        label = { Text("label") },
        modifier = Modifier
            // add focusRequester modifier 
            .focusRequester(focusRequester)
    )

然后使用 focusRequester.requestFocus()。通过这种方式,系统将焦点授予与此 FocusRequester 相关联的组件。

要使字段在屏幕出现时自动聚焦,您可以使用:

LaunchedEffect(Unit) {
    focusRequester.requestFocus()
}

要手动将焦点授予该字段:

    Button(
         onClick = { focusRequester.requestFocus() }
    ){
        Text("Click to give the focus")
    }

10
在自动聚焦字段方面,使用DisposableEffect相比于LaunchedEffect的优点是什么? - danartillaga
1
还是只使用 SideEffect,它不会启动协程或需要实现 onDispose - Florian Walther
5
DisposableEffect 用于仅请求一次焦点,但由于我们不使用 onDispose,它没有意义。因此,我认为使用 LaunchedEffect(Unit){} 更有意义,因为它只会在首次创建 Compose 时启动,并且不会在重新组合时请求焦点。 - Amr
我该如何取消焦点? - Tanjim ahmed
1
在某些设备上,在LaunchedEffect中调用focusRequester.requestFocus()时,我偶尔会遇到“FocusRequester未初始化”的异常。可能是竞态条件,但这种解决方案并不适用于所有情况。 - Sven Jacobs
9
这种情况不仅会出现在LaunchedEffect中,还会出现在DisposableEffect中。在组合期间不应请求焦点。我找到的解决方法是,在组合之后立即请求焦点。 因此,您可以执行以下操作:```LaunchedEffect(Unit) { this.coroutineContext.job.invokeOnCompletion { focusRequester.requestFocus() } }``` 这样可以确保您的代码将在LauchedEffect离开组合时运行,因为它(协程)要么被取消,要么完成。 - Benyam

15
在最新版本的Jetpack Compose(目前为1.3.3),如果你使用了这个:
LaunchedEffect(Unit) {
    focusRequester.requestFocus()
}

尝试自动聚焦一个TextField,你会得到:
java.lang.IllegalStateException:
FocusRequester is not initialized. Here are some possible fixes:

    1. Remember the FocusRequester: val focusRequester = remember { FocusRequester() }
    2. Did you forget to add a Modifier.focusRequester() ?
    3. Are you attempting to request focus during composition? Focus requests should be made in
    response to some event. Eg Modifier.clickable { focusRequester.requestFocus() }

这里有一个优雅的解决方案,而不使用delay
val focusRequester = remember { FocusRequester() }
var textFieldLoaded by remember { mutableStateOf(false) }

TextField(
    modifier = Modifier
        .focusRequester(focusRequester)
        .onGloballyPositioned {
            if (!textFieldLoaded) {
                focusRequester.requestFocus() // IMPORTANT
                textFieldLoaded = true // stop cyclic recompositions
            }
        },
    value = someValue,
    onValueChange = {
        // ...
    }
)

// We don't need this
// LaunchedEffect(Unit) {
//     focusRequester.requestFocus()
// }


3
我尝试了很多方法来请求焦点,但只有这个方法有效!许多与焦点相关的函数似乎已经在Jetpack Compose中被弃用,但这些函数没有。 即使是那些默认情况下不可聚焦的组件,比如Text(),只需在.focusRequester(focusRequester)之后添加.focusable()即可,这些函数的顺序很重要! - Glenn85
1
这个不是答案吗?这样简单多了,有什么问题吗? - Travis Griggs
我有两个文本字段,当我使用这种方法时无法从第一个文本字段切换到第二个文本字段。有什么解决方法吗? - Tonnie
2
@ Tonnie在'onGloballyPositioned'中使用'requestFocus'会导致循环重新组合,并且您将无限获得焦点。请改用'LaunchedEffect'方法。 - UneXp
@UneXp 不错的建议,我会记住的。 - Tonnie

14
val focusRequester = remember { FocusRequester() }
val inputService = LocalTextInputService.current
val focus = remember { mutableStateOf(false) }
BasicTextField(
    value = "value",
    modifier = Modifier
        .height(40.dp)
        .fillMaxWidth()
        .focusRequester(focusRequester)
        .onFocusChanged {
            if (focus.value != it.isFocused) {
                focus.value = it.isFocused
                if (!it.isFocused) {
                    inputService?.hideSoftwareKeyboard()
                }
            }
        },
),

LaunchedEffect("") {
    delay(300)
    inputService?.showSoftwareKeyboard()
    focusRequester.requestFocus()
}

3
请在您的回答中提供更多细节。目前的写法让人难以理解您的解决方案。 - Community
感谢您解决键盘状态的问题!其他答案似乎没处理这个。 - VIN

7
如果你希望在导航到屏幕时自动将文本字段聚焦,你可以使用以下方法。这样可以避免在组合过程中进行requestFocus操作,这不是应该做的事情。否则,有时会出现带有“FocusRequester未初始化”的IllegalStateException消息。
 @Composable fun YourComposable(){ 
   val focusRequester = remember {FocusRequester()}
    
        LaunchedEffect(Unit) {
            this.coroutineContext.job.invokeOnCompletion {
                focusRequester.requestFocus()
            }
        }
    
       OutlinedTextField( modifier = Modifier.focusRequester(focusRequester))
         
              
}

这是因为 LaunchedEffect 中的协程会在它离开组合时被取消。

5

从Compose单元测试应用到TextField 链接

    @ExperimentalFocus
    @Composable
    fun AutoFocusingText() {
        val textState = remember { mutableStateOf(TextFieldValue()) }
        val focusState = remember { mutableStateOf(FocusState.Inactive) }
        val focusRequester = FocusRequester()
        val focusModifier = Modifier.focus()
        Row(
            modifier = Modifier.focusObserver { focusState.value = it }
        ) {
            val focusRequesterModifier = Modifier.focusRequester(focusRequester)
            TextField(
                modifier = focusModifier.then(focusRequesterModifier),
                value = textState.value,
                onValueChange = { value: TextFieldValue ->
                    textState.value = value
                },

                )
        }
        onActive {
            focusRequester.requestFocus()
        }
    }

编辑:将 Box 更改为 Row,因为在“1.0.0-alpha04”中已弃用 Box。


3

    @Composable fun SomeComposable() {
        val focusRequester = remember { FocusRequester() }

    OutlinedTextField(
        value = TextFieldValue(
        text = state.text, // state come from else where
        selection = TextRange(state.text.length,state.text.length)),  
   
        onValueChange = { _:TextFieldValue -> Unit },
        modifier = Modifier
          .fillMaxWidth()
          .focusRequester(focusRequester),)

    SideEffect {
        focusRequester.requestFocus()
        }
    }

为什么要使用SideEffect:是文档中的第一行。
关于光标位置,这个回答很有帮助。 onValueChanged 在我的工作代码中保留原样,因为我不需要它。


每次重新组合后,都会运行一个副作用。 - undefined

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接