新建项目-5.png

Form的子孙元素必须是FormField类型,FormField是一个抽象类,定义几个属性,FormState内部通过它们来完成操作,FormField部分定义如下:

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

为了方便使用,Flutter提供了一个TextFormField组件,它继承自FormField类,也是TextField的一个包装类,所以除了FormField定义的属性之外,它还包括TextField的属性。

FormState

FormStateFormState类,可以通过Form.of()GlobalKey获得。我们可以通过它来对Form的子孙FormField进行统一操作。我们看看其常用的三个方法:

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

    示例

    我们修改一下上面用户登录的示例,在提交之前校验:
  1. 用户名不能为空,如果为空则提示“用户名不能为空”。
  2. 密码不能小于6位,如果小于6为则提示“密码不能少于6位”。

完整代码:

  1. class _MyHomePageState extends State<MyHomePage> {
  2. TextEditingController _unameController = new TextEditingController();
  3. TextEditingController _pwdController = new TextEditingController();
  4. GlobalKey _formKey= new GlobalKey<FormState>();
  5. @override
  6. Widget build(BuildContext context) {
  7. return Scaffold(
  8. appBar: AppBar(
  9. title:Text("Form Test"),
  10. ),
  11. body: Padding(
  12. padding: const EdgeInsets.symmetric(vertical: 16.0, horizontal: 24.0),
  13. child: Form(
  14. key: _formKey, //设置globalKey,用于后面获取FormState
  15. autovalidate: true, //开启自动校验
  16. child: Column(
  17. children: <Widget>[
  18. TextFormField(
  19. autofocus: true,
  20. controller: _unameController,
  21. decoration: InputDecoration(
  22. labelText: "用户名",
  23. hintText: "用户名或邮箱",
  24. icon: Icon(Icons.person)
  25. ),
  26. // 校验用户名
  27. validator: (v) {
  28. return v
  29. .trim()
  30. .length > 0 ? null : "用户名不能为空";
  31. }
  32. ),
  33. TextFormField(
  34. controller: _pwdController,
  35. decoration: InputDecoration(
  36. labelText: "密码",
  37. hintText: "您的登录密码",
  38. icon: Icon(Icons.lock)
  39. ),
  40. obscureText: true,
  41. //校验密码
  42. validator: (v) {
  43. return v
  44. .trim()
  45. .length > 5 ? null : "密码不能少于6位";
  46. }
  47. ),
  48. // 登录按钮
  49. Padding(
  50. padding: const EdgeInsets.only(top: 28.0),
  51. child: Row(
  52. children: <Widget>[
  53. Expanded(
  54. child: RaisedButton(
  55. padding: EdgeInsets.all(15.0),
  56. child: Text("登录"),
  57. color: Theme
  58. .of(context)
  59. .primaryColor,
  60. textColor: Colors.white,
  61. onPressed: () {
  62. //在这里不能通过此方式获取FormState,context不对
  63. //print(Form.of(context));
  64. // 通过_formKey.currentState 获取FormState后,
  65. // 调用validate()方法校验用户名密码是否合法,校验
  66. // 通过后再提交数据。
  67. if((_formKey.currentState as FormState).validate()){
  68. //验证通过提交数据
  69. }
  70. },
  71. ),
  72. ),
  73. ],
  74. ),
  75. )
  76. ],
  77. ),
  78. ),
  79. ),
  80. );
  81. }
  82. }

image.png

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

  1. Expanded(
  2. // 通过Builder来获取RaisedButton所在widget树的真正context(Element)
  3. child:Builder(builder: (context){
  4. return RaisedButton(
  5. ...
  6. onPressed: () {
  7. //由于本widget也是Form的子代widget,所以可以通过下面方式获取FormState
  8. if(Form.of(context).validate()){
  9. //验证通过提交数据
  10. }
  11. },
  12. );
  13. })
  14. )

其实context正是操作Widget所对应的Element的一个接口,由于Widget树对应的Element都是不同的,所以context也都是不同的,有关context的更多内容会在后面高级部分详细讨论。Flutter中有很多“of(context)”这种方法,读者在使用时一定要注意context是否正确。

因为自己去写的时候发现这块有的小部件不熟悉,有点懵逼. 暂时复制过来. 以后会修改
文章完全复制地址: https://book.flutterchina.club/chapter3/input_and_form.html