持续创作,加速成长!这是我参与「掘金日新计划 · 10 月更文挑战」的第15天,点击查看活动详情
概述:
实际业务中,在正式向服务器提交数据前,都会对各个输入框数据进行合法性校验,但是对每一个TextField都分别进行校验将会是一件很麻烦的事。还有,如果用户想清除一组TextField的内容,除了一个一个清除有没有什么更好的办法呢?为此,Flutter提供了一个Form widget,它可以对输入框进行分组,然后进行一些统一操作,如输入内容校验、输入框重置以及输入内容保存。
Form的子孙元素必须是FormField类型,FormField是一个抽象类,定义几个属性,FormState内部通过它们来完成操作。
FormField
FormField是一个表单控件,此控件包含表单的状态,方便更新UI,通常情况下,我们不会直接使用FormField,而是使用TextFormField。
TextFormField
TextFormField继承自FormField,是一个输入框表单,因此TextFormField中有很多关于TextField的属性,TextFormField的基本用法:
TextFormField(
decoration: InputDecoration(
hintText: '电话号码',
),
autovalidateMode: AutovalidateMode.always,
validator: (value) {
RegExp reg = new RegExp(r'^\d{11}$');
if (!reg.hasMatch(value!)) {
return '请输入11位手机号码';
}
return null;
},
onSaved: (text){
print('$text');
},
)
运行效果:
onSaved
是一个可选参数,当Form调用FormState.save时才会回调此方法。
autovalidateModel
参数为枚举类型,disabled不自动验证,always实时验证,onUserInteraction,用户输入完成验证
enum AutovalidateMode {
/// No auto validation will occur.
disabled,
/// Used to auto-validate [Form] and [FormField] even without user interaction.
always,
/// Used to auto-validate [Form] and [FormField] only after each user
/// interaction.
onUserInteraction,
}
validator
验证函数,输入的值不匹配的时候返回的字符串显示在TextField的errorText属性位置,返回null,表示没有错误。
Form
Form组件是一个容器类控件,可以包含多个FormField表单控件,这样的好处是统一管理。
在使用Form的时候需要设置其key,通过key获取当前的FormState,然后可以调用FormState的save
、validate
、reset
等方法,一般通过如下方法设置:
final _formKey = GlobalKey<FormState>();
Form(
key: _formKey,
...
)
获取FormState并调用相关方法:
var _state = _formKey.currentState;
if(_state.validate()){
_state.save();
}
Form基本用法:
Form(
autovalidateMode: AutovalidateMode.always,
onChanged: () {
Form.of(primaryFocus!.context!)!.save();
},
child: Wrap(
children: List<Widget>.generate(5, (int index) {
return Padding(
padding: const EdgeInsets.all(8.0),
child: ConstrainedBox(
constraints: BoxConstraints.tight(const Size(300, 50)),
child: TextFormField(
onSaved: (String? value) {
print('Value for field $index saved as "$value"');
},
decoration: InputDecoration(
hintText: '电话号码$index',
),
),
),
);
}),
),
)
构造一个登录页面:
_buildLogin(){
var _account;
var _pwd;
final _formKey = GlobalKey<FormState>();
return Form(
child: Column(
children: [
TextFormField(
autovalidateMode: AutovalidateMode.always,
decoration: InputDecoration(hintText: '请输入账号'),
onSaved: (value){
_account = value;
}, validator: (value) {
RegExp reg = new RegExp(r'^.{4}$');
if (!reg.hasMatch(value!)) {
return '账号最少4个字符';
}
return null;
},
),
//---------------
TextFormField(
autovalidateMode: AutovalidateMode.always,
decoration: InputDecoration(hintText: '输入密码'),
obscureText: true,
onSaved: (value) {
_pwd = value;
},
validator: (value) {
return value!.length >= 6 ? null : '密码最少6个字符';
},
),
//登录
ElevatedButton(
onPressed: (){
var _state = Form.of(context);
if(_state!.validate()){
_state.save();
_login(_account,_pwd);
}
},
child: Text('登录')
)
],
),
);
}
_login(account,pwd){
print("账号:$account\n密码:$pwd");
}
我们希望用户在输入表单时点击返回按钮提示用户”确认退出吗?”,用法如下:
_buildLogin1(){
var _account;
var _pwd;
final _formKey = GlobalKey<FormState>();
return Form(
key: _formKey,
onWillPop: ()async{
return await showDialog(
context: context,
builder: (BuildContext context){
return AlertDialog(
title: Text('提示'),
content: Text('确认退出吗?'),
actions: [
ElevatedButton(onPressed: (){
Navigator.of(context).pop(false);
}, child: Text('取消')),
ElevatedButton(onPressed: (){
Navigator.of(context).pop(true);
}, child: Text('确定')),
],
);
});
},
child: Column(
children: [
TextFormField(
autovalidateMode: AutovalidateMode.always,
decoration: InputDecoration(hintText: '请输入账号'),
onSaved: (value){
_account = value;
}, validator: (value) {
RegExp reg = new RegExp(r'^.{4}$');
if (!reg.hasMatch(value!)) {
return '账号最少4个字符';
}
return null;
},
),
//---------------
TextFormField(
autovalidateMode: AutovalidateMode.always,
decoration: InputDecoration(hintText: '输入密码'),
obscureText: true,
onSaved: (value) {
_pwd = value;
},
validator: (value) {
return value!.length >= 6 ? null : '密码最少6个字符';
},
),
//登录
ElevatedButton(
onPressed: (){
var _state = Form.of(context);
if(_state!.validate()){
_state.save();
_login(_account,_pwd);
}
},
child: Text('登录')
)
],
),
);
}
运行效果:
onWillPop
回调决定Form
所在的路由是否可以直接返回,该回调需要返回Future<bool>
,返回false
表示当前路由不会返回;为true
,则会返回到上一个路由。此属性通常用于拦截返回按钮。
onChanged
:当子表单控件发生变化时回调。
回过头再看看TextField构造函数:
class TextFormField extends FormField<String> {
/// Creates a [FormField] that contains a [TextField].
///
/// When a [controller] is specified, [initialValue] must be null (the
/// default). If [controller] is null, then a [TextEditingController]
/// will be constructed automatically and its `text` will be initialized
/// to [initialValue] or the empty string.
///
/// For documentation about the various parameters, see the [TextField] class
/// and [new TextField], the constructor.
TextFormField({
Key? key,
this.controller,
String? initialValue,
FocusNode? focusNode,
InputDecoration? decoration = const InputDecoration(),
TextInputType? keyboardType,
TextCapitalization textCapitalization = TextCapitalization.none,
TextInputAction? textInputAction,
TextStyle? style,
StrutStyle? strutStyle,
TextDirection? textDirection,
TextAlign textAlign = TextAlign.start,
TextAlignVertical? textAlignVertical,
bool autofocus = false,
bool readOnly = false,
ToolbarOptions? toolbarOptions,
bool? showCursor,
String obscuringCharacter = '•',
bool obscureText = false,
bool autocorrect = true,
SmartDashesType? smartDashesType,
SmartQuotesType? smartQuotesType,
bool enableSuggestions = true,
@Deprecated(
'Use autovalidateMode parameter which provide more specific '
'behaviour related to auto validation. '
'This feature was deprecated after v1.19.0.',
)
bool autovalidate = false,
@Deprecated(
'Use maxLengthEnforcement parameter which provides more specific '
'behavior related to the maxLength limit. '
'This feature was deprecated after v1.25.0-5.0.pre.',
)
bool maxLengthEnforced = true,
MaxLengthEnforcement? maxLengthEnforcement,
int? maxLines = 1,
int? minLines,
bool expands = false,
int? maxLength,
ValueChanged<String>? onChanged,
GestureTapCallback? onTap,
VoidCallback? onEditingComplete,
ValueChanged<String>? onFieldSubmitted,
FormFieldSetter<String>? onSaved,
FormFieldValidator<String>? validator,
List<TextInputFormatter>? inputFormatters,
bool? enabled,
double cursorWidth = 2.0,
double? cursorHeight,
Radius? cursorRadius,
Color? cursorColor,
Brightness? keyboardAppearance,
EdgeInsets scrollPadding = const EdgeInsets.all(20.0),
bool enableInteractiveSelection = true,
TextSelectionControls? selectionControls,
InputCounterWidgetBuilder? buildCounter,
ScrollPhysics? scrollPhysics,
Iterable<String>? autofillHints,
AutovalidateMode? autovalidateMode,
ScrollController? scrollController,
String? restorationId,
bool enableIMEPersonalizedLearning = true,
}) : assert(initialValue == null || controller == null),
assert(textAlign != null),
assert(autofocus != null),
assert(readOnly != null),
assert(obscuringCharacter != null && obscuringCharacter.length == 1),
assert(obscureText != null),
assert(autocorrect != null),
assert(enableSuggestions != null),
assert(autovalidate != null),
assert(
autovalidate == false ||
autovalidate == true && autovalidateMode == null,
'autovalidate and autovalidateMode should not be used together.',
),
assert(maxLengthEnforced != null),
assert(
maxLengthEnforced || maxLengthEnforcement == null,
'maxLengthEnforced is deprecated, use only maxLengthEnforcement',
),
assert(scrollPadding != null),
assert(maxLines == null || maxLines > 0),
assert(minLines == null || minLines > 0),
assert(
(maxLines == null) || (minLines == null) || (maxLines >= minLines),
"minLines can't be greater than maxLines",
),
assert(expands != null),
assert(
!expands || (maxLines == null && minLines == null),
'minLines and maxLines must be null when expands is true.',
),
assert(!obscureText || maxLines == 1, 'Obscured fields cannot be multiline.'),
assert(maxLength == null || maxLength == TextField.noMaxLength || maxLength > 0),
assert(enableInteractiveSelection != null),
assert(enableIMEPersonalizedLearning != null),
super(
key: key,
restorationId: restorationId,
initialValue: controller != null ? controller.text : (initialValue ?? ''),
onSaved: onSaved,
validator: validator,
enabled: enabled ?? decoration?.enabled ?? true,
autovalidateMode: autovalidate
? AutovalidateMode.always
: (autovalidateMode ?? AutovalidateMode.disabled),
builder: (FormFieldState<String> field) {
final _TextFormFieldState state = field as _TextFormFieldState;
final InputDecoration effectiveDecoration = (decoration ?? const InputDecoration())
.applyDefaults(Theme.of(field.context).inputDecorationTheme);
void onChangedHandler(String value) {
field.didChange(value);
if (onChanged != null) {
onChanged(value);
}
}
return UnmanagedRestorationScope(
bucket: field.bucket,
child: TextField(
restorationId: restorationId,
controller: state._effectiveController,
focusNode: focusNode,
decoration: effectiveDecoration.copyWith(errorText: field.errorText),
keyboardType: keyboardType,
textInputAction: textInputAction,
style: style,
strutStyle: strutStyle,
textAlign: textAlign,
textAlignVertical: textAlignVertical,
textDirection: textDirection,
textCapitalization: textCapitalization,
autofocus: autofocus,
toolbarOptions: toolbarOptions,
readOnly: readOnly,
showCursor: showCursor,
obscuringCharacter: obscuringCharacter,
obscureText: obscureText,
autocorrect: autocorrect,
smartDashesType: smartDashesType ?? (obscureText ? SmartDashesType.disabled : SmartDashesType.enabled),
smartQuotesType: smartQuotesType ?? (obscureText ? SmartQuotesType.disabled : SmartQuotesType.enabled),
enableSuggestions: enableSuggestions,
maxLengthEnforced: maxLengthEnforced,
maxLengthEnforcement: maxLengthEnforcement,
maxLines: maxLines,
minLines: minLines,
expands: expands,
maxLength: maxLength,
onChanged: onChangedHandler,
onTap: onTap,
onEditingComplete: onEditingComplete,
onSubmitted: onFieldSubmitted,
inputFormatters: inputFormatters,
enabled: enabled ?? decoration?.enabled ?? true,
cursorWidth: cursorWidth,
cursorHeight: cursorHeight,
cursorRadius: cursorRadius,
cursorColor: cursorColor,
scrollPadding: scrollPadding,
scrollPhysics: scrollPhysics,
keyboardAppearance: keyboardAppearance,
enableInteractiveSelection: enableInteractiveSelection,
selectionControls: selectionControls,
buildCounter: buildCounter,
autofillHints: autofillHints,
scrollController: scrollController,
enableIMEPersonalizedLearning: enableIMEPersonalizedLearning,
),
);
},
);
/// Controls the text being edited.
///
/// If null, this widget will create its own [TextEditingController] and
/// initialize its [TextEditingController.text] with [initialValue].
final TextEditingController? controller;
@override
FormFieldState<String> createState() => _TextFormFieldState();
}
表单聚焦 FocusNode
TextFormField
支持 autofocus
属性,在页面进来的时候,为 true 的输入框,会自动聚焦
TextFormField(
autofocus: true,
decoration: InputDecoration(
hintText: '用户名',
),
)
动态指定输入框聚焦
除了 autofocus
属性之外,TextFormField
还支持传入一个 focusNode
,类型是 FocusNode
,通过传入之后,能够实现这个基本操作
首先我们需要创建一个 FocusNode:
FocusNode _focusNode = FocusNode();
TextFormField(
focusNode: _focusNode,
decoration: InputDecoration(
hintText: '电话号码',
),
)
通过 FocusScope
使 FocusNode 生效
Positioned(
child: InkWell(
child: Icon(Icons.edit),
onTap: () => FocusScope.of(context).requestFocus(_focusNode)),
bottom: 30,
right: 40,
)
除此之外TextFormField还有很多基础属性,如是否可以编辑,装饰器效果,等等,在实际项目中都是很常用的。
总结:
本篇主要介绍了Form
,FormField
,TextFormField
等相关组件,以及FormState
,autofocus
的基本使用。 git代码地址:gitee.com/wywinstonwy…
今天的文章15、 Flutter Widgets 之 Form、FormField、TextFormField相关表单控件分享到此就结束了,感谢您的阅读。
版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 举报,一经查实,本站将立刻删除。
如需转载请保留出处:https://bianchenghao.cn/14219.html