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()会逐个调用所有后代FormField的validator。集中保存:
FormState.save()会逐个触发所有FormField的onSaved。集中重置:
FormState.reset()把每个字段恢复到它们的initialValue。统一监听变化:任意字段变化时触发
Form.onChanged,便于启用/禁用提交按钮等。拦截返回(Pop):通过
canPop/onPopInvokedWithResult(替代旧的onWillPop)控制页面是否可返回,例如表单未保存时给出二次确认
FormState
是什么
class FormState extends State<Form> {
// ......
}
FormState 是 Form 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关联起来。
- Flutter 调用
_formKey.currentState
- 返回与该 key 绑定的
FormState实例。这就是Formwidget 的状态对象。
通过 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 树 向上查找最近的Formwidget,并返回它的状态对象FormState。返回值类型是FormState?。
使用的前提条件:要在 Form 的子树里
- “子树” 指的是 以某个 widget 为根,向下包含的所有 widget 节点。
- 如果某个 widget 被放在
Form的child或者更深层的后代里,就说这个 widget 处于Form的子树。
Form(
child: Column(
children: [
TextFormField(...), // ✅ 在 Form 子树里
ElevatedButton(...), // ✅ 在 Form 子树里
],
),
);
OutsideButton(...); // ❌ 不在 Form 子树里
- 在
ElevatedButton的onPressed里用Form.of(context)能取到FormState。 - 在
OutsideButton的onPressed里用Form.of(context)会返回null。
FormState 的常用方法
bool validate():逐个字段执行validator,全部通过才返回true。void save():逐个字段执行onSaved。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();
}
key:Form的key用来 唯一标识这个 Form widget。为了能够让外部就通过currentState获取它的FormState,从而整体调用validate()、save()、reset()。child:表单内容。autovalidateMode:自动校验模式disabled(默认,不自动校验)onUserInteraction(用户编辑后才自动校验)always(始终自动校验)
onChanged:任一字段变化就会回调(并触发所有字段重建,适合做按钮可用性联动)。返回拦截相关(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();
}
TextFormField是FormField<String> 的一个封装,专门用于文本输入。它内部其实就是:
FormField<String>(
builder: (_) => TextField(...),
)
TextField 是一个独立的输入框,不会参与 Form 的统一管理。
- 但是会帮忙管理
TextEditingController、FocusNode。
- 但是会帮忙管理
TextFormField 是基于
FormField<String>,因此:- 支持
validator/onSaved/reset()。 - 能被
FormState.validate()、FormState.save()调用。 - 内部自动管理
TextEditingController。
- 支持
自动把输入变化同步到
FormFieldState<String>(通过didChange)。
TextFormField = FormField
常用属性
输入相关
controller: 类型TextEditingController?,用来控制输入内容、监听输入变化。final controller = TextEditingController(); TextFormField(controller: controller);initialValue: 设置初始值(如果有controller会忽略它)。keyboardType: 键盘类型,比如TextInputType.emailAddress、TextInputType.number。textInputAction: 键盘右下角按钮的样式,如TextInputAction.done、TextInputAction.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)会做两件事:- 把
state.value更新为newValue。 - 通知 Flutter 重新 build(让 UI 和错误提示刷新)。
- 如果
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(...)来创建实例的时候,本质上就是:- Flutter 创建了一个
MyCheckboxFormField实例。 - 因为它继承自
FormField<bool>,所以会调用FormField.createState()。 - 生成一个
FormFieldState<bool>对象,并且 Flutter 框架把它和这个 widget 绑定在一起。 - 渲染时,这个
state会被传给你builder: (state) { ... },让你能操作它。
- Flutter 创建了一个
这个 FormFieldState<T> 就是 表单字段的状态管理器,提供了一些关键方法/属性:
value→ 当前字段的值didChange(newValue)→ 更新字段值并触发builder重建hasError/errorText→ 校验结果save()→ 调用onSavedvalidate()→ 调用validator
builder 和 validator 什么时候触发
builder:
- 在第一次构建时会调用一次。
- 每当该字段的值变化(
state.didChange(newValue)被调用)时会重新执行,刷新 UI。 - 所以
builder就是负责“渲染这个字段”的地方。
validator:
- 当
FormState.validate()被调用时触发。 - 和
TextFormField一样,返回null表示通过,返回字符串表示错误。 - 如果有错误,
state.hasError会变为true,state.errorText会存储错误字符串,进而触发builder重建,把错误信息渲染出来。