flutter:表单:FormField和TextFormField

Form

Form 是什么

class Form extends StatefulWidget {
  /// Creates a container for form fields.
  const Form({
    super.key,
    required this.child,
    this.canPop,
    @Deprecated(
      'Use onPopInvokedWithResult instead. '
      'This feature was deprecated after v3.22.0-12.0.pre.',
    )
    this.onPopInvoked,
    this.onPopInvokedWithResult,
    @Deprecated(
      'Use canPop and/or onPopInvokedWithResult instead. '
      'This feature was deprecated after v3.12.0-1.0.pre.',
    )
    this.onWillPop,
    this.onChanged,
    AutovalidateMode? autovalidateMode,
  }) : autovalidateMode = autovalidateMode ?? AutovalidateMode.disabled,
       assert(
         onPopInvokedWithResult == null || onPopInvoked == null,
         'onPopInvoked is deprecated; use onPopInvokedWithResult',
       ),
       assert(
         ((onPopInvokedWithResult ?? onPopInvoked ?? canPop) == null) || onWillPop == null,
         'onWillPop is deprecated; use canPop and/or onPopInvokedWithResult.',
       );
  @override
  FormState createState() => FormState();
}

Form 是一个 容器 Widget,用来管理一组表单字段(FormField)。它的作用主要是:

  • 统一管理表单状态(比如校验、保存)。
  • 提供一个全局的 FormState,方便统一调用 validate() / save() / reset()

Form 有什么作用

  • 集中校验FormState.validate() 会逐个调用所有后代 FormFieldvalidator

  • 集中保存FormState.save() 会逐个触发所有 FormFieldonSaved

  • 集中重置FormState.reset() 把每个字段恢复到它们的 initialValue

  • 统一监听变化:任意字段变化时触发 Form.onChanged,便于启用/禁用提交按钮等。

  • 拦截返回(Pop):通过 canPop / onPopInvokedWithResult(替代旧的 onWillPop)控制页面是否可返回,例如表单未保存时给出二次确认

FormState

是什么

class FormState extends State<Form> {
  // ......
}

FormStateForm widget 对应的 状态类(继承自 State<Form>)。

它保存并管理 所有子 FormField 的状态引用。也就是**FormState 内部会收集并持有每个子 FormFieldState 的引用**,这样才能统一操作(校验、保存、重置)所有表单字段。

Form 是一个容器,它里面会放多个 FormField(比如 TextFormField、自定义 CheckboxFormField 等)。

每个 FormField 自己都有一个 FormFieldState<T>(状态对象,保存它的值、错误提示等)。

当我们调用 FormState 的方法(比如 validate()save())时,FormState 需要 知道自己有哪些子字段,才能把操作分发下去。所以:FormState 内部维护了一个列表(或集合),里面存放所有子 FormFieldState 的引用。这样一来,FormState.validate() 就能 循环调用

for (var field in _formFields) {
  field.validate();
}

同理 save()reset() 也是循环调用。

获取方式

通过 GlobalKey<FormState>(最常用)

final _formKey = GlobalKey<FormState>();

Form(
  key: _formKey,
  child: ...
);

// 使用
_formKey.currentState!.validate();
_formKey.currentState!.save();

final _formKey = GlobalKey<FormState>();

  • _formKey 是一个 GlobalKey<FormState> 对象。
  • 它是一个标识符,类型参数 <FormState> 限定了它只能绑定 FormState 实例。

Form(key: _formKey, child: ...)

  • Form 被插入 widget 树时:
    • Flutter 调用 Form.createState(),创建一个新的 FormState 实例
    • 框架会把这个 FormState_formKey 关联起来。

_formKey.currentState

  • 返回与该 key 绑定的 FormState 实例。这就是 Form widget 的状态对象。

通过 Form.of(context)

通过 Form.of(context)

final formState = Form.of(context);
  • Form.of(BuildContext context) 是一个 静态方法

    static FormState of(BuildContext context) {
      final FormState? formState = maybeOf(context);
      assert(() {
        if (formState == null) {
          throw FlutterError(
            'Form.of() was called with a context that does not contain a Form widget.\n'
            'No Form widget ancestor could be found starting from the context that '
            'was passed to Form.of(). This can happen because you are using a widget '
            'that looks for a Form ancestor, but no such ancestor exists.\n'
            'The context used was:\n'
            '  $context',
          );
        }
        return true;
      }());
      return formState!;
    }
    
  • 它会从给定的 context 出发,沿着 widget 树 向上查找最近的 Form widget,并返回它的状态对象 FormState。返回值类型是 FormState?


使用的前提条件要在 Form 的子树里

  • “子树” 指的是 以某个 widget 为根,向下包含的所有 widget 节点
  • 如果某个 widget 被放在 Formchild 或者更深层的后代里,就说这个 widget 处于 Form 的子树。
Form(
  child: Column(
    children: [
      TextFormField(...),    // ✅ 在 Form 子树里
      ElevatedButton(...),   // ✅ 在 Form 子树里
    ],
  ),
);

OutsideButton(...);          // ❌ 不在 Form 子树里
  • ElevatedButtononPressed 里用 Form.of(context) 能取到 FormState
  • OutsideButtononPressed 里用 Form.of(context) 会返回 null

FormState 的常用方法

  1. bool validate():逐个字段执行 validator,全部通过才返回 true

  2. void save():逐个字段执行 onSaved

  3. void reset():把每个字段恢复到各自的 initialValue

Form 的主要属性

class Form extends StatefulWidget {
  /// Creates a container for form fields.
  const Form({
    super.key,
    required this.child,
    this.canPop,
    @Deprecated(
      'Use onPopInvokedWithResult instead. '
      'This feature was deprecated after v3.22.0-12.0.pre.',
    )
    this.onPopInvoked,
    this.onPopInvokedWithResult,
    @Deprecated(
      'Use canPop and/or onPopInvokedWithResult instead. '
      'This feature was deprecated after v3.12.0-1.0.pre.',
    )
    this.onWillPop,
    this.onChanged,
    AutovalidateMode? autovalidateMode,
  }) : autovalidateMode = autovalidateMode ?? AutovalidateMode.disabled,
       assert(
         onPopInvokedWithResult == null || onPopInvoked == null,
         'onPopInvoked is deprecated; use onPopInvokedWithResult',
       ),
       assert(
         ((onPopInvokedWithResult ?? onPopInvoked ?? canPop) == null) 
         																				|| onWillPop == null,
         'onWillPop is deprecated; use canPop and/or onPopInvokedWithResult.',
       );
  @override
  FormState createState() => FormState();
}
  1. keyFormkey 用来 唯一标识这个 Form widget。为了能够让外部就通过 currentState 获取它的 FormState,从而整体调用 validate()save()reset()

  2. child:表单内容。

  3. autovalidateMode:自动校验模式

    • disabled(默认,不自动校验)
    • onUserInteraction(用户编辑后才自动校验)
    • always(始终自动校验)
  4. onChanged:任一字段变化就会回调(并触发所有字段重建,适合做按钮可用性联动)。

  5. 返回拦截相关(Flutter 3.12+ / 3.22+)

    • canPop:为 false 时阻止当前路由被弹出(返回)。
    • onPopInvokedWithResult:页面尝试返回后得到回调(带结果);onPopInvoked 已弃用;更早期的 onWillPop 也已弃用。

    这些参数让 Form 自带“未保存离开拦截”的能力,无需再单独包一层 WillPopScope

FormField

FormField是什么

class FormField<T> extends StatefulWidget {
  /// Creates a single form field.
  const FormField({
    super.key,
    required this.builder,
    this.onSaved,
    this.forceErrorText,
    this.validator,
    this.errorBuilder,
    this.initialValue,
    this.enabled = true,
    AutovalidateMode? autovalidateMode,
    this.restorationId,
  }) : autovalidateMode = autovalidateMode ?? AutovalidateMode.disabled;
  @override
  FormFieldState<T> createState() => FormFieldState<T>();
}

FormField<T>表单字段的抽象基类,它定义了字段在表单中的行为(StatefulWidget,泛型 T 表示值类型)。它本身不会渲染具体 UI,而是定义了字段的状态管理、验证、保存、重置等行为。

核心点:

  • 每个 FormField 都有一个 FormFieldState<T>,用来存储当前字段的值。
  • FormField 自己不画界面,它通过 builder 回调,让你返回具体的输入控件。
  • 内置支持:
    • validator:校验逻辑
    • onSaved:保存逻辑
    • initialValue:初始值
    • enabled:是否可用

TextFormField

是什么

class TextFormField extends FormField<String> {
  // ......
  @override
  FormFieldState<String> createState() => _TextFormFieldState();
}

TextFormFieldFormField<String> 的一个封装,专门用于文本输入。它内部其实就是:

FormField<String>(
  builder: (_) => TextField(...),
)
  • TextField 是一个独立的输入框,不会参与 Form 的统一管理。

    • 但是会帮忙管理 TextEditingControllerFocusNode
  • TextFormField 是基于 FormField<String>,因此:

    • 支持 validator / onSaved / reset()
    • 能被 FormState.validate()FormState.save() 调用。
    • 内部自动管理 TextEditingController
  • 自动把输入变化同步到 FormFieldState<String>(通过 didChange)。

TextFormField = FormField + TextEditingController 状态管理 + TextField UI 封装

常用属性

输入相关

  • controller: 类型 TextEditingController?,用来控制输入内容、监听输入变化。

    final controller = TextEditingController();
    TextFormField(controller: controller);
    
  • initialValue: 设置初始值(如果有 controller 会忽略它)。

  • keyboardType: 键盘类型,比如 TextInputType.emailAddressTextInputType.number

  • textInputAction: 键盘右下角按钮的样式,如 TextInputAction.doneTextInputAction.next

  • obscureText: 是否隐藏输入(用于密码输入)。

  • maxLines / minLines / maxLength: 控制行数和最大输入长度。

表单相关

  • validator:(String? value) → String?。表单校验函数,返回 null 表示合法,否则返回错误提示。

    validator: (value) {
      if (value == null || value.isEmpty) return "不能为空";
      return null;
    }
    
  • onSaved:(String? value) → void。当调用 FormState.save() 时触发,把值保存到模型。

  • autovalidateMode:控制验证时机:

    • disabled(默认,仅调用 validate() 时验证)
    • always(输入时即时验证)
    • onUserInteraction(用户改动后才验证)

装饰和 UI

  • decoration: 用的是 InputDecoration,它的属性非常丰富,常用的有:

    • 提示文本相关

      • labelText:输入框上方的标签(Material 风格)。
      • hintText:输入框内的提示文字。
      • helperText:辅助说明文字,显示在输入框下方。
      • errorText:错误提示(validator 返回字符串时自动设置)。
    • 图标相关

      • icon:输入框前的图标(在输入框外)。
      • prefixIcon:输入框左边的图标(在输入框内部)。
      • suffixIcon:输入框右边的图标(常用于清空按钮、密码可见切换)。
    • 样式相关

      • border:边框(OutlineInputBorder / UnderlineInputBorder)。
      • filled & fillColor:是否填充背景色。
      • contentPadding:内容的内边距。
    TextFormField(
      decoration: InputDecoration(
        labelText: '邮箱',
        hintText: '请输入邮箱地址',
        prefixIcon: Icon(Icons.email),
        border: OutlineInputBorder(),
      ),
    )  
    
  • style: 输入文字的样式 TextStyle

  • textAlign: 输入文字对齐方式。

事件回调

  • onChanged: 输入内容改变时触发。
  • onFieldSubmitted: 用户点击键盘的「完成/提交」时触发。
  • onEditingComplete:输入结束时触发。

FormFieldState<T>

它是 FormField<T> 对应的 状态类,继承自 State<FormField<T>>,核心职责就是管理表单字段的值、校验、保存

核心属性

属性 类型 说明
widget FormField<T> 当前绑定的表单字段 widget。
value T? 当前字段的值。
hasError bool 是否存在错误。
errorText String? 错误信息(来自 validator 返回值)。
isValid bool 当前字段是否通过校验。
isDirty bool 当前值是否被用户修改过。
context BuildContext Widget 树上下文(从 State 继承)。

核心方法

方法 说明
didChange(T? value) 手动更新字段值,并触发 builder 重建。
reset() 重置字段值为初始值,并清空错误。
validate() 调用 validator 并返回是否通过校验。
save() 调用 onSaved 回调。
setValue(T? value) 更新值(类似 didChange,但不会标记为脏)。
setState(fn) 触发 UI 重建(从 State 继承)。

FormField 体系里:

  • state.value 存的就是当前表单字段的值。
    • 对于TextFormField,它的 state.value 对应 输入框当前的文本内容
    • 对于自己定义的MyCheckboxFormField,它的 state.value 对应 复选框当前的选中状态(true/false)
    • state.value:是 表单字段内部的临时状态,随用户操作不断变化。
  • state.didChange(newValue) 会做两件事:
    1. state.value 更新为 newValue
    2. 通知 Flutter 重新 build(让 UI 和错误提示刷新)。
    3. 如果 autovalidateMode 开启了,还会立即触发 validator

生命周期方法(继承自 State)

initState() → 初始化时调用。

dispose() → 销毁时调用。

didUpdateWidget() → Widget 更新时调用。

FormState vs FormFieldState 对比表

特性 FormState(表单级别) FormFieldState(字段级别)
归属 管理整个 Form 管理单个 FormField<T>
获取方式 - GlobalKey<FormState>
- Form.of(context)
FormField.builder 中直接拿到 state
主要职责 - 统筹所有字段
- 表单整体的校验、保存、重置
- 管理字段值
- 管理字段的校验、保存、重置
核心属性 - widget(对应 Form)
- autovalidateMode
- context
- widget(对应 FormField)
- value(当前值)
- errorText(错误提示)
- hasError / isValid / isDirty
核心方法 - validate() → 校验所有字段
- save() → 保存所有字段
- reset() → 重置所有字段
- didChange(value) → 更新值
- validate() → 校验当前字段
- save() → 保存当前字段
- reset() → 重置当前字段
调用时机 - 提交表单时
- 一键重置时
- 用户输入/交互时- Builder 渲染时
典型使用场景 提交按钮里:
if (_formKey.currentState!.validate()) { _formKey.currentState!.save();}
自定义表单控件:builder: (state) { return Checkbox(value: state.value, onChanged: (v) => state.didChange(v),);}

层次关系

Form 是一个 StatefulWidget,它的 State 就是 FormState

TextFormField(或自定义 FormField<T>)是 Form 的子节点,它们各自有 FormFieldState

Form (widget)
 └── FormState (管理整个表单)
      ├── FormField<String> (比如 TextFormField)
      │    └── FormFieldState<String>
      ├── FormField<bool> (比如 Checkbox)
      │    └── FormFieldState<bool>
      └── ...

FormField如何工作

字段注册

  • 当一个 FormField 被创建时,它的 FormFieldState 会通过 FormState._register 注册到 FormState。销毁时会自动注销。

校验 validate()

  • 调用 FormState.validate() 时,它会遍历所有注册的 FormFieldState,依次调用它们的 validate()。每个字段内部再执行自己的 validator,返回错误信息或 null。最终 FormState 汇总结果。

保存 save()

  • 调用 FormState.save() 时,它会遍历所有 FormFieldState,依次调用 onSaved。比如 TextFormField.onSaved 会把文本保存到模型变量里。

重置 reset()

  • 调用 FormState.reset() 时,它会遍历所有 FormFieldState,调用 reset(),恢复初始值。

UI 更新

  • 当某个 FormField 调用 state.didChange(newValue) 时,
    • 它会更新自己在 FormFieldState 里的值;
    • 同时触发 builder 重新渲染;
    • 但不会直接影响其他字段,除非在外部逻辑里写了联动。

autovalidateMode自动校验

autovalidateMode 的作用就是 控制 validator 的触发时机

AutovalidateMode.disabled → 不会因为输入触发,只有 formKey.currentState!.validate() 时才触发。

AutovalidateMode.always → 初始 build 时就触发,以后在输入框里 打字 / 删除 / 粘贴 ——每次内容变化,都会触发一次 validator

AutovalidateMode.onUserInteraction首次用户输入后才开始触发,每次输入都会继续触发。

一个案例理解上面的关系

代码

import 'package:flutter/material.dart';

void main() {
  runApp(const MyApp());
}

class MyApp extends StatelessWidget {
  const MyApp({super.key});

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Form 调试 Demo',
      theme: ThemeData(useMaterial3: true, colorSchemeSeed: Colors.blue),
      home: Scaffold(
        appBar: AppBar(title: Text('Form 调试日志')),
        body: Padding(padding: EdgeInsets.all(16), child: DebugForm()),
      ),
    );
  }
}

class DebugForm extends StatefulWidget {
  const DebugForm({super.key});
  @override
  State<DebugForm> createState() => _DebugFormState();
}

class _DebugFormState extends State<DebugForm> {
  final _formKey = GlobalKey<FormState>();

  String? _email;
  bool _agreed = false;

  @override
  Widget build(BuildContext context) {
    return Form(
      key: _formKey,
      // 用户编辑后才自动校验:用户的每一次编辑都会触发 validator
      autovalidateMode: AutovalidateMode.onUserInteraction,
      child: Column(
        crossAxisAlignment: CrossAxisAlignment.start,
        children: [
          // TextFormField 示例
          TextFormField(
            decoration: const InputDecoration(labelText: '邮箱'),
            validator: (v) {
              print("TextFormField.validator 被调用, 当前值: $v");
              if (v == null || !v.contains('@')) return '请输入有效的邮箱';
              return null; // 返回null表示通过
            },
            onSaved: (v) {
              print("TextFormField.onSaved 被调用, 值: $v");
              _email = v;
            },
          ),
          const SizedBox(height: 16),

          // 自定义 Checkbox 表单字段
          MyCheckboxFormField(
            label: "我已阅读并同意条款",
            onSaved: (value) {
              print("MyCheckboxFormField.onSaved 被调用, 值: $value");
              _agreed = value ?? false;
            },
          ),

          const SizedBox(height: 24),

          Center(
            child: ElevatedButton(
              onPressed: () {
                print("=== 点击提交按钮 ===");
                // 触发表单验证
                if (_formKey.currentState!.validate()) {
                  print("FormState.validate() 返回 true");
                  // 触发 onSaved
                  _formKey.currentState!.save();
                  print("FormState.save() 执行完毕");
                  // 弹出提示框
                  ScaffoldMessenger.of(context).showSnackBar(
                    SnackBar(content: Text('邮箱: $_email, 同意: $_agreed')),
                  );
                } else {
                  print("FormState.validate() 返回 false");
                }
              },
              child: const Text('提交'),
            ),
          ),
        ],
      ),
    );
  }
}

/// 自定义 Checkbox 表单字段
class MyCheckboxFormField extends FormField<bool> {
  MyCheckboxFormField({super.key, required String label, super.onSaved})
    : super(
        initialValue: false,
        validator: (value) {
          print("MyCheckboxFormField.validator 被调用, 值: $value");
          return value == true ? null : '必须勾选才能继续';
        },
        builder: (state) {
          print(
            "MyCheckboxFormField.builder 被调用, 值: ${state.value}, hasError: ${state.hasError}",
          );
          return Row(
            children: [
              Checkbox(
                value: state.value, // 在这里绑定
                onChanged: (val) {
                  print("Checkbox.onChanged 被调用, 新值: $val");
                  state.didChange(val); // 触发状态更新
                },
              ),
              Text(label),
              if (state.hasError)
                Padding(
                  padding: const EdgeInsets.only(left: 8),
                  child: Text(
                    state.errorText!,
                    style: const TextStyle(color: Colors.red),
                  ),
                ),
            ],
          );
        },
      );
}

日志如下:

Restarted application in 2,549ms.
I/flutter ( 3781): MyCheckboxFormField.builder 被调用, 值: false, hasError: false
I/flutter ( 3781): MyCheckboxFormField.builder 被调用, 值: false, hasError: false
I/flutter ( 3781): TextFormField.validator 被调用, 当前值: 1
I/flutter ( 3781): MyCheckboxFormField.validator 被调用, 值: false
I/flutter ( 3781): MyCheckboxFormField.builder 被调用, 值: false, hasError: true
I/flutter ( 3781): TextFormField.validator 被调用, 当前值:
I/flutter ( 3781): MyCheckboxFormField.validator 被调用, 值: false
I/flutter ( 3781): MyCheckboxFormField.builder 被调用, 值: false, hasError: true
I/flutter ( 3781): TextFormField.validator 被调用, 当前值: @
I/flutter ( 3781): MyCheckboxFormField.validator 被调用, 值: false
I/flutter ( 3781): MyCheckboxFormField.builder 被调用, 值: false, hasError: true
I/flutter ( 3781): Checkbox.onChanged 被调用, 新值: true
I/flutter ( 3781): TextFormField.validator 被调用, 当前值: @
I/flutter ( 3781): MyCheckboxFormField.validator 被调用, 值: true
I/flutter ( 3781): MyCheckboxFormField.builder 被调用, 值: true, hasError: false
I/flutter ( 3781): === 点击提交按钮 ===
I/flutter ( 3781): TextFormField.validator 被调用, 当前值: @
I/flutter ( 3781): MyCheckboxFormField.validator 被调用, 值: true
I/flutter ( 3781): FormState.validate() 返回 true
I/flutter ( 3781): TextFormField.onSaved 被调用, 值: @
I/flutter ( 3781): MyCheckboxFormField.onSaved 被调用, 值: true
I/flutter ( 3781): FormState.save() 执行完毕
I/flutter ( 3781): TextFormField.validator 被调用, 当前值: @
I/flutter ( 3781): MyCheckboxFormField.validator 被调用, 值: true
I/flutter ( 3781): MyCheckboxFormField.builder 被调用, 值: true, hasError: false

MyCheckboxFormField 的 state

class FormField<T> extends StatefulWidget {
  const FormField({...});

  @override
  FormFieldState<T> createState() => FormFieldState<T>();
}
/// 自定义 Checkbox 表单字段
class MyCheckboxFormField extends FormField<bool> {
  MyCheckboxFormField({super.key, required String label, super.onSaved})
    : super(
      builder: (state) {
        // 操作state
        // state.value 就是当前的值
  		 	// state.didChange(newValue) 可以更新值
  			// state.hasError / state.errorText 管理错误信息
      }
    )
}
  • 每个 StatefulWidget 在构建时,Flutter 框架都会调用它的 createState() 方法,生成一个 State 对象。这个 State 对象会在 widget 的整个生命周期中保存下来,用来维护状态。
  • FormField<T> 本身就是一个 StatefulWidget。它的 createState() 方法返回的是一个 FormFieldState<T>(不是通用的 FormState,注意名字区别)
  • 当我们写MyCheckboxFormField(...)来创建实例的时候,本质上就是:
    1. Flutter 创建了一个 MyCheckboxFormField 实例。
    2. 因为它继承自 FormField<bool>,所以会调用 FormField.createState()
    3. 生成一个 FormFieldState<bool> 对象,并且 Flutter 框架把它和这个 widget 绑定在一起。
    4. 渲染时,这个 state 会被传给你 builder: (state) { ... },让你能操作它。

这个 FormFieldState<T> 就是 表单字段的状态管理器,提供了一些关键方法/属性:

  • value → 当前字段的值
  • didChange(newValue) → 更新字段值并触发 builder 重建
  • hasError / errorText → 校验结果
  • save() → 调用 onSaved
  • validate() → 调用 validator

builder 和 validator 什么时候触发

builder

  • 在第一次构建时会调用一次。
  • 每当该字段的值变化(state.didChange(newValue) 被调用)时会重新执行,刷新 UI。
  • 所以 builder 就是负责“渲染这个字段”的地方。

validator

  • FormState.validate() 被调用时触发。
  • TextFormField 一样,返回 null 表示通过,返回字符串表示错误。
  • 如果有错误,state.hasError 会变为 truestate.errorText 会存储错误字符串,进而触发 builder 重建,把错误信息渲染出来。

×

喜欢就点赞,疼爱就打赏