Skip to content

表单

Form

Form 继承自 StatefulWidget 对象,它对应的状态类为 FormState

dart
Form({
  required Widget child,
  bool autovalidate = false,
  WillPopCallback onWillPop,
  VoidCallback onChanged,
})
  • autovalidate:是否自动校验输入内容;当为true时,每一个子 FormField 内容发生变化时都会自动校验合法性,并直接显示错误信息。否则,需要通过调用FormState.validate()来手动校验
  • onWillPop:决定Form所在的路由是否可以直接返回(如点击返回按钮),该回调返回一个Future对象,如果 Future 的最终结果是false,则当前路由不会返回;如果为true,则会返回到上一个路由。此属性通常用于拦截返回按钮
  • onChangedForm的任意一个子FormField内容发生变化时会触发此回调

FormField

Form的子孙元素必须是FormField类型

dart
const FormField({
  ...
  FormFieldSetter<T> onSaved, //保存回调
  FormFieldValidator<T>  validator, //验证回调
  T initialValue, //初始值
  bool autovalidate = false, //是否自动校验。
})

为了方便使用,Flutter 提供了一个TextFormField组件,它继承自FormField

FormState

FormStateFormState类,可以通过Form.of()GlobalKey获得。我们可以通过它来对Form的子孙FormField进行统一操作

  • FormState.validate():调用此方法后,会调用Form子孙FormField的validate回调,如果有一个校验失败,则返回false,所有校验失败项都会返回用户返回的错误提示。
  • FormState.save():调用此方法后,会调用Form子孙FormFieldsave回调,用于保存表单内容
  • FormState.reset():调用此方法后,会将子孙FormField的内容清空。

登录例子

dart
class _HomePageState extends State<HomePage> {
  TextEditingController _usernameController = TextEditingController();
  TextEditingController _passwordController = TextEditingController();
  GlobalKey _formKey = GlobalKey<FormState>();

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: Text('Home')),
      body: Padding(
        padding: const EdgeInsets.all(18.0),
        child: Form(
          key: _formKey, // 设置globalKey,用于后面获取FormState
          autovalidateMode: AutovalidateMode.onUserInteraction,
          child: Column(
            children: [
              TextFormField(
                autofocus: true,
                controller: _usernameController,
                decoration: InputDecoration(labelText: "用户名"),
                validator: (v) => v!.trim().length > 0 ? null : "用户名不能为空",
              ),
              TextFormField(
                controller: _passwordController,
                decoration: InputDecoration(labelText: "密码"),
                obscureText: true,
                validator: (v) => v!.trim().length > 5 ? null : "密码不能少于 6 位",
              ),
              Container(
                alignment: Alignment.center,
                child: ElevatedButton(
                  onPressed: () {
                    if ((_formKey.currentState as FormState).validate()) {
                      // 验证通过提交数据
                    }
                  },
                  child: Text('登录')
                )
              )
            ],
          ),
        )
      )
    );
  }
}

登录按钮的onPressed方法中不能通过Form.of(context)来获取

原因是,此处的contextFormPage的context,而Form.of(context)是根据所指定context向根去查找,而FormState是在FormPage的子树中,所以不行。正确的做法是通过Builder来构建登录按钮,Builder会将widget节点的context作为回调参数

dart
Container(
  alignment: Alignment.center,
  child: Builder(builder: (context) {
    return ElevatedButton(
      onPressed: () {
        if (Form.of(context)?.validate() ?? false) {
          // 验证通过提交数据
        }
      },
      child: Text('登录')
    );
  })
)