flutter:自定义Form

  1. 自定义 FormField 的要点
  2. 深入理解FormFieldState.value

自定义 FormField 的要点

  1. 使用 FormField<T> 包装 UI

    • 你必须定义一个 builder,builder 会传入 FormFieldState<T> state,通过 state.valuestate.errorText 获取/设置值与错误提示。
  2. 更新值要调用 state.didChange(newValue)

    • 重点:不要自己 setState,而是调用 state.didChange(newValue),这样 Form 才能感知值变化。
  3. 显示错误信息

    • state.errorText 显示校验失败时的提示。
  4. 校验、保存、重置

    • 校验: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 → 验证通过
  1. 用户在 InputWidget 输入内容 → InputWidgetonChanged 被触发

  2. 触发 onChangedHandler方法,拿到当前输入的 value。这个value就是用户输入的值。

    void onChangedHandler(String value) {
      field.didChange(value); // 更新 FormFieldState.value
      onChanged?.call(value); // 调用外部传进来的回调
    }
    
  3. onChangedHandler 里调用 field.didChange(value)

    • 这一步会把 FormFieldState.value 更新成输入的 value
    • 然后触发一次 FormFieldState.setState(),重新 build builder
    • 这时候 FormFieldState 还会根据情况触发 校验
      • didChange 之后,如果表单处于 autovalidateMode(比如 alwaysonUserInteraction),就会立即调用你传入的 validator(field.value)
      • 否则,要等你手动调用 Form.of(context).validate(),才会统一对所有字段跑一次 validator
      • validator 接收到的参数就是 FormFieldState.value,也就是你刚刚 didChange 更新的那个值。
  4. 如果还传了额外的 onChanged 回调(外部想监听输入),就再手动调用一下 onChanged?.call(value)

×

喜欢就点赞,疼爱就打赏