开启掘金成长之旅!这是我参与「掘金日新计划 · 2 月更文挑战」的第 13 天,点击查看活动详情
TextField
文本输入框,类似于iOS的UITextFiled和UITextView的集合。
const TextField({
...
TextEditingController controller, //编辑框的控制器,通过它可以获取、选择、编辑、监听文本改变事件。
FocusNode focusNode,//是否占有当前键盘的焦点
InputDecoration decoration = const InputDecoration(),//用于控制TextField的外观显示,本身是一个装饰器,用于修改背景色边框等。
TextInputType keyboardType,//键盘样式
TextInputAction textInputAction,//键盘回车样式
TextStyle style,//正在编辑的文本样式
TextAlign textAlign = TextAlign.start,//输入框内编辑文本在水平方向的对齐方式
bool autofocus = false,//是否自动聚焦
bool obscureText = false,//是否隐藏正在编辑的文本
int maxLines = 1,//文本最大行数
int maxLength,//输入的最大内容
this.maxLengthEnforcement,//超过最大长度后文本显示样式
ToolbarOptions? toolbarOptions,//长按或鼠标右击出现的菜单
ValueChanged<String> onChanged,//输入框内容改变时的回调
VoidCallback onEditingComplete,//输入完成时回调
ValueChanged<String> onSubmitted,//输入完成时回调,接收参数ValueChanged<String>
List<TextInputFormatter> inputFormatters,//用于指定输入格式,当用户输入内容改变时,会根据指定的格式来校验。
bool enabled,//是否禁用,类似于iOS的enable
this.cursorWidth = 2.0,//光标宽度
this.cursorRadius,//光标圆角
this.cursorColor,//光标颜色
this.onTap,//点击
...
})
使用实例:
Column(
children: <Widget>[
TextField(
autofocus: true,
decoration: InputDecoration(
labelText: "用户名",
hintText: "用户名或邮箱",
prefixIcon: Icon(Icons.person)
),
),
TextField(
decoration: InputDecoration(
labelText: "密码",
hintText: "您的登录密码",
prefixIcon: Icon(Icons.lock)
),
obscureText: true,
),
],
);
需要注意的是,Widget中的组件,大多是通过Controller去控制的,比如在Widget中也提供了比如onChange的方法去监听文本的输入,以及一些其他的用法。
@override
void initState() {
//监听输入改变
_unameController.addListener((){
print(_unameController.text);
});
}
//设置默认值
_selectionController.text="hello world!";
//选中后面字符
_selectionController.selection=TextSelection(
baseOffset: 2,
extentOffset: _selectionController.text.length
);
//自定义文本框样式
TextField(
decoration: InputDecoration(
labelText: "请输入用户名",
prefixIcon: Icon(Icons.person),
// 未获得焦点下划线设为灰色
enabledBorder: UnderlineInputBorder(
borderSide: BorderSide(color: Colors.grey),
),
//获得焦点下划线设为蓝色
focusedBorder: UnderlineInputBorder(
borderSide: BorderSide(color: Colors.blue),
),
),
),
Form
继承StatefulWidget对象,对应的状态为FormState。
Form({
required Widget child,
bool autovalidate = false,//是否自动校验输入内容,为True,每次输入都会去校验
WillPopCallback onWillPop,//决定Form所在的路由是否可以直接返回,返回一个Future
VoidCallback onChanged,//Form的任意一个FormField内容变化会触发回调
})
FormField
Form的子元素必须是FormField类型:
const FormField({
...
FormFieldSetter<T> onSaved, //保存回调
FormFieldValidator<T> validator, //验证回调
T initialValue, //初始值
bool autovalidate = false, //是否自动校验。
})
FormState
FormState为Form的State类,可以通过Form.of()或者GlobalKey获取。
- FormState.validate():调用此方法后,会调用Form子孙FormField的validate回调,如果有一个校验失败,则返回false
- FormState.save():会调用子Widget FormState的save回调,用于保存表单内容
- FormState.reset():会将子孙FormField的内容清空
import 'package:flutter/material.dart';
class FormTestRoute extends StatefulWidget {
@override
_FormTestRouteState createState() => _FormTestRouteState();
}
class _FormTestRouteState extends State<FormTestRoute> {
TextEditingController _unameController = TextEditingController();
TextEditingController _pwdController = TextEditingController();
GlobalKey _formKey = GlobalKey<FormState>();
@override
Widget build(BuildContext context) {
return Form(
key: _formKey, //设置globalKey,用于后面获取FormState
autovalidateMode: AutovalidateMode.onUserInteraction,
child: Column(
children: <Widget>[
TextFormField(
autofocus: true,
controller: _unameController,
decoration: InputDecoration(
labelText: "用户名",
hintText: "用户名或邮箱",
icon: Icon(Icons.person),
),
// 校验用户名
validator: (v) {
return v!.trim().isNotEmpty ? 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: ElevatedButton(
child: Padding(
padding: const EdgeInsets.all(16.0),
child: Text("登录"),
),
onPressed: () {
// 通过_formKey.currentState 获取FormState后,
// 调用validate()方法校验用户名密码是否合法,校验
// 通过后再提交数据。
if ((_formKey.currentState as FormState).validate()) {
//验证通过提交数据
}
},
),
),
],
),
)
],
),
);
}
}
注意,登录按钮的onPressed方法中不能通过Form.of(context)来获取FormState,原因是,此处的context为FormTestRoute的context,而Form.of(context)是根据所指定context向根去查找,而FormState是在FormTestRoute的子树中,所以不行。正确的做法是通过Builder来构建登录按钮,Builder会将widget节点的context作为回调参数:
Expanded(
// 通过Builder来获取ElevatedButton所在widget树的真正context(Element)
child:Builder(builder: (context){
return ElevatedButton(
...
onPressed: () {
//由于本widget也是Form的子代widget,所以可以通过下面方式获取FormState
if(Form.of(context).validate()){
//验证通过提交数据
}
},
);
})
)
其实context正是操作Widget所对应的Element的一个接口,由于Widget树对应的Element都是不同的,所以context也都是不同的,有关context的更多内容会在后面高级部分详细讨论。Flutter中有很多“of(context)”这种方法,读者在使用时一定要注意context是否正确。