【Flutter】Form表单,狗都不用到真香 & 如何自定义Form表单子控件

Form表单的使用、封装、与自定义

前言

不管是一个应用还是一个网页,一个表单提交都是少不了,登录、注册、完善消息等常用的功能都离不开表单的提交。

在之前我们开发 Android 应用都是直接堆布局就能实现表单,校验逻辑一一对应一个一个写,没什么问题,甚至我开发过前端网页也是这么做的,也没什么问题,既然如此,我为什么要用 Form 这个鬼东西。

看似我们又需要学习一个新的控件和一些规则,其实它真的很简单。它简化了校验,错误展示,重置,等一系列操作,让表单更加的方便,统一化的操作真的用了之后真香。

Form表单其实不用学,特别简单,就是对一系列的输入进行统一的操作,如输入内容校验、输入框重置以及输入内容保存等。

Form的子孙是 FormField 类型,FormState 为 Form 的 State 类,可以通过 Form.of() 或 GlobalKey 获得,关于 Form 的一些用法和常用方法都是一些基本的固定用法,一点都不难。

下面就一起学习如何使用 Form ,怎么封装 Form 内的输入框,怎么自定义其他的子 Form 表单。

一、如何简单使用

在没有 Form 表单之前,我们都是直接用 TextField ,我们对 TextField 做一些封装,直接使用,设置 TextEditingController FocusNode 然后通过操作输入框和焦点,来控制校验,通过设置错误文本并刷新布局来展示错误信息。

dart 复制代码
  Map<String, Map<String, dynamic>> formData = {
    'phone': {
      'value': '',
      'controller': TextEditingController(),
      'focusNode': FocusNode(),
      'hintText': '请输入手机号码'.tr,
      'obsecure': false,
    },
    'code': {
      'value': '',
      'controller': TextEditingController(),
      'focusNode': FocusNode(),
      'hintText': '请输入验证码'.tr,
      'obsecure': false,
    },
  };
less 复制代码
           //电话号码
                    _buildInputLayout(
                      "phone",
                      Assets.authLoginPhoneIcon,
                      leftIconWidth: 13.5,
                      leftIconHeight: 16,
                      marginTop: 20,
                      textInputType: TextInputType.phone,
                      textInputAction: TextInputAction.next,
                      errorText: controller.mobilePhoneErrorText,
                      onSubmit: (formKey, value) {
                        state.formData[formKey]!['focusNode'].unfocus();
                        FocusScope.of(context).requestFocus(state.formData['code']!['focusNode']);
                      },
                    ),

                    //验证码
                    _buildInputLayout(
                      "code",
                      Assets.authLoginVerifyIcon,
                      leftIconWidth: 13.5,
                      leftIconHeight: 15,
                      marginTop: 10,
                      paddingRight: 15,
                      textInputAction: TextInputAction.next,
                      errorText: controller.codeErrorText,
                      showRightIcon: true,
                      rightWidget: MyTextView(
                        controller.isCounting ? controller.countdownTime.toString() + " s" : '获取验证码'.tr,
                        textAlign: TextAlign.center,
                        textColor: controller.isCounting ? ColorConstants.gray99 : ColorConstants.appBlue,
                        fontSize: 14,
                        paddingRight: 3,
                        isFontMedium: true,
                        onClick: controller.isCounting ? null : () => controller.showVerifyCodedDialog(),
                      ).paddingOnly(top: 15, bottom: 15),
                      onSubmit: (formKey, value) {
                        state.formData[formKey]!['focusNode'].unfocus();
                        controller.doChangePhone();
                      },
                    ),

内部的 TextField 的封装之前有贴过代码,这里不重复。

我们需要手动的通过 TextEditingController 拿到值, 通过 FocusNode 设置这个 Form 表单内部的焦点变化到下一步。

ini 复制代码
  /// 执行手机号码的绑定
  void doChangePhone() {
    mobilePhoneErrorText = null;
    codeErrorText = null;
    update();

    var phoneController = state.formData['phone']!['controller'];
    var codeController = state.formData['code']!['controller'];

    phone = phoneController.text;
    code = codeController.text;

    Log.d('phone:$phone code:$code');

    if (Utils.isEmpty(phone)) {
      mobilePhoneErrorText = "电话号码不能为空";
      update();
    } else if (Utils.isEmpty(code)) {
      codeErrorText = "验证码不能为空";
      update();
    }  else {
      _requestForgetPsd();
    }
  }

// =========================== 焦点控制 ===========================

  FocusNode? _phoneFocusNode;
  FocusNode? _codeFocusNode;

  void _onPhoneFocusChange() {
    if (_phoneFocusNode?.hasFocus == true) {
      mobilePhoneErrorText = null;
      update();
    }
  }

  void _onCodeFocusChange() {
    if (_codeFocusNode?.hasFocus == true) {
      codeErrorText = null;
      update();
    }
  }


  @override
  void onInit() {
    super.onInit();
    _phoneFocusNode = state.formData['phone']!['focusNode'];
    _codeFocusNode = state.formData['code']!['focusNode'];
  }

  @override
  void onReady() {
    super.onReady();
    _phoneFocusNode?.addListener(_onPhoneFocusChange);
    _codeFocusNode?.addListener(_onCodeFocusChange);
  }

  @override
  void onClose() {
    super.onClose();
    _phoneFocusNode?.removeListener(_onPhoneFocusChange);
    _codeFocusNode?.removeListener(_onCodeFocusChange);
    _phoneFocusNode = null;
    _codeFocusNode = null;
  }

使用 Form 表单之前的效果:

使用 Form 表单之后,我们就可以不需要这么多控制器,焦点控制的逻辑,我们就能使用 Form 表单比较简单的实现:

less 复制代码
  return Form(
            key: _formKey, // 关联 GlobalKey
            child: Column(
              children: [
                TextFormField(
                  // 设置表单字段的验证规则
                  validator: (value) {
                    if (value?.isEmpty == true) {
                      return '请输入姓名';
                    }
                    return null;
                  },
                  onChanged: (value) {
                    setState(() {
                      _name = value;
                    });
                  },
                  decoration: const InputDecoration(
                    labelText: '姓名',
                  ),
                ),

                // _buildInputLayout(
                //   "name",
                //   Assets.authLoginPhoneIcon,
                //   leftIconWidth: 13.5,
                //   leftIconHeight: 16,
                //   marginTop: 20,
                //   textInputType: TextInputType.phone,
                //   textInputAction: TextInputAction.next,
                //   onSaved: (value) {
                //     state.formData['name']!['value'] = value;
                //   },
                // ),

                TextFormField(
                  // 设置表单字段的验证规则
                  validator: (value) {
                    if (value?.isEmpty == true) {
                      return '请输入邮箱';
                    }
                    return null;
                  },
                  onChanged: (value) {
                    setState(() {
                      _email = value;
                    });
                  },
                  decoration: const InputDecoration(
                    labelText: '邮箱',
                  ),
                ),

                // _buildInputLayout(
                //   "email",
                //   Assets.authLoginPasswordIcon,
                //   leftIconWidth: 13.5,
                //   leftIconHeight: 16,
                //   marginTop: 20,
                //   textInputType: TextInputType.phone,
                //   textInputAction: TextInputAction.next,
                //   onSaved: (value) {
                //     state.formData['email']!['value'] = value;
                //   },
                // ),

                SizedBox(height: 16.0),

                ElevatedButton(
                  onPressed: () {
                    // 验证表单字段
                    if (_formKey.currentState?.validate() == true) {
                      //可以手动的设置错误文本
                      state.formData['name']!['errorText'] = null;
                      state.formData['email']!['errorText'] = null;
                      controller.update();

                      _formKey.currentState?.save(); //调用保存
                      // 表单验证通过,可以提交表单
                      _submitForm();
                    } else {
                      Log.e("校验不通过");
                    }
                  },
                  child: const Text('提交'),
                ),

                ElevatedButton(
                  onPressed: () {
                    setState(() {
                      _selectedOption = null;
                    });
                    _formKey.currentState?.reset(); //重置
                  },
                  child: const Text('重置'),
                ),
              ],
            ),
          );

          
  void _submitForm() {
    // 在这里执行表单提交的逻辑,例如发送网络请求等
    print("当前的表单数据为:name:${state.formData['name']!['value']}  email:${state.formData['email']!['value']}");
  }

我们就能很简单得到实现 Form 表单,它的优势就是集中的快速校验,展示错误信息,重置等操作。

二、如何封装表单

是的 Form 的子控件想要实现快速校验,展示错误信息,重置,保存等操作,就需要让其子控件集成自 FormField 对象,而 Flutter 自带几个 FormField 的实现对象

我们关注的重点就是输入框,我使用 TextField 习惯了,使用 Form 中的子对象 TextFormField 有什么区别?

其实没什么区别,只是多了一些 validator,onSaved等给 Form 父布局调用的一些方法。在其他的用法上其实都是一样的,甚至我们都能简化一些 TextFormField 的封装:

kotlin 复制代码
/*
 *  Form表单内部的输入框的封装
 */
class MyTextFormField extends StatelessWidget {
  String formKey;
  String value;
  bool? enabled;
  TextInputType inputType;
  String? labelText;
  TextStyle? labelStyle;
  String? errorText;
  double cursorWidth;
  Color? cursorColor;
  String? hintText;
  String? initialValue;
  TextStyle? hintStyle;
  TextStyle? style;
  bool? autofocus;
  int? maxLines = 1;
  InputBorder? border;
  BoxBorder? boxBorder;
  bool? showLeftIcon;
  Widget? leftWidget;
  bool? showRightIcon;
  Widget? rightWidget;
  bool? showDivider;
  Color? dividerColor;
  bool obscureText;
  double height;
  Color? fillBackgroundColor;
  double? fillCornerRadius;
  EdgeInsetsGeometry padding;
  EdgeInsetsGeometry margin;
  InputDecoration? decoration;
  TextInputAction textInputAction = TextInputAction.done;
  Function? onChanged;
  Function? onSubmit;
  String? Function(String? value)? onSaved; //Form表单的保存
  String? Function(String? value)? validator; //Form表单的校验
  final ClickType changeActionType; //默认没有点击类型
  final int changeActionMilliseconds; //点击类型的时间戳(毫秒)
  final ClickType submitActionType; //默认没有点击类型
  final int submitActionMilliseconds; //点击类型的时间戳(毫秒)

  MyTextFormField(
    this.formKey,
    this.value, {
    Key? key,
    this.enabled = true, //是否可用
    this.inputType = TextInputType.text, //输入类型
    this.initialValue, //初始化文本
    this.labelText,
    this.labelStyle,
    this.errorText, //错误的文本
    this.cursorWidth = 2.0, // 光标宽度
    this.cursorColor = ColorConstants.appBlue, // 光标颜色
    this.hintText, //提示文本
    this.hintStyle, //提示文本样式
    this.style, //默认的文本样式
    this.autofocus = false, // 自动聚焦
    this.maxLines = 1, //最多行数,高度与行数同步
    this.border = InputBorder.none, //TextFiled的边框
    this.boxBorder, // 外层Container的边框
    this.showLeftIcon = false, //是否展示左侧的布局
    this.leftWidget, //左侧的布局
    this.showRightIcon = false, //是否展示右侧的布局
    this.rightWidget, //右侧的布局
    this.showDivider = true, // 是否显示下分割线
    this.dividerColor = const Color.fromARGB(255, 212, 212, 212), // 下分割线颜色
    this.obscureText = false, //是否隐藏文本,即显示密码类型
    this.height = 50.0,
    this.fillBackgroundColor, //整体的背景颜色
    this.fillCornerRadius, //整体的背景颜色圆角
    this.padding = EdgeInsets.zero, //整体布局的Padding
    this.margin = EdgeInsets.zero, //整体布局的Margin
    this.decoration, //自定义装饰
    this.textInputAction = TextInputAction.done, //默认的行为是Done(完成)
    this.validator, //Form验证
    this.onSaved, //Form保存
    this.onChanged, //输入改变回调
    this.onSubmit, //完成行为的回调(默认行为是Done完成)
    this.changeActionType = ClickType.none, //默认没有点击类型
    this.changeActionMilliseconds = 500, //回调类型的时间戳(毫秒)
    this.submitActionType = ClickType.none, //默认没有点击类型
    this.submitActionMilliseconds = 500, //回调类型的时间戳(毫秒)
  }) : super(key: key);

  @override
  Widget build(BuildContext context) {
    //抽取的改变的回调
    changeAction(value) {
      onChanged?.call(formKey, value);
    }

    //抽取的提交的回调
    submitAction(value) {
      onSubmit?.call(formKey, value);
    }

    return Container(
      margin: margin,
      decoration: BoxDecoration(
        color: fillBackgroundColor ?? Colors.transparent,
        borderRadius: BorderRadius.all(Radius.circular(fillCornerRadius ?? 0)),
        border: boxBorder,
      ),
      padding: padding,
      child: ConstrainedBox(
        constraints: BoxConstraints(minHeight: height),
        child: Column(
          mainAxisAlignment: maxLines == null ? MainAxisAlignment.start : MainAxisAlignment.center,
          children: [
            TextFormField(
              enabled: enabled,
              style: style,
              maxLines: maxLines,
              keyboardType: inputType,
              obscureText: obscureText,
              cursorWidth: cursorWidth,
              cursorColor: DarkThemeUtil.multiColors(cursorColor, darkColor: ColorConstants.white),
              autofocus: autofocus!,
              validator: validator,
              decoration: decoration ??
                  InputDecoration(
                    hintText: hintText,
                    hintStyle: hintStyle,
                    icon: showLeftIcon == true ? leftWidget : null,
                    border: border,
                    suffixIcon: showRightIcon == true ? rightWidget : null,
                    labelText: labelText,
                    errorText: errorText,
                    errorStyle: const TextStyle(color: Colors.red, fontSize: 11.5),
                    errorBorder: const OutlineInputBorder(
                      borderSide: BorderSide(color: Colors.red),
                    ),
                  ),
              onChanged: changeActionType == ClickType.debounce
                  ? debounce(changeAction, changeActionMilliseconds)
                  : changeActionType == ClickType.throttle
                      ? throttle(changeAction, changeActionMilliseconds)
                      : changeAction,
              onFieldSubmitted: submitActionType == ClickType.debounce
                  ? debounce(submitAction, submitActionMilliseconds)
                  : submitActionType == ClickType.throttle
                      ? throttle(submitAction, submitActionMilliseconds)
                      : submitAction,
              onSaved: onSaved,
              textInputAction: textInputAction,
            ),
            showDivider == true
                ? Divider(
                    height: 0.5,
                    color: dividerColor!,
                  ).marginOnly(top: errorText == null ? 0 : 10)
                : const SizedBox.shrink(),
          ],
        ),
      ),
    );
  }

  //带参数的函数防抖,由于参数不固定就没有用过扩展,直接用方法包裹
  void Function(String value) debounce(void Function(String value) callback, [int milliseconds = 500]) {
    Timer? _debounceTimer;
    return (value) {
      if (_debounceTimer?.isActive ?? false) _debounceTimer?.cancel();
      _debounceTimer = Timer(Duration(milliseconds: milliseconds), () {
        callback(value);
      });
    };
  }

  //带参数的函数节流,由于参数不固定就没有用过扩展,直接用方法包裹
  void Function(String value) throttle(void Function(String value) callback, [int milliseconds = 500]) {
    bool _isAllowed = true;
    Timer? _throttleTimer;
    return (value) {
      if (!_isAllowed) return;
      _isAllowed = false;
      callback(value);
      _throttleTimer?.cancel();
      _throttleTimer = Timer(Duration(milliseconds: milliseconds), () {
        _isAllowed = true;
      });
    };
  }
}

使用:

less 复制代码
          return Form(
            key: _formKey, // 关联 GlobalKey
            child: Column(
              children: [

                _buildInputLayout(
                  "name",
                  Assets.authLoginPhoneIcon,
                  leftIconWidth: 13.5,
                  leftIconHeight: 16,
                  marginTop: 20,
                  textInputType: TextInputType.phone,
                  textInputAction: TextInputAction.next,
                  onSaved: (value) {
                    state.formData['name']!['value'] = value;
                  },
                ),
                
                _buildInputLayout(
                  "email",
                  Assets.authLoginPasswordIcon,
                  leftIconWidth: 13.5,
                  leftIconHeight: 16,
                  marginTop: 20,
                  textInputType: TextInputType.phone,
                  textInputAction: TextInputAction.next,
                  onSaved: (value) {
                    state.formData['email']!['value'] = value;
                  },
                ),

                SizedBox(height: 16.0),

                ElevatedButton(
                  onPressed: () {
                    // 验证表单字段
                    if (_formKey.currentState?.validate() == true) {
                      //可以手动的设置错误文本
                      state.formData['name']!['errorText'] = null;
                      state.formData['email']!['errorText'] = null;
                      controller.update();

                      _formKey.currentState?.save(); //调用保存
                      // 表单验证通过,可以提交表单
                      _submitForm();
                    } else {
                      Log.e("校验不通过");
                    }
                  },
                  child: const Text('提交'),
                ),

                ElevatedButton(
                  onPressed: () {
                    setState(() {
                      _selectedOption = null;
                    });
                    _formKey.currentState?.reset(); //重置
                  },
                  child: const Text('重置'),
                ),
              ],
            ),
          );

 Widget _buildInputLayout(
    String key,
    String leftIconRes, {
    double leftIconWidth = 0,
    double leftIconHeight = 0,
    double marginTop = 23,
    double paddingRight = 18,
    bool? showRightIcon = false, //是否展示右侧的布局
    Widget? rightWidget, //右侧的布局
    TextInputType textInputType = TextInputType.text,
    String? errorText,
    TextInputAction textInputAction = TextInputAction.done,
    String? Function(String? value)? validator, //自定义Form验证
    String? Function(String? value)? onSaved, //Form的保存
    Function? onSubmit,
  }) {
    return IgnoreKeyboardDismiss(
      child: MyTextFormField(
        key,
        state.formData[key]!['value'],
        hintText: state.formData[key]!['hintText'],
        hintStyle: const TextStyle(
          fontSize: 14.0,
          fontWeight: FontWeight.w500,
        ),
        margin: EdgeInsets.only(left: 20, right: 20, top: marginTop),
        showDivider: false,
        fillBackgroundColor: DarkThemeUtil.multiColors(ColorConstants.white, darkColor: ColorConstants.darkBlackItem),
        fillCornerRadius: 5,
        padding: EdgeInsets.only(left: 16, right: paddingRight, top: 2.5, bottom: 2.5),
        height: 50,
        style: TextStyle(
          color: DarkThemeUtil.multiColors(ColorConstants.tabTextBlack, darkColor: ColorConstants.white),
          fontSize: 14.0,
          fontWeight: FontWeight.w500,
        ),
        inputType: textInputType,
        textInputAction: textInputAction,
        onSubmit: onSubmit,
        validator: validator ?? state.formData[key]!['validator'],
        onSaved: onSaved,
        cursorColor: ColorConstants.tabTextBlack,
        obscureText: state.formData[key]!['obsecure'],
        errorText: errorText ?? state.formData[key]!['errorText'],
        showLeftIcon: true,
        showRightIcon: showRightIcon,
        rightWidget: rightWidget,
        leftWidget: Row(
          children: [
            MyAssetImage(leftIconRes, width: leftIconWidth, height: leftIconHeight),
            const Spacer(),
            Container(
              color: ColorConstants.graye5,
              width: 1,
              height: 15,
            )
          ],
        ).constrained(width: 30),
      ),
    );
  }

和之前一样的封装,UI效果和之前是一样的效果,只是我们不再需要一些 Contriller FocusNode 等控制逻辑了,个人感觉会更方便了。

此时我们的 FormFiledState 只需要设置一些简单的表单数据即可。

kotlin 复制代码
  Map<String, Map<String, dynamic>> formData = {
    'name': {
      'value': '',
      'hintText': '请输入姓名',
      'errorText': null,
      'obsecure': false,
      'validator': (value) {
        if (value.isEmpty) {
          return '请输入姓名';
        }
        return null;
      },
    },
    'email': {
      'value': '',
      'hintText': '请输入邮箱',
      'errorText': null,
      'obsecure': false,
      'validator': (value) {
        if (value.isEmpty) {
          return '请输入邮箱';
        }
        return null;
      },
    },
  };

那么除了默认的,常见的输入框控件,Form表单还有其他的控件怎么办?

三、如何自定义子控件

除了最常见的表单输入框还有一些常见的下拉选控件,现在 Flutter 也支持下拉选控件 DropdownButtonFormField 。

它的用法他也是比较类似:

less 复制代码
                Container(
                  margin: const EdgeInsets.only(left: 20, right: 20, top: 23),
                  decoration: BoxDecoration(
                    color: DarkThemeUtil.multiColors(ColorConstants.white, darkColor: ColorConstants.darkBlackItem),
                    borderRadius: const BorderRadius.all(Radius.circular(5)),
                    border: Border.all(color: ColorConstants.secondaryAppColor, width: 0.5),
                  ),
                  padding: const EdgeInsets.only(left: 16, right: 18, top: 2.5, bottom: 2.5),
                  child: DropdownButtonFormField<String>(
                    decoration: const InputDecoration(
                      hintText: "请选择来源",
                      hintStyle: TextStyle(
                        fontSize: 14.0,
                        fontWeight: FontWeight.w500,
                      ),
                      //左侧图片
                      icon: null,
                      border: InputBorder.none,
                      //右图片
                      suffixIcon: null,
                      //框内,文本上面的提示文本
                      labelText: null,
                      errorText: null,
                      //错误信息
                      errorStyle: TextStyle(color: Colors.red, fontSize: 14),
                    ),
                    value: _selectedOption,
                    style: TextStyle(
                      color: DarkThemeUtil.multiColors(ColorConstants.tabTextBlack, darkColor: ColorConstants.white),
                      fontSize: 15.0,
                      fontWeight: FontWeight.w500,
                    ),
                    items: resource.map((String option) {
                      return DropdownMenuItem<String>(
                        value: option,
                        child: Text(option), //如果想要图片加文本,在这里修改布局即可
                      );
                    }).toList(),
                    onChanged: (value) {},
                    onSaved: (value) {
                      setState(() {
                        _selectedOption = value;
                      });
                    },
                    validator: (value) {
                      if (value == null) {
                        return 'Please select an option';
                      }
                      return null;
                    },
                  ),
                ),

我们还是用同样的样式来修饰这个下拉选,效果为:

那么如果我这是一个很复杂的长表单呢?还想要一些其他的控件呢?比如 CheckBox,RadioButton,Switch,Image 等控件呢?我都想让他们自动校验并且展示对应的错误信息提示呢?

没关系,我们可以自定义 FormField 。

比如我们定义一个 CheckBoxFormField ,一般我只需要定义 onSaved,validator,错误文本的显示逻辑,如下:

php 复制代码
class CheckBoxFormField extends FormField<bool> {
  CheckBoxFormField({
    FormFieldSetter<bool>? onSaved,
    FormFieldValidator<bool>? validator,
    bool? initialValue = false,
    bool? autovalidate = false,
    required Widget title,
  }) : super(
          onSaved: onSaved,
          validator: validator,
          initialValue: initialValue ?? false,
          autovalidateMode: autovalidate! ? AutovalidateMode.always : AutovalidateMode.disabled,
          builder: (FormFieldState<bool> field) {
            return Column(
              crossAxisAlignment: CrossAxisAlignment.start,
              children: [
                Row(
                  mainAxisAlignment: MainAxisAlignment.start,
                  crossAxisAlignment: CrossAxisAlignment.center,
                  children: [
                    Checkbox(
                      value: field.value,
                      onChanged: field.didChange,
                    ),
                    title,
                  ],
                ),
                if (field.hasError)  //自定义错误信息
                  Text(
                    field.errorText!,
                    style: const TextStyle(color: Colors.red, fontSize: 14),
                  ).marginOnly(left: 15),
              ],
            );
          },
        );
}

通过一个简单的定义,我们就能在 Form 中使用了,效果如下:

那么问题来了,如果是其他的控件想要在 Form 中使用,应该如何继承封装,我相信现在你应该懂了吧。

这个非得你自己去实现不可呀,因为我不能把你要用的每一个控件都给封装一遍吧,并且每一个项目的错误展示也不一致,还是需要你自行实现哦。

总结

之前我写 Flutter 的表单的时候也是一个输入框一个输入框,一个下拉选一个下拉选,一个控件一个控件的堆叠,写一大堆的焦点控制,一大堆的输入框控制器等逻辑。如果是下拉选,各种控制器与数据切换逻辑。

虽然都可以自己细节化的控制,但是真的没必要,用了 Form 之后真的清爽很多,所以说用了之后真香。

使用 Form 表单内的输入框,可以带来以下好处:

  1. 提高用户体验:自动校验可以帮助用户在输入时快速发现错误,并提供即时反馈,减少用户提交后的不必要等待和修正操作。自动保存可以防止用户数据丢失,让用户无需担心意外关闭或刷新页面而导致输入信息的丢失。

  2. 减少用户工作量:自动校验和自动显示下一步焦点可以帮助用户快速填写表单,减少冗余的操作。例如,当用户在一个字段中输入合法数据后,自动将焦点移动到下一个字段,使用户无需手动点击下一个输入框。

  3. 错误预防和纠正:自动校验可以在用户提交之前捕捉到错误,帮助用户避免提交无效或不完整的数据。同时,自动显示错误并刷新UI可以直接在界面上显示错误信息,引导用户进行必要的更正。

  4. 数据一致性和有效性:通过自动校验,可以确保用户输入的数据符合特定的规则和格式要求,从而提高数据的一致性和有效性。这有助于后续数据处理和分析的准确性。

  5. 提高工作效率:自动校验和自动保存功能可以减少用户的重复劳动,提高工作效率。用户不需要手动检查和保存数据,整个过程更加流畅和高效。

总之,Form 可以自动校验、自动保存、自动显示错误、自动显示下一步焦点等功能,可以提高用户体验、减少用户工作量、预防和纠正错误、保证数据一致性和有效性,以及提高工作效率。

如果你也没有用过 Form 那么我推荐你尝试一下哦。

Ok,那么本期内容就到这里,如讲的不到位或错漏的地方,希望同学们可以评论区指出,如果有更多更好更方便的方式也欢迎大家评论区交流。

本文的代码已经全部贴出,部分没贴出的代码可以在前文中找到,也可以到我的 Flutter Demo 查看源码【传送门】

如果感觉本文对你有一点点的启发,还望你能点赞支持一下,你的支持是我最大的动力啦!

Ok,这一期就此完结。

相关推荐
Ranye1232 小时前
从 JS 到 Dart:语法基础
javascript·flutter·dart
我要最优解9 小时前
关于在mac中配置Java系统环境变量
java·flutter·macos
江上清风山间明月2 天前
Flutter开发的应用页面非常多时如何高效管理路由
android·flutter·路由·页面管理·routes·ongenerateroute
Zsnoin能2 天前
flutter国际化、主题配置、视频播放器UI、扫码功能、水波纹问题
flutter
早起的年轻人2 天前
Flutter CupertinoNavigationBar iOS 风格导航栏的组件
flutter·ios
HappyAcmen2 天前
关于Flutter前端面试题及其答案解析
前端·flutter
coooliang3 天前
Flutter 中的单例模式
javascript·flutter·单例模式
coooliang3 天前
Flutter项目中设置安卓启动页
android·flutter
JIngles1233 天前
flutter将utf-8编码的字节序列转换为中英文字符串
java·javascript·flutter
B.-3 天前
在 Flutter 中实现文件读写
开发语言·学习·flutter·android studio·xcode