You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Jetpack Compose表单验证:如何将焦点定位到首个错误字段?

实现Jetpack Compose表单首个错误字段自动聚焦

1. ViewModel层维护错误状态与首个错误标识

在ViewModel里,为每个表单字段单独维护错误信息,同时新增一个状态来记录视觉顺序上的首个错误字段标识(比如用字符串或枚举区分不同字段)。校验时严格按照表单显示的顺序判断,第一个触发错误的字段就写入标识。

示例代码:

class FormViewModel : ViewModel() {
    // 输入字段状态
    val username = mutableStateOf("")
    val password = mutableStateOf("")
    val email = mutableStateOf("")

    // 字段错误信息
    private val _usernameError = mutableStateOf<String?>(null)
    val usernameError: State<String?> = _usernameError

    private val _passwordError = mutableStateOf<String?>(null)
    val passwordError: State<String?> = _passwordError

    private val _emailError = mutableStateOf<String?>(null)
    val emailError: State<String?> = _emailError

    // 首个错误字段标识
    private val _firstErrorField = mutableStateOf<String?>(null)
    val firstErrorField: State<String?> = _firstErrorField

    fun validateForm() {
        // 重置所有错误状态
        _usernameError.value = null
        _passwordError.value = null
        _emailError.value = null
        _firstErrorField.value = null

        // 按表单显示顺序校验,第一个出错的字段记录标识
        if (username.value.isBlank()) {
            _usernameError.value = "用户名不能为空"
            _firstErrorField.value = "username"
        } else if (password.value.length < 6) {
            _passwordError.value = "密码长度不能少于6位"
            _firstErrorField.value = "password"
        } else if (!email.value.contains("@")) {
            _emailError.value = "请输入合法邮箱"
            _firstErrorField.value = "email"
        }
    }
}

2. UI层绑定焦点请求器并触发聚焦

为每个输入框创建FocusRequester,通过LaunchedEffect监听ViewModel的首个错误字段标识变化,一旦有值就自动请求对应输入框的焦点。

示例代码:

@Composable
fun FormScreen(viewModel: FormViewModel = viewModel()) {
    val focusManager = LocalFocusManager.current

    // 为每个字段绑定焦点请求器
    val usernameFocus = FocusRequester()
    val passwordFocus = FocusRequester()
    val emailFocus = FocusRequester()

    // 监听首个错误字段,触发聚焦
    LaunchedEffect(viewModel.firstErrorField.value) {
        when (viewModel.firstErrorField.value) {
            "username" -> usernameFocus.requestFocus()
            "password" -> passwordFocus.requestFocus()
            "email" -> emailFocus.requestFocus()
        }
    }

    Column(
        modifier = Modifier.padding(16.dp),
        verticalArrangement = Arrangement.spacedBy(12.dp)
    ) {
        // 用户名输入框
        OutlinedTextField(
            value = viewModel.username.value,
            onValueChange = { viewModel.username.value = it },
            label = { Text("用户名") },
            modifier = Modifier
                .fillMaxWidth()
                .focusRequester(usernameFocus),
            isError = viewModel.usernameError.value != null,
            supportingText = { viewModel.usernameError.value?.let { Text(it) } }
        )

        // 密码输入框
        OutlinedTextField(
            value = viewModel.password.value,
            onValueChange = { viewModel.password.value = it },
            label = { Text("密码") },
            modifier = Modifier
                .fillMaxWidth()
                .focusRequester(passwordFocus),
            isError = viewModel.passwordError.value != null,
            supportingText = { viewModel.passwordError.value?.let { Text(it) } },
            visualTransformation = PasswordVisualTransformation()
        )

        // 邮箱输入框
        OutlinedTextField(
            value = viewModel.email.value,
            onValueChange = { viewModel.email.value = it },
            label = { Text("邮箱") },
            modifier = Modifier
                .fillMaxWidth()
                .focusRequester(emailFocus),
            isError = viewModel.emailError.value != null,
            supportingText = { viewModel.emailError.value?.let { Text(it) } }
        )

        Button(
            onClick = { viewModel.validateForm() },
            modifier = Modifier.fillMaxWidth()
        ) {
            Text("提交")
        }
    }
}

3. 优化小技巧

如果表单字段较多,可以用Map统一管理焦点请求器,减少重复代码:

val focusRequesters = mapOf(
    "username" to FocusRequester(),
    "password" to FocusRequester(),
    "email" to FocusRequester()
)

// LaunchedEffect里简化为
viewModel.firstErrorField.value?.let { fieldKey ->
    focusRequesters[fieldKey]?.requestFocus()
}

内容的提问来源于stack exchange,提问作者Lena

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.19 08:25:11