自定义 FormField 的要点
使用
FormField<T>包装 UI- 你必须定义一个
builder,builder 会传入FormFieldState<T> state,通过state.value和state.errorText获取/设置值与错误提示。
- 你必须定义一个
更新值要调用
state.didChange(newValue)- 重点:不要自己
setState,而是调用state.didChange(newValue),这样Form才能感知值变化。
- 重点:不要自己
显示错误信息
- 用
state.errorText显示校验失败时的提示。
- 用
校验、保存、重置
校验:
Form.of(context).validate()会触发validator保存:
Form.of(context).save()会触发onSaved重置:
Form.of(context).reset()会触发state.reset()
import 'package:ducafe_ui_core/ducafe_ui_core.dart';
import 'package:flutter/material.dart';
import '../../index.dart';
/// Form 字段组件
class InputFormFieldWidget extends FormField<String> {
InputFormFieldWidget({
super.key,
required this.labelText,
this.tipText,
this.initValue,
this.onChanged,
this.controller,
this.placeholder,
this.prefix,
this.suffix,
this.obscureText,
this.cleanable,
this.keyboardType,
this.autofocus,
Function(String?)? validator,
}) : super(
initialValue: initValue ?? controller?.text,
validator: (val) {
if (validator != null) {
return validator(val);
}
return null;
},
builder: (field) {
void onChangedHandler(String value) {
field.didChange(value); // 更新 FormFieldState.value
onChanged?.call(value); // 调用外部传进来的回调
}
return <Widget>[
// 字段说明
TextWidget.label(
labelText,
).paddingLeft(AppSpace.card),
// 输入框
InputWidget(
placeholder: placeholder,
prefix: prefix,
suffix: suffix,
controller: controller,
obscureText: obscureText ?? false,
cleanable: cleanable ?? true,
onChanged: onChangedHandler,
),
// 提示词
if (tipText != null)
TextWidget.muted(
tipText,
).paddingLeft(AppSpace.card),
// 错误提示
if (field.errorText != null)
TextWidget.muted(
field.errorText!,
color: field.context.colors.scheme.error,
).paddingLeft(AppSpace.card),
].toColumnSpace(
crossAxisAlignment: CrossAxisAlignment.start,
);
},
);
/// 字段文字
final String labelText;
/// 提示词
final String? tipText;
/// 初始值
final String? initValue;
/// 输入框控制器
final TextEditingController? controller;
/// 占位符
final String? placeholder;
/// 前缀
final Widget? prefix;
/// 后缀
final Widget? suffix;
/// 是否隐藏文本
final bool? obscureText;
/// 是否可清空
final bool? cleanable;
/// 值被改变时的回调
final void Function(String?)? onChanged;
/// 输入法类型
final TextInputType? keyboardType;
/// 自动焦点
final bool? autofocus;
@override
InputFormWidgetFieldState createState() => InputFormWidgetFieldState();
}
class InputFormWidgetFieldState extends FormFieldState<String> {
@override
InputFormFieldWidget get widget => super.widget as InputFormFieldWidget;
}
深入理解FormFieldState.value
用户输入内容
└──> InputWidget
└──> 触发 onChanged 回调
└──> 调用 onChangedHandler
└──> field.didChange(value)
├── 更新 FormFieldState.value
└── (autovalidateMode = always/onUserInteraction 时)
└── 调用 validator(FormFieldState.value)
├── 返回错误信息 → errorText 更新
└── 返回 null → 验证通过
用户在
InputWidget输入内容 →InputWidget的onChanged被触发触发
onChangedHandler方法,拿到当前输入的value。这个value就是用户输入的值。void onChangedHandler(String value) { field.didChange(value); // 更新 FormFieldState.value onChanged?.call(value); // 调用外部传进来的回调 }在
onChangedHandler里调用field.didChange(value)- 这一步会把
FormFieldState.value更新成输入的value - 然后触发一次
FormFieldState.setState(),重新 buildbuilder。 - 这时候
FormFieldState还会根据情况触发 校验didChange之后,如果表单处于autovalidateMode(比如always或onUserInteraction),就会立即调用你传入的validator(field.value)。- 否则,要等你手动调用
Form.of(context).validate(),才会统一对所有字段跑一次validator。 validator接收到的参数就是FormFieldState.value,也就是你刚刚didChange更新的那个值。
- 这一步会把
如果还传了额外的
onChanged回调(外部想监听输入),就再手动调用一下onChanged?.call(value)