一、 输入框
类似UITextField 与 UITextView结合体,
1.属性大概介绍总结:
controller
:编辑框的控制器,通过它可以设置/获取编辑框的内容、选择编辑内容、监听编辑文本改变事件。大多数情况下我们都需要显式提供一个controller来与文本框交互。如果没有提供controller,则TextField内部会自动创建一个。
focusNode
:用于控制TextField是否占有当前键盘的输入焦点。
InputDecoration
:用于控制TextField的外观显示,如提示文本、背景颜色、边框等。
keyboardType
:用于设置该输入框默认的键盘输入类型,类似UITextField的键盘类型,可自行测试。
style
:正在编辑的文本样式。
textAlign
: 输入框内编辑文本在水平方向的对齐方式。
autofocus
: 是否自动获取焦点,若为YES,即键盘弹出成为第一响应者。
obscureText
:输入密码用的类型,输入后变成小黑点。
maxLines
:输入框的最大行数,默认为1;如果为null
,则无行数限制。
maxLength
:代表输入框文本的最大长度,设置后输入框右下角会显示输入的文本计数。
maxLengthEnforced
决定当输入文本长度超过maxLength时是否阻止输入,为true时会阻止输入,为false时不会阻止输入但输入框会变红。
onChange
:输入框内容改变时的回调函数;注:内容改变事件也可以通过controller来监听。
onEditingComplete和onSubmitted
:这两个回调都是在输入框输入完成时触发,比如按了键盘的完成键(对号图标)或搜索键.
inputFormatters
:用于指定输入格式;当用户输入内容改变时,会根据指定的格式来校验。
enable
:如果为false
,则输入框会被禁用,禁用状态不接收输入和事件,同时显示禁用态样式(在其decoration中定义)。
cursorWidth、cursorRadius和cursorColor
:自定义输入框光标宽度、圆角和颜色的。
2.实践结果尝试
3. 核心测试代码
class _FlutterTextFieldFormState extends State<FlutterTextField> {
TextEditingController _textEditingController = TextEditingController();
// 每一个输入框都有一个FocusNode与之对应
var _focusTextFieldNode = FocusNode();
var _focusPwdFieldNode = FocusNode();
var _focusCustomFieldNode = FocusNode();
FocusScopeNode focusScopeNode;
@override
void initState() {
super.initState();
// 初始化字符串,与选中字符
_textEditingController.text = '123er';
_textEditingController.selection =
TextSelection(baseOffset: 0, extentOffset: 3);
//监听输入改变
_textEditingController.addListener(() {
print('Controller监听:${_textEditingController.text}');
});
_focusTextFieldNode.addListener((){
print('监听焦点变化 : ${_focusTextFieldNode.hasFocus}');
});
// 这里监听文本成为第一响应者,从而更新底边颜色
_focusCustomFieldNode.addListener((){
setState(() {
});
});
}
Widget _buildTextField(context) {
return Theme(
data: Theme.of(context).copyWith(
hintColor: Colors.red,
inputDecorationTheme: InputDecorationTheme(
labelStyle: TextStyle(
color: Colors.yellow
),
hintStyle: TextStyle(
color: Colors.purple,
fontSize: 20
)
)
),
child: TextField(
controller: _textEditingController,
focusNode: _focusTextFieldNode,
keyboardType: TextInputType.text,
textInputAction: TextInputAction.search,
style: TextStyle(
color: Colors.red,
fontSize: 30,
),
textAlign: TextAlign.center,
autofocus: true,
obscureText: false,
maxLines: 1,
maxLength: 10,
maxLengthEnforced: false,
onChanged: (text) {
print(text);
},
onEditingComplete: () {
print('完成后:${_textEditingController.text}');
},
onSubmitted: (text) {
print('提交点击');
_focusTextFieldNode.unfocus();
},
//List<TextInputFormatter> inputFormatters,
enabled: true,
cursorWidth: 2.0,
cursorRadius: Radius.circular(20.0),
cursorColor: Colors.cyan,
decoration: InputDecoration(
labelText: "用户名",
hintText: "用户名或邮箱",
prefixIcon: Icon(Icons.person),
icon: Icon(
Icons.directions_run,
color: Colors.red,
size: 40,
),
),
),
);
}
Widget _buidlCustomField(context){
return Container(
decoration: BoxDecoration(
border: Border(
bottom: BorderSide(
color: _focusCustomFieldNode.hasFocus ? Colors.lightBlueAccent : Colors.green[200],
width: 5.0,
)
)
),
child: TextField(
focusNode: _focusCustomFieldNode,
keyboardType: TextInputType.text,
decoration: InputDecoration(
labelText: "Email",
hintText: "电子邮件地址",
prefixIcon: Icon(Icons.email),
border: InputBorder.none //隐藏下划线
),
),
);
}
Widget _buildPwdField() {
return TextField(
focusNode: _focusPwdFieldNode,
decoration: InputDecoration(
labelText: 'password',
hintText: '输入你的密码',
prefixIcon: Icon(Icons.arrow_drop_down),
),
obscureText: true,
);
}
Widget _buildFeatherButton(context){
return Builder(builder: (ctx)
{
return Column(
children: <Widget>[
RaisedButton(
child: Text("移动焦点"),
onPressed: () {
if (null == focusScopeNode) {
focusScopeNode = FocusScope.of(context);
}
print(focusScopeNode);
focusScopeNode.requestFocus(_focusPwdFieldNode);
},
),
RaisedButton(
child: Text("隐藏键盘"),
onPressed: () {
_focusTextFieldNode.unfocus();
_focusPwdFieldNode.unfocus();
},
),
],
);
},
);
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('TextField'),
backgroundColor: Colors.orange,
),
body: Center(
child: Column(
children: <Widget>[
_buildTextField(context),
_buildPwdField(),
_buildFeatherButton(context),
_buidlCustomField(context),
],
),
),
);
}
}
4. 补充:控制焦点
- 焦点可以通过FocusNode和FocusScopeNode来控制。
- 默认情况下,焦点由FocusScope来管理,它代表焦点控制范围,可以在这个范围内可以通过FocusScopeNode在输入框之间移动焦点、设置默认焦点等。
- 通过FocusScope.of(context) 来获取widget树中默认的FocusScopeNode。
二、表单
Form widget,它可以对输入框进行分组,然后进行一些统一操作,如输入内容校验、输入框重置以及输入内容保存。
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内容发生变化时会触发此回调。
1. 与Form相关的几个类型
FormField
: Form的子孙元素必须是FormField类型,FormField是一个抽象类,定义几个属性,FormState内部通过它们来完成操作,FormField部分定义如下:
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的内容清空。
2. 测试案例运行结果
3. 核心测试代码
因为有状态变更,所以依然需要继承自State,监听并更新。
class _FlutterFromState extends State<FlutterForm> {
// TextEditingController用来监听文本
TextEditingController _accountEditController = TextEditingController();
TextEditingController _pwdEditController = TextEditingController();
// 设置globalKey,用于后面获取FormState
GlobalKey _formKey = GlobalKey<FormState>();
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('Form'),
),
body: Padding(
padding: const EdgeInsets.symmetric(vertical: 40, horizontal: 20),
child: Form(
key: _formKey,
autovalidate: true,
child: Column(
children: <Widget>[
_buildAccountTF(),
_buildPwdTF(),
_buildLoginButton(),
_buildClearButton(),
],
),
),
),
);
}
Widget _buildAccountTF() {
return TextFormField(
autofocus: true,
controller: _accountEditController,
decoration: InputDecoration(
labelText: '用户名', hintText: '请输入用户账号', icon: Icon(Icons.person)),
// 验证
validator: (v) {
return v.trim().length < 6 ? '账号不能小于6位' : null;
},
);
}
Widget _buildPwdTF() {
return TextFormField(
autofocus: false,
controller: _pwdEditController,
decoration: InputDecoration(
labelText: '密码',
hintText: '请输入密码',
icon: Icon(Icons.lock),
),
validator: (v) {
return v.trim().length > 0 ? null : '密码不能为空';
},
);
}
Widget _buildLoginButton() {
return Padding(
padding: const EdgeInsets.all(20),
child: Row(
children: <Widget>[
Expanded(
child: RaisedButton(
padding: const EdgeInsets.all(5),
child: Text(
'点我登录',
style: TextStyle(
fontSize: 20,
),
),
textColor: Colors.red,
onPressed: () {
if ((_formKey.currentState as FormState).validate()) {
print('验证通过,可以登录');
} else {
print('验证不通过');
}
}))
],
),
);
}
Widget _buildClearButton() {
return Padding(
padding: const EdgeInsets.symmetric(vertical: 10, horizontal: 20),
child: Row(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
RaisedButton(
padding: const EdgeInsets.all(5),
child: Text(
'清除数据',
style: TextStyle(fontSize: 20),
),
onPressed: () {
if (_accountEditController.text.length <= 0 &&
_pwdEditController.text.trim().length <= 0) {
print('没有数据');
return;
}
FormState s = _formKey.currentState as FormState;
s.reset();
})
],
),
);
}
}
4. 注意context
参数
context正是操作Widget所对应的Element的一个接口,由于Widget树对应的Element都是不同的,所以context也都是不同的,注意此context非彼context。