正式向服务器提交数据前,都会对各个输入框数据进行合法性校验。但对每个TextField都分别校验很麻烦。
如果想清除一组TextfFiled的内容,一个个清除也很麻烦。所以,Flutter提供了一个Form widget,可以对输入框进行分组,然后进行一些统一的操作。
Form:
Form继承自StatefulWidget对象,它对应的状态类为FormState。
其定义:
Form({
@required Widget child,
bool autovalidate = false,
WillPopCallback onWillPop,
VoidCallback onChanged,
})
其中:
autovalidate:是否自动校验输入内容;当为 true 时,每一个子FormField内容发生变化时
都会自动校验合法性,并直接显示错误信息。否则,需要通过调用 FormState.validate() 来手
动校验.
onWillPop:决定Form所在的路由是否可以直接返回(如点击返回按钮),该回调返回一
个 Future 对象,如果Future的最终结果是false,则当前路由不会返回;如果为 true ,则
会返回到上一个路由。此属性通常用于拦截返回按钮。
onChanged:Form的任意一个子FormField内容发生变化时会触发此回调。
FormField:
Form的子孙元素必须是FormField类型,FormField是一个抽象类,定义几个属性,FormState内 部通过它们来完成操作,
const FormField({
...
FormFieldSetter<T> onSaved, //保存回调
FormFieldValidator<T> validator, //验证回调
T initialValue, //初始值
bool autovalidate = false, //是否自动校验。
})
Flutter提供了一个TextFormField widget,它继承自FormField类,也是 TextField的一个包装类,所以除了FormField定义的属性之外,它还包括TextField的属性。
FormState:
FormState为Form的State类,可以通过 Form.of() 或GlobalKey获得。可以通过它来对 Form的子孙FormField进行统一操作。
其常用的三个方法:
FormState.validate() :调用此方法后,会调用Form子孙FormField的validate回调,如 果有一个校验失败,则返回false,所有校验失败项都会返回用户返回的错误提示。
FormState.save() :调用此方法后,会调用Form子孙FormField的save回调,用于保存表单 内容.
FormState.reset() :调用此方法后,会将子孙FormField的内容清空。
实战示例:
用户登录的示例,在提交之前校验:
1.用户名不能为空,如果为空则提示“用户名不能为空”。
2. 密码不能小于6位,如果小于6为则提示“密码不能少于6位”。
代码如下:
import 'package:flutter/material.dart';
/**
* form表单的使用
*/
void main() => runApp(MyApp());
class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Flutter 表单Form',
theme: ThemeData(
primarySwatch: Colors.blue,
),
home: FormRouteDemo(),
);
}
}
class FormRouteDemo extends StatefulWidget {
@override
_FormRouteDemoState createState() => _FormRouteDemoState();
}
class _FormRouteDemoState extends State<FormRouteDemo> {
TextEditingController _unameController = new TextEditingController();
TextEditingController _pwdController = new TextEditingController();
//FormState为Form的State类,可以通过 Form.of() 或GlobalKey获得。我们可以通过它来对
//Form的子孙FormField进行统一操作。
GlobalKey _formKey = new GlobalKey<FormState>();
@override
Widget build(BuildContext context) {
return Scaffold(
body: Padding(
//symmetric代表着对称,其vertical代表上下对称,horizontal代表左右对称
padding: const EdgeInsets.symmetric(vertical: 16.0, horizontal: 24.0),
child: Form(
key: _formKey,//设置GlobalKey,用于后面获取FormState
autovalidate: true,//开启自动校验
child: Column(
children: <Widget>[
TextFormField(
autofocus: true,
controller: _unameController,
decoration: InputDecoration(
labelText: '用户名',
hintText: '用户名或邮箱',
icon: Icon(Icons.person)
),
//校验用户名
validator: (v){
return v
.trim()
.length > 0 ? null : '用户名不能为空';
},
),
TextFormField(
controller: _pwdController,
decoration: InputDecoration(
labelText: '密码',
hintText: '您的登录密码',
icon: Icon(Icons.lock)
),
obscureText: true,
//校验密码
validator: (v){
return v
.trim()
.length > 5 ? null : '密码不能少于6位';
},
),
//登录按钮
Padding(
padding: const EdgeInsets.only(top: 28.0),
child: Row(
children: <Widget>[
Expanded(
child: RaisedButton(
padding: EdgeInsets.all(15.0),
child: Text('登录'),
color: Theme.of(context).primaryColor,
textColor: Colors.white,
onPressed: (){
/***
* 在这里不能通过Form.of(context)的方式获取FormState,context不对
* 通过_formKey.currentState获取FormState后,
* 调用validate()方法校验用户名密码是否合法,校验通过后再提交数据
*/
if((_formKey.currentState as FormState).validate()){
//验证通过提交数据
//。。。。。
}
}
),
)
],
),
)
],
)
),
),
);
}
}
上述代码说明:
登录按钮的onPressed方法中不能通过 Form.of(context) 来获取,原因是,此处的 context为FormTestRoute的context,而 Form.of(context) 是根据所指定context向根去查 找,而FormState是在FormTestRoute的子树中,所以不行。正确的做法是通过Builder来构建登录 按钮,Builder会将widget节点的context作为回调参数:
Expanded(
// 通过Builder来获取RaisedButton所在widget树的真正context(Element)
child:Builder(builder: (context){
return RaisedButton(
...
onPressed: () {
//由于本widget也是Form的子代widget,所以可以通过下面方式获取FormState
if(Form.of(context).validate()){
//验证通过提交数据
}
},
);
})
)
其实context正是操作Widget所对应的Element的一个接口,由于Widget树对应的Element都是不 同的,所以context也都是不同的。Element树才是最终的绘制树,Element树是通过widget树来创建的(通 过 Widget.createElement() ),widget其实就是Element的配置数据。
效果图:
很开心的一个消息是Flutter Web 预览版出来了,期待他的正式版!