Flutter表单管理插件flutter_form_registry的使用

发布于 1周前 作者 eggper 来自 Flutter

Flutter表单管理插件flutter_form_registry的使用

简介

flutter_form_registry 是一个用于跟踪表单字段并在表单验证失败时自动滚动到第一个无效字段的插件。它支持检查 FormField 是否完全可见,并可以将其滚动到视图中。

功能特性

  • 跟踪注册的 widget:可以跟踪表单中的所有 FormField
  • 自动滚动到第一个无效字段:当表单验证失败时,自动滚动到第一个无效字段。
  • 每个注册的 FormField 包含其 key、value、error text 和辅助方法:用于滚动显示和检查是否完全可见。

依赖项

  • Flutter SDK:>=3.7.0
  • Dart SDK:>=2.19.0 <3.0.0

对于较旧版本的 Flutter SDK:

  • =3.0.0 <3.7.0

  • =2.5.0 <3.0.0

  • <2.5.0

安装

pubspec.yaml 文件中添加以下依赖:

dependencies:
  flutter_form_registry: ^0.7.0

使用方法

1. 包裹包含所有表单字段的 widget

使用 FormRegistryWidget 包裹包含所有表单字段的 widget,并为其提供一个 GlobalKey<FormRegistryWidgetState> 或调用 FormRegistryWidgetState.of 静态方法来访问所有已注册的表单字段。

FormRegistryWidget(
  key: _registerdKey,
  autoScrollToFirstInvalid: true, // 自动滚动到第一个无效字段
  child: Form(
    key: _formKey,
    child: SingleChildScrollView(
      child: Column(
        children: [
          // 表单字段
        ],
      ),
    ),
  ),
)
2. 处理自定义 FormField

如果你有一个自定义的 FormField,你需要:

  • 使用 FormFieldRegistrantMixin 混入类。
  • 覆盖 registryIdregistryTypelookupPriority
class CustomTextFormField extends FormField<String> with FormFieldRegistrantMixin {
  CustomTextFormField({
    Key? key,
    this.registryId,
    this.registryType,
    this.lookupPriority,
    // 其他参数
  });

  [@override](/user/override)
  final String? registryId;

  [@override](/user/override)
  final Object? registryType;

  [@override](/user/override)
  final int? lookupPriority;
}

class _CustomTextFormFieldState extends FormFieldState<String> with FormFieldStateRegistrantMixin {
  // 实现
}

你可以覆盖默认的滚动行为:

class _TextFormFieldState extends FormFieldState<String> with FormFieldStateRegistrantMixin {
  [@override](/user/override)
  double get alignment => yourAlignment;

  [@override](/user/override)
  Duration get duration => yourDuration;

  [@override](/user/override)
  Curve get curve => yourCurve;

  [@override](/user/override)
  ScrollPositionAlignmentPolicy get alignmentPolicy => yourAlignmentPolicy;
}
3. 处理框架或包中的 FormField

对于框架或包中的 FormField,你需要使用 FormFieldRegistrant 包裹它们。

FormFieldRegistrant(
  registrarId: 'select date',
  registrarType: 'date',
  validator: (DateTime? value) {
    if (value == null) {
      return "Empty!";
    }

    if (value.isBefore(DateTime.now())) {
      return 'The date must be before today';
    }

    return null;
  },
  builder: (
    GlobalKey<FormFieldState<DateTime>> formFieldKey,
    String? Function(DateTime?) validator,
  ) {
    return DateTimeFormField(
      key: formFieldKey,
      validator: validator,
      onDateSelected: (value) {
        setState(() {
          selectedDate = value;
        });
      },
      mode: DateTimeFieldPickerMode.date,
      initialValue: selectedDate,
    );
  },
),
4. 获取表单字段的值

你可以通过 FormRegistryWidgetState 获取已注册的表单字段,并获取其值。

final FormRegistryWidgetState formRegistryWidgetState = FormRegistryWidget.of(context);
final RegisteredField registeredField = formRegistryWidgetState.getFieldBy('your field registry id');
final currentValue = registeredField.getValue();

示例代码

以下是一个完整的示例代码,展示了如何使用 flutter_form_registry 插件来管理表单并自动滚动到第一个无效字段。

import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';
import 'package:flutter_form_registry/flutter_form_registry.dart';

void main() => runApp(const MyApp());

class MyApp extends StatelessWidget {
  const MyApp({Key? key}) : super(key: key);

  [@override](/user/override)
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Flutter Demo Scroll To Invalid FormField',
      debugShowCheckedModeBanner: false,
      theme: ThemeData(
        primarySwatch: Colors.blue,
      ),
      home: const MyHomePage(title: 'Scroll to invalid field'),
    );
  }
}

class MyHomePage extends StatefulWidget {
  const MyHomePage({Key? key, required this.title}) : super(key: key);

  final String title;

  [@override](/user/override)
  State<MyHomePage> createState() => _MyHomePageState();
}

class _MyHomePageState extends State<MyHomePage> {
  final GlobalKey<FormState> _formKey = GlobalKey();
  final GlobalKey<FormRegistryWidgetState> _registerdKey = GlobalKey();

  final List<GlobalKey<FormFieldState<String>>> fieldKeys = [
    for (int i = 21; i < 40; i++) GlobalKey(),
  ];

  String? integerTextFieldValidator(String? value) {
    if (value == null) {
      return 'Null!';
    }
    try {
      int.parse(value);
    } catch (error) {
      return 'Not an integer!';
    }
    return null;
  }

  [@override](/user/override)
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text(widget.title),
      ),
      body: Padding(
        padding: const EdgeInsets.all(20.0),
        child: FormRegistryWidget(
          key: _registerdKey,
          autoScrollToFirstInvalid: true,
          child: Center(
            child: Form(
              key: _formKey,
              child: SingleChildScrollView(
                child: Column(
                  children: <Widget>[
                    for (int i = 0; i < 15; i++)
                      CustomTextFormField(
                        registrarId: "No. $i",
                        initialValue: "$i",
                        validator: integerTextFieldValidator,
                      ),
                    CustomTextFormField(
                      registrarId: "No. 15",
                      initialValue: 'fifteen',
                      validator: integerTextFieldValidator,
                    ),
                    for (int i = 16; i < 20; i++)
                      FormFieldRegistrant(
                        registrarId: "No. $i",
                        validator: integerTextFieldValidator,
                        builder: (
                          GlobalKey<FormFieldState<String>> formFieldKey,
                          String? Function(String?) validator,
                        ) {
                          return MyTextField(
                            initial: "$i",
                            fieldKey: formFieldKey,
                            validator: validator,
                          );
                        },
                      ),
                    FormFieldRegistrant(
                      registrarId: 'No. 20',
                      validator: integerTextFieldValidator,
                      builder: (
                        GlobalKey<FormFieldState<String>> formFieldKey,
                        String? Function(String?) validator,
                      ) {
                        return MyTextField(
                          initial: 'twenty',
                          fieldKey: formFieldKey,
                          validator: validator,
                        );
                      },
                    ),
                    for (int i = 21; i < 25; i++)
                      FormFieldRegistrant(
                        registrarId: 'No. $i',
                        formFieldKey: fieldKeys[i - 21],
                        validator: integerTextFieldValidator,
                        builder: (_, String? Function(String?) validator) {
                          return MyTextField(
                            initial: "$i",
                            fieldKey: fieldKeys[i - 21],
                            validator: validator,
                          );
                        },
                      ),
                    FormFieldRegistrant(
                      registrarId: 'No. 25',
                      formFieldKey: fieldKeys[25 - 21],
                      validator: integerTextFieldValidator,
                      builder: (_, String? Function(String?) validator) {
                        return MyTextField(
                          initial: 'twenty-five',
                          fieldKey: fieldKeys[25 - 21],
                          validator: validator,
                        );
                      },
                    ),
                    for (int i = 26; i < 40; i++)
                      FormFieldRegistrant(
                        registrarId: 'No. $i',
                        formFieldKey: fieldKeys[i - 21],
                        validator: integerTextFieldValidator,
                        builder: (_, String? Function(String?) validator) {
                          return MyTextField(
                            initial: "$i",
                            fieldKey: fieldKeys[i - 21],
                            validator: validator,
                          );
                        },
                      ),
                  ],
                ),
              ),
            ),
          ),
        ),
      ),
      floatingActionButton: FloatingActionButton(
        onPressed: () {
          _formKey.currentState?.validate();

          if (kDebugMode) {
            print(_registerdKey.currentState!.firstInvalid?.errorText);
          }
        },
        tooltip: 'Scroll to invalid',
        child: const Icon(Icons.search),
      ),
    );
  }
}

// 自定义的 TextField 组件
class MyTextField extends StatelessWidget {
  final String initial;
  final GlobalKey<FormFieldState<String>> fieldKey;
  final String? Function(String?) validator;

  const MyTextField({
    Key? key,
    required this.initial,
    required this.fieldKey,
    required this.validator,
  }) : super(key: key);

  [@override](/user/override)
  Widget build(BuildContext context) {
    return TextFormField(
      key: fieldKey,
      initialValue: initial,
      validator: validator,
      decoration: const InputDecoration(
        border: OutlineInputBorder(),
      ),
    );
  }
}

// 自定义的 TextFormFiled 组件
class CustomTextFormField extends FormField<String> with FormFieldRegistrantMixin {
  CustomTextFormField({
    Key? key,
    this.registrarId,
    this.registrarType,
    this.lookupPriority,
    String? initialValue,
    FormFieldValidator<String>? validator,
    InputDecoration? decoration,
  }) : super(
          key: key,
          initialValue: initialValue,
          validator: validator,
          builder: (FormFieldState<String> field) {
            final InputDecoration effectiveDecoration = (decoration ?? const InputDecoration())
                .applyDefaults(Theme.of(field.context).inputDecorationTheme);
            return TextField(
              controller: TextEditingController(text: field.value),
              onChanged: field.didChange,
              decoration: effectiveDecoration.copyWith(
                errorText: field.errorText,
              ),
            );
          },
        );

  [@override](/user/override)
  final String? registrarId;

  [@override](/user/override)
  final Object? registrarType;

  [@override](/user/override)
  final int? lookupPriority;
}

class _CustomTextFormFieldState extends FormFieldState<String> with FormFieldStateRegistrantMixin {
  [@override](/user/override)
  double get alignment => 0.5;

  [@override](/user/override)
  Duration get duration => const Duration(milliseconds: 300);

  [@override](/user/override)
  Curve get curve => Curves.easeInOut;

  [@override](/user/override)
  ScrollPositionAlignmentPolicy get alignmentPolicy => ScrollPositionAlignmentPolicy.keepVisibleAtEnd;
}

更多关于Flutter表单管理插件flutter_form_registry的使用的实战系列教程也可以访问 https://www.itying.com/category-92-b0.html

1 回复

更多关于Flutter表单管理插件flutter_form_registry的使用的实战系列教程也可以访问 https://www.itying.com/category-92-b0.html


当然,以下是如何在Flutter项目中使用flutter_form_registry插件来管理表单的一个代码案例。flutter_form_registry插件允许你注册和管理表单字段,使得表单处理更加模块化和可重用。

首先,确保你已经在pubspec.yaml文件中添加了flutter_form_registry依赖:

dependencies:
  flutter:
    sdk: flutter
  flutter_form_registry: ^x.y.z  # 请替换为最新版本号

然后,运行flutter pub get来安装依赖。

接下来,我们来看一个具体的代码案例:

1. 创建表单字段注册器

首先,我们需要创建一个表单字段注册器来注册我们的表单字段。

import 'package:flutter/material.dart';
import 'package:flutter_form_registry/flutter_form_registry.dart';

class MyFormRegistry extends FormRegistry {
  @override
  void registerFields(FormRegistryBuilder builder) {
    builder
      .addField('username', TextFormFieldBuilder(
        labelText: 'Username',
        validator: (value) {
          if (value == null || value.isEmpty) {
            return 'Username is required';
          }
          return null;
        },
      ))
      .addField('email', TextFormFieldBuilder(
        labelText: 'Email',
        keyboardType: TextInputType.emailAddress,
        validator: (value) {
          if (value == null || value.isEmpty || !value.contains('@')) {
            return 'Please enter a valid email';
          }
          return null;
        },
      ))
      .addField('password', TextFormFieldBuilder(
        labelText: 'Password',
        obscureText: true,
        validator: (value) {
          if (value == null || value.isEmpty) {
            return 'Password is required';
          }
          return null;
        },
      ));
  }
}

2. 使用表单注册器构建表单

现在,我们可以使用MyFormRegistry来构建我们的表单。

import 'package:flutter/material.dart';
import 'package:flutter_form_registry/flutter_form_registry.dart';
import 'my_form_registry.dart';  // 导入我们刚才创建的表单注册器

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

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Flutter Form Registry Demo',
      theme: ThemeData(
        primarySwatch: Colors.blue,
      ),
      home: MyFormScreen(),
    );
  }
}

class MyFormScreen extends StatefulWidget {
  @override
  _MyFormScreenState createState() => _MyFormScreenState();
}

class _MyFormScreenState extends State<MyFormScreen> {
  final MyFormRegistry _formRegistry = MyFormRegistry();
  final GlobalKey<FormState> _formKey = GlobalKey<FormState>();

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text('Form Registry Demo'),
      ),
      body: Padding(
        padding: const EdgeInsets.all(16.0),
        child: Form(
          key: _formKey,
          child: _formRegistry.buildForm(context),
        ),
      ),
      floatingActionButton: FloatingActionButton(
        onPressed: () {
          if (_formKey.currentState!.validate()) {
            // 如果验证通过,可以在这里处理表单提交
            ScaffoldMessenger.of(context).showSnackBar(
              SnackBar(content: Text('Form submitted successfully')),
            );
          }
        },
        tooltip: 'Submit',
        child: Icon(Icons.send),
      ),
    );
  }
}

3. 运行应用

现在,你可以运行你的Flutter应用,应该会看到一个包含用户名、邮箱和密码字段的表单。当你点击提交按钮时,表单字段会被验证,如果有任何字段未通过验证,会显示相应的错误信息。

这个示例展示了如何使用flutter_form_registry插件来注册和管理表单字段,使得表单的创建和验证更加模块化和可重用。希望这对你有所帮助!

回到顶部