15、 Flutter Widgets 之 Form、FormField、TextFormField相关表单控件

15、 Flutter Widgets 之 Form、FormField、TextFormField相关表单控件持续创作,加速成长!这是我参与「掘金日新计划 · 10 月更文挑战」的第15天,点击查看活动详情 概述: 实际业务中,在正式向服务器提交数据前,都会对各个输入框数据进行合法性校验,但是对每一个Text

持续创作,加速成长!这是我参与「掘金日新计划 · 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');
      },
    )

运行效果:

图片.png

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的savevalidatereset等方法,一般通过如下方法设置:

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',
                   ),
                 ),
               ),
             );
           }),
         ),
       )

图片.png

构造一个登录页面:

_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('登录')
          )
 
        ],
      ),
 
    );
 
  }

运行效果:

表单登录.gif

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还有很多基础属性,如是否可以编辑,装饰器效果,等等,在实际项目中都是很常用的。

总结:

本篇主要介绍了FormFormFieldTextFormField等相关组件,以及FormStateautofocus的基本使用。 git代码地址:gitee.com/wywinstonwy…

今天的文章15、 Flutter Widgets 之 Form、FormField、TextFormField相关表单控件分享到此就结束了,感谢您的阅读。

版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 举报,一经查实,本站将立刻删除。
如需转载请保留出处:https://bianchenghao.cn/14219.html

(0)
编程小号编程小号

相关推荐

发表回复

您的电子邮箱地址不会被公开。 必填项已用*标注