Flutter 的Material组件库中提供了输入框组件TextField
和表单组件Form
。git
TextField
主要用于文本输入。api
构造函数以下:bash
const TextField({
...
TextEditingController controller,
FocusNode focusNode,
InputDecoration decoration = const InputDecoration(),
TextInputType keyboardType,
TextInputAction textInputAction,
TextStyle style,
TextAlign textAlign = TextAlign.start,
bool autofocus = false,
bool obscureText = false,
int maxLines = 1,
int maxLength,
bool maxLengthEnforced = true,
ValueChanged<String> onChanged,
VoidCallback onEditingComplete,
ValueChanged<String> onSubmitted,
List<TextInputFormatter> inputFormatters,
bool enabled,
this.cursorWidth = 2.0,
this.cursorRadius,
this.cursorColor,
...
})
复制代码
不重要的就没粘app
controller
:编辑框的控制器,经过它能够设置/获取编辑框的内容、选择编辑内容、监听编辑文本改变事件等。
大多数状况下咱们都须要显式提供一个controller
来与文本框交互。若是没有提供controller
,则TextField
内部会自动建立一个。ide
focusNode
:用于控制TextField
是否占有当前键盘的输入焦点。它是咱们和键盘交互的一个句柄(handle)。函数
InputDecoration
:用于控制TextField
的外观显示,如提示文本、背景颜色、边框等。 源码示例(给出源码,本身下去尝试一下,不作过多解释)优化
const InputDecoration({
this.icon,
this.labelText,
this.labelStyle,
this.helperText,
this.helperStyle,
this.hintText,
this.hintStyle,
this.hintMaxLines,
this.errorText,
this.errorStyle,
this.errorMaxLines,
this.hasFloatingPlaceholder = true,
this.isDense,
this.contentPadding,
this.prefixIcon,
this.prefix,
this.prefixText,
this.prefixStyle,
this.suffixIcon,
this.suffix,
this.suffixText,
this.suffixStyle,
this.counter,
this.counterText,
this.counterStyle,
this.filled,
this.fillColor,
this.errorBorder,
this.focusedBorder,
this.focusedErrorBorder,
this.disabledBorder,
this.enabledBorder,
this.border,
this.enabled = true,
this.semanticCounterText,
this.alignLabelWithHint,
})
复制代码
keyboardType
:用于设置该输入框默认的键盘输入类型,TextInputType
枚举值以下:ui
maxLines
配合使用(设为null或大于1)* #
: -
@ .
/ .
textInputAction
:键盘动做按钮图标(即回车键位图标),它是一个枚举值,有多个可选值。所有取值请参考官方文档:api.flutter.dev
示例:当值为TextInputAction.search
时,原生Android系统下键盘样式以下:this
style
:正在编辑的文本样式有关属性在TextStyle
里设置url
textAlign
:输入框内编辑文本在水平方向的对齐方式。
如:textAlign: TextAlign.right,
则是从后往左显示
autofocus
:是否自动获取焦点。值为true
或者false
obscureText
:是否隐藏正在编辑的文本,如用于输入密码的场景等,文本内容会用“•”替换。
maxLines
:输入框的最大行数,默认为1;若是为null
,则无行数限制。
maxLength
和maxLengthEnforced
:maxLength
表明输入框文本的最大长度,设置后输入框右下角会显示输入的文本计数。maxLengthEnforced
决定当输入文本长度超过maxLength
时是否阻止输入,为true
时会阻止输入,为false
时不会阻止输入但输入框会变红。
onChanged
:输入框内容改变时的回调函数;注:内容改变事件也能够经过controller
来监听。
onEditingComplete
和onSubmitted
:这两个回调都是在输入框输入完成时触发,好比按了键盘的完成键(对号图标)或搜索键(🔍图标)。不一样的是两个回调签名不一样,onSubmitted
回调是ValueChanged<String>
类型,它接收当前输入内容作为参数,而onEditingComplete
不接收参数。
inputFormatters
:用于指定输入格式;当用户输入内容改变时,会根据指定的格式来校验。
cursorWidth
、cursorRadius
和cursorColor
:这三个属性是用于自定义输入框光标宽度、圆角和颜色的。
如:cursorColor: Colors.green
、cursorWidth: 10,
、cursorRadius: Radius.circular(4),
TextField(
keyboardType: TextInputType.url,
decoration: InputDecoration(
labelText: 'xxx',
prefix: Icon(Icons.lock),
// enabled: false
),
textInputAction: TextInputAction.next,
style: TextStyle(color: Colors.red),
textAlign: TextAlign.right,
autofocus: false,
// obscureText: true,
maxLines: null,
maxLength: 10,
maxLengthEnforced: true,
cursorColor: Colors.green,
cursorWidth: 10,
cursorRadius: Radius.circular(4),
),
复制代码
运行效果:
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,
),
],
);
复制代码
运行效果:
获取输入内容有两种方式:
定义两个变量,用于保存用户名和密码,而后在onChanged
触发时,各自保存一下输入内容。
经过controller
直接获取。
第一种方式比较简单,因此就不说了,重点说第二种方式,以用户名输入框为例:
controller
://定义一个controller
TextEditingController _unameController = TextEditingController();
复制代码
controller
:TextField(
autofocus: true,
controller: _unameController, //设置controller
...
)
复制代码
controller
获取输入框内容:print(_unameController.text)
复制代码
代码示例:
import 'package:flutter/material.dart';
class CategoryPage extends StatefulWidget {
@override
_CategoryPageState createState() => _CategoryPageState();
}
class _CategoryPageState extends State<CategoryPage> {
//定义一个controller
TextEditingController _unameController = TextEditingController();
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text("输入框"),
),
body: Column(
children: <Widget>[
TextField(
autofocus: true,
controller: _unameController, //设置
decoration: InputDecoration(
labelText: "用户名",
hintText: "用户名或邮箱",
prefixIcon: Icon(Icons.person),
),
onChanged: (String) => { //输入框改变时触发onChanged
print(_unameController.text) //打印输入框的内容
},
),
TextField(
decoration: InputDecoration(
labelText: "密码",
hintText: "您的登陆密码",
prefixIcon: Icon(Icons.lock)),
obscureText: true,
),
],
),
);
}
}
复制代码
运行效果:
监听文本变化也有两种方式:
onChanged
回调,如:TextField(
autofocus: true,
onChanged: (v) {
print("onChange: $v");
}
)
复制代码
controller
监听,如:@override
void initState() {
//监听输入改变
_unameController.addListener((){
print(_unameController.text);
});
}
复制代码
这两种方式相比,onChanged
是专门用于监听文本变化,而controller
的功能却多一些,除了能监听文本变化外,它还能够设置默认值、选择文本,
例子:
controller
:TextEditingController _selectionController = TextEditingController();
复制代码
_selectionController.text="hello world!";
_selectionController.selection=TextSelection(
baseOffset: 2,
extentOffset: _selectionController.text.length
);
复制代码
controller
:TextField(
controller: _selectionController,
)
复制代码
代码:
class _CategoryPageState extends State<CategoryPage> {
TextEditingController _selectionController = TextEditingController();
@override
Widget build(BuildContext context) {
_selectionController.text = "我好喜欢你吖!";
_selectionController.selection = TextSelection(
baseOffset: 2, extentOffset: _selectionController.text.length);
return Scaffold(
appBar: AppBar(
title: Text("输入框"),
),
body: Column(
children: <Widget>[
TextField(
controller: _selectionController,
),
],
),
);
}
}
复制代码
运行效果:
焦点能够经过FocusNode
和FocusScopeNode
来控制。
默认状况下,焦点由FocusScope
来管理,它表明焦点控制范围,能够在这个范围内能够经过FocusScopeNode
在输入框之间移动焦点、设置默认焦点等。
咱们能够经过FocusScope.of(context)
来获取Widget树中默认的FocusScopeNode
。
示例要求:
建立两个TextField
,第一个自动获取焦点,而后建立两个按钮:点击第一个按钮能够将焦点从第一个TextField挪到第二个TextField,点击第二个按钮能够关闭键盘。
代码以下:
class FocusTestRoute extends StatefulWidget {
@override
_FocusTestRouteState createState() => new _FocusTestRouteState();
}
class _FocusTestRouteState extends State<FocusTestRoute> {
FocusNode focusNode1 = new FocusNode();
FocusNode focusNode2 = new FocusNode();
FocusScopeNode focusScopeNode;
@override
Widget build(BuildContext context) {
return Padding(
padding: EdgeInsets.all(16.0),
child: Column(
children: <Widget>[
TextField(
autofocus: true,
focusNode: focusNode1,//关联focusNode1
decoration: InputDecoration(
labelText: "input1"
),
),
TextField(
focusNode: focusNode2,//关联focusNode2
decoration: InputDecoration(
labelText: "input2"
),
),
Builder(builder: (ctx) {
return Column(
children: <Widget>[
RaisedButton(
child: Text("移动焦点"),
onPressed: () {
//将焦点从第一个TextField移到第二个TextField
// 这是一种写法 FocusScope.of(context).requestFocus(focusNode2);
// 这是第二种写法
if(null == focusScopeNode){
focusScopeNode = FocusScope.of(context);
}
focusScopeNode.requestFocus(focusNode2);
},
),
RaisedButton(
child: Text("隐藏键盘"),
onPressed: () {
// 当全部编辑框都失去焦点时键盘就会收起
focusNode1.unfocus();
focusNode2.unfocus();
},
),
],
);
},
),
],
),
);
}
}
复制代码
由于我这个模拟器,他不显示键盘,因此当输入框获取焦点的时候,下面会显示一个键盘(我这个不显示)
FocusNode
继承自ChangeNotifier
,经过FocusNode
能够监听焦点的改变事件,如:
...
// 建立 focusNode
FocusNode focusNode = new FocusNode();
...
// focusNode绑定输入框
TextField(focusNode: focusNode);
...
// 监听焦点变化
focusNode.addListener((){
print(focusNode.hasFocus);
});
复制代码
得到焦点时focusNode.hasFocus
值为true
,失去焦点时为false
。
前面介绍的输入框能够单个操做,可是每每在开发中,须要多多个输入框同时进行操做,好比清空全部输入框的内容。
为此,Flutter提供了一个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
内容发生变化时会触发此回调。
Form
的子孙元素必须是FormField
类型,FormField
是一个抽象类,定义几个属性,FormState
内部经过它们来完成操做,
FormField
部分构造函数以下:
const FormField({
...
FormFieldSetter<T> onSaved, //保存回调
FormFieldValidator<T> validator, //验证回调
T initialValue, //初始值
bool autovalidate = false, //是否自动校验。
})
复制代码
为了方便使用,Flutter提供了一个TextFormField
组件,它继承自FormField
类,也是TextField
的一个包装类,因此除了FormField
定义的属性以外,它还包括TextField
的属性。
FormState
为Form
的State
类,能够经过Form.of()
或GlobalKey
得到。
咱们能够经过它来对Form
的子孙FormField
进行统一操做。
经常使用的三个方法:
FormState.validate()
:调用此方法后,会调用Form
子孙FormField
的validate
回调,若是有一个校验失败,则返回false
,全部校验失败项都会返回用户返回的错误提示。
FormState.save()
:调用此方法后,会调用Form
子孙FormField
的save
回调,用于保存表单内容。
FormState.reset()
:调用此方法后,会将子孙FormField
的内容清空。
修改上面用户登陆的示例,在提交以前校验:
代码:
import 'package:flutter/material.dart';
class CategoryPage extends StatefulWidget {
@override
_CategoryPageState createState() => _CategoryPageState();
}
class _CategoryPageState extends State<CategoryPage> {
TextEditingController _unameController = new TextEditingController();
TextEditingController _pwdController = new TextEditingController();
GlobalKey _formKey = new GlobalKey<FormState>();
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text("Form Test"),
),
body: Padding(
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: () {
//在这里不能经过此方式获取FormState,context不对
//print(Form.of(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
的指向问题。