Flutter表单构建插件easy_form_kit的使用

Flutter表单构建插件easy_form_kit的使用

EasyForm 是一组用于处理表单字段数据的控件。它类似于 Flutter 的 FormTextFormField 控件,并且与之紧密兼容。

EasyFormEasyTextFormField 控件将表单字段放在一个容器内,每个字段都有一个名称(通过 name 参数指定)。当保存表单时,表单字段的数据会被传递给 onSave 回调作为 Map<String, dynamic>,其中键是字段名,值是表单字段的值。

表单可以通过 EasyFormSaveButton 或者通过调用 EasyForm.of(context).save() 进行保存。

要插入文本字段,可以使用 EasyTextFormField 控件,对于其他类型的字段,可以直接在 widget 树中使用 EasyCustomFormField 控件或创建其继承类。

示例代码

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

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

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'EasyForm Example',
      theme: ThemeData(
        primarySwatch: Colors.teal,
      ),
      home: ExampleScreen(),
    );
  }
}

class ExampleScreen extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      body: SafeArea(
        child: Center(
          child: Padding(
            padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 24),
            child: EasyForm(
              onSave: (values, form) async {
                return Future.delayed(const Duration(seconds: 3), () {
                  return <String, dynamic>{
                    'hasError': false,
                  };
                });
              },
              onSaved: (response, values, form) {
                if (response['hasError']) {
                  _alert(context, response['error']);
                } else {
                  Navigator.of(context).push(
                    MaterialPageRoute(
                      builder: (context) => LoggedScreen(),
                    ),
                  );
                }
              },
              child: Column(
                crossAxisAlignment: CrossAxisAlignment.start,
                mainAxisSize: MainAxisSize.min,
                children: <Widget>[
                  EasyTextFormField(
                    name: 'username',
                    decoration: const InputDecoration(
                      hintText: 'Enter your username',
                    ),
                    validator: (value, [values]) {
                      if (value.isEmpty) {
                        return 'Please enter some text';
                      }
                      return null;
                    },
                  ),
                  const SizedBox(height: 16.0),
                  EasyTextFormField(
                    name: 'password',
                    decoration: const InputDecoration(
                      hintText: 'Enter your password',
                    ),
                    obscureText: true,
                    validator: (value, [values]) {
                      if (value.isEmpty) {
                        return 'Please enter some text';
                      }
                      return null;
                    },
                  ),
                  Padding(
                    padding: const EdgeInsets.symmetric(vertical: 24.0),
                    child: EasyFormSaveButton.text('Sign In'),
                  ),
                ],
              ),
            ),
          ),
        ),
      ),
    );
  }

  Future<void> _alert(BuildContext context, String text) async {
    return showDialog(
      context: context,
      builder: (context) => AlertDialog(
        title: Text('Error'),
        content: Text(text),
      ),
    );
  }
}

class LoggedScreen extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      body: SafeArea(
        child: Center(
          child: Column(
            mainAxisSize: MainAxisSize.min,
            children: [
              Text('Welcome, John.'),
              const SizedBox(height: 24),
              TextButton(
                child: Text('Back'),
                onPressed: () => Navigator.of(context).pop(),
              ),
            ],
          ),
        ),
      ),
    );
  }
}

数据处理

EasyForm

在表单保存期间,所有字段的值会被收集到一个 Map<String, dynamic> 中并传递给 onSave 回调。

onSave 回调是异步的,你可以在其中保存或发送值,等待响应后再传递给 onSaved。如果 onSave 返回空值,则会将表单字段的值映射传递给 onSaved

EasyDataForm

EasyForm 不同的是,在 EasyDataForm<T> 中,onSave 回调返回的结果必须与 EasyDataForm<T> 实例化时指定的类型 T 匹配。

EasyDataForm<T> 处理 onSave 回调结果的方式也有所不同:

  • EasyFormonSave 回调的结果传递给 onSaved 回调。如果 onSave 结果为 null(即 onSave 返回 null 或未指定),则传递表单字段的值映射。
  • EasyDataForm 始终仅将 onSave 回调的结果传递给 onSaved 回调,或者传递 null 如果 onSave 未指定。

错误处理

如果需要将外部来源(例如通过 API)的错误传递给字段,可以使用 EasyForm 构造函数的 errors 参数或 EasyFormStatesetErrors 方法。

使用构造函数设置错误

class SampleFormPage extends StatefulWidget {
  @override
  _SampleFormPageState createState() => _SampleFormPageState();
}

class _SampleFormPageState extends State<SampleFormPage> {
  Map<String, String> errors;

  @override
  Widget build(BuildContext context) {
    return EasyForm(
      errors: errors, // 这里设置字段中的错误。
      onSave: (values, form) async {
        return API.login(values['username'], values['password']);
      },
      onSaved: (response, values, form) {
        if (response.hasError) {
          setState(() {
            errors = response.fieldsErrors;
          });
        } else {
          // ... 导航到另一个屏幕
        }
      },
      child: Column(
        crossAxisAlignment: CrossAxisAlignment.start,
        children: <Widget>[
          EasyTextFormField(
            name: 'username',
            decoration: const InputDecoration(
              hintText: 'Enter your username',
            ),
            validator: (value) {
              if (value.isEmpty) {
                return 'Please enter some text';
              }
              return null;
            },
          ),
          const SizedBox(height: 16.0),
          EasyTextFormField(
            name: 'password',
            decoration: const InputDecoration(
              hintText: 'Enter your password',
            ),
            validator: (value) {
              if (value.isEmpty) {
                return 'Please enter some text';
              }
              return null;
            },
          ),
          Padding(
            padding: const EdgeInsets.symmetric(vertical: 24.0),
            child: EasyFormSaveButton.text('Sign In'),
          ),
        ],
      ),
    );
  }
}

使用 setErrors 方法

class SampleFormPage extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return EasyForm(
      onSave: (values, form) async {
        return API.login(values['username'], values['password']);
      },
      onSaved: (response, values, form) {
        if (response.hasError) {
          form.setErrors(response.fieldsErrors); // 这里设置字段中的错误。
        } else {
          // ... 导航到另一个屏幕
        }
      },
      child: Column(
        crossAxisAlignment: CrossAxisAlignment.start,
        children: <Widget>[
          EasyTextFormField(
            name: 'username',
            decoration: const InputDecoration(
              hintText: 'Enter your username',
            ),
            validator: (value) {
              if (value.isEmpty) {
                return 'Please enter some text';
              }
              return null;
            },
          ),
          const SizedBox(height: 16.0),
          EasyTextFormField(
            name: 'password',
            decoration: const InputDecoration(
              hintText: 'Enter your password',
            ),
            validator: (value) {
              if (value.isEmpty) {
                return 'Please enter some text';
              }
              return null;
            },
          ),
          Padding(
            padding: const EdgeInsets.symmetric(vertical: 24.0),
            child: EasyFormSaveButton.text('Sign In'),
          ),
        ],
      ),
    );
  }
}

自定义错误显示

对于非标准错误显示,或者希望在布局的其他位置显示错误,可以使用 EasyFormFieldError 小部件。

例如,以下示例中,错误消息显示在一个自定义字段的布局下方:

class CustomErrorDisplayPage extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return EasyForm(
      onSave: (values, form) async {
        return {
          'file': 'Invalid file.',
        };
      },
      onSaved: (response, values, form) {
        if (response != null) {
          form.setErrors(response);
        }
      },
      child: Column(
        crossAxisAlignment: CrossAxisAlignment.start,
        children: <Widget>[
          Row(
            children: [
              // 自定义表单字段。
              DocumentFormField(
                name: 'file',
              ),
              const SizedBox(width: 16),
              Text('Document to upload.'),
            ],
          ),

          // 错误消息将显示在这里。
          EasyFormFieldError(
            name: 'file',
            textStyle: TextStyle(color: Colors.red),
          ),

          const Divider(
            height: 24,
          ),

          EasyTextFormField(
            name: 'username',
            decoration: const InputDecoration(
              hintText: 'Enter your username',
            ),
            validator: (value) {
              if (value.isEmpty) {
                return 'Please enter some text';
              }
              return null;
            },
          ),
          const SizedBox(height: 16.0),
          EasyTextFormField(
            name: 'password',
            decoration: const InputDecoration(
              hintText: 'Enter your password',
            ),
            validator: (value) {
              if (value.isEmpty) {
                return 'Please enter some text';
              }
              return null;
            },
          ),
          Padding(
            padding: const EdgeInsets.symmetric(vertical: 24.0),
            child: EasyFormSaveButton.text('Sign In'),
          ),
        ],
      ),
    );
  }
}

自定义字段

对于文本字段,可以使用 EasyTextFormField 小部件,该小部件封装了 TextField 小部件及其所有属性。

EasyForm 可以轻松创建自定义类型的表单字段。

作为控制器,可以使用泛型 EasyFormFieldController 或创建其继承类:

class ColorController extends EasyFormFieldController<Color> {
  ColorController(super.value);
}

要创建字段,可以创建 EasyFormGenericField 的继承类并实现其 build 方法:

class ColorField extends EasyFormGenericField<Color> {
  const ColorField({
    super.key,
    required ColorController super.controller,
    super.onChange,
  });

  void _change() {
    value = _getRandomColor();
  }

  Color _getRandomColor() {
    return Color((Random().nextDouble() * 0xFFFFFF).toInt()).withOpacity(1.0);
  }

  @override
  Widget build(BuildContext context) {
    return Align(
      alignment: Alignment.centerLeft,
      child: GestureDetector(
        onTap: _change,
        child: Container(
          width: 50,
          height: 50,
          decoration: BoxDecoration(
            color: value,
            border: Border.all(
              color: Colors.grey,
              width: 2,
            ),
            shape: BoxShape.circle,
          ),
        ),
      ),
    );
  }
}

这个字段和控制器可以独立于 EasyForm 使用。

要在 EasyForm 中使用它们,可以通过 EasyCustomFormField 的泛型小部件直接使用:

EasyCustomFormField<Color, ColorController>(
  name: 'color',
  initialValue: Colors.teal,
  controllerBuilder: (value) => ColorController(value),
  builder: (fieldState, onChangedHandler) => ColorField(
    controller: fieldState.controller as ColorController,
    onChange: onChangedHandler,
  ),
),

或者创建 EasyCustomFormField 小部件的继承类:

class ColorFormField extends EasyCustomFormField<Color, ColorController> {
  ColorFormField({
    super.key,
    required super.name,
    super.controller,
    Color? initialValue,
  }) : super(
          initialValue: initialValue ?? const Color(0x00000000),
          controllerBuilder: (value) => ColorController(value),
          builder: (state, onChangedHandler) {
            return ColorField(
              controller: state.controller as ColorController,
              onChange: onChangedHandler,
            );
          },
        );
}

然后在表单中使用它:

ColorFormField(
  name: 'color',
  initialValue: Colors.teal,
),

状态自定义字段

EasyFormGenericField 小部件是一个状态小部件,如果你需要存储中间状态(例如选择照片),可以将上述字段代码转换为状态小部件:

class ColorField extends EasyFormGenericField<Color> {
  const ColorField({
    super.key,
    required ColorController super.controller,
    super.onChange,
  });

  @override
  ColorFieldState createState() => ColorFieldState();
}

class ColorFieldState extends EasyFormGenericFieldState<Color> {
  void _change() {
    value = _getRandomColor();
  }

  Color _getRandomColor() {
    return Color((Random().nextDouble() * 0xFFFFFF).toInt()).withOpacity(1.0);
  }

  @override
  Widget build(BuildContext context) {
    return Align(
      alignment: Alignment.centerLeft,
      child: GestureDetector(
        onTap: _change,
        child: Container(
          width: 50,
          height: 50,
          decoration: BoxDecoration(
            color: value,
            border: Border.all(
              color: Colors.grey,
              width: 2,
            ),
            shape: BoxShape.circle,
          ),
        ),
      ),
    );
  }
}

自定义

表单保存按钮、重置按钮、保存指示器和错误消息的外观和布局可以进行定制。

你可以使用 EasyFormDefaultSettings 设置默认外观应用于所有底层小部件。

通常这些设置是在 MaterialApp.builder 中设置的:

return MaterialApp(
  ...
  builder: (context, child) => EasyFormDefaultSettings(
    saveButton: EasyFormSaveButtonSettings(
      builder: (context, key, child, onPressed, adaptivity) =>
        ElevatedButton(
          style: ElevatedButton.styleFrom(
            foregroundColor: Colors.white,
            backgroundColor: Colors.amber.shade700,
          ),
          onPressed: onPressed,
          child: child ?? const SizedBox.shrink(),
        ),
    ),
  ),
  ...
);

这样可以为以下内容设置默认设置:

  • EasyFormSaveButton
    • builder
    • layoutBuilder
    • indicatorBuilder
  • EasyFormResetButton
    • builder
  • EasyFormActionButton
    • builder
  • EasyFormSaveIndicator
    • builder
    • layoutBuilder
  • EasyFormFieldError
    • builder

表单按钮

为了方便起见,EasyForm 提供了三种类型的按钮:

  • EasyFormSaveButton - 用于保存表单;
  • EasyFormResetButton - 用于将表单恢复到初始状态;
  • EasyFormButton - 用于任何表单操作;

EasyFormSaveButtonEasyFormResetButton 按钮各有两种构造函数,一种接受小部件作为按钮内容,另一种名为 text 接受文本作为按钮内容:

EasyFormSaveButton(child: Text('Save')),
// 或
EasyFormSaveButton.text('Save'),

按钮定制

默认情况下,EasyFormSaveButton 创建为 ElevatedButtonCupertinoButton.filled,而 EasyFormResetButton 创建为 OutlinedButtonCupertinoButton

可以通过 EasyForm 构造函数的 adaptivity 参数来定制按钮的外观:

  • EasyFormAdaptivity.auto - 根据平台选择按钮类型;
  • EasyFormAdaptivity.material - Material-design 按钮;
  • EasyFormAdaptivity.cupertion - Apple 设计按钮。

这些按钮可以通过覆盖创建按钮小部件本身的 builder 来定制。

builder 可以传递给构造函数:

EasyFormSaveButton.text(
  'Save',
  builder: (context, key, child, onPressed) => ElevatedButton(
    key: key,
    child: child,
    onPressed: onPressed,
  ),
),

… 或者全局重新定义,适用于整个应用程序(除了那些在构造函数中传递了 builder 的按钮)使用 EasyFormDefaultSettings

表单保存过程

在表单保存期间,EasyFormSaveButtonEasyFormResetButton 按钮会被禁用,EasyFormSaveButton 按钮的内容(例如文本)会被进度指示器替换。

默认情况下,指示器是一个 18x18 的 CircularProgressIndicatorCupertinoActivityIndicator,颜色与 ElevatedButton 的文本颜色相同。

可以通过在按钮构造函数中覆盖 builder 来定制指示器:

EasyFormSaveButton.text(
  'Save',
  indicatorBuilder: (context, size) => SizedBox.fromSize(
    size: size,
    child: CircularProgressIndicator.adaptive(
      strokeWidth: 2,
    ),
  ),
)

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

1 回复

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


当然,以下是一个关于如何在Flutter中使用easy_form_kit插件来构建表单的代码示例。easy_form_kit是一个用于快速创建和管理表单的Flutter插件,它提供了一系列方便的方法来定义和处理表单字段。

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

dependencies:
  flutter:
    sdk: flutter
  easy_form_kit: ^最新版本号 # 请替换为最新的版本号

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

接下来,是一个简单的示例,展示如何使用easy_form_kit来创建一个包含文本字段、数字字段和提交按钮的表单:

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

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

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      home: Scaffold(
        appBar: AppBar(
          title: Text('Easy Form Kit Demo'),
        ),
        body: EasyFormKitDemo(),
      ),
    );
  }
}

class EasyFormKitDemo extends StatefulWidget {
  @override
  _EasyFormKitDemoState createState() => _EasyFormKitDemoState();
}

class _EasyFormKitDemoState extends State<EasyFormKitDemo> {
  final _formKey = GlobalKey<FormState>();
  late EasyFormController _formController;

  @override
  void initState() {
    super.initState();
    _formController = EasyFormController(
      formFields: [
        EasyFormTextField(
          name: 'name',
          label: 'Name',
          validators: [
            EasyFormValidators.required(),
          ],
        ),
        EasyFormTextField(
          name: 'email',
          label: 'Email',
          keyboardType: TextInputType.emailAddress,
          validators: [
            EasyFormValidators.required(),
            EasyFormValidators.email(),
          ],
        ),
        EasyFormNumberField(
          name: 'age',
          label: 'Age',
          validators: [
            EasyFormValidators.required(),
            EasyFormValidators.numberRange(min: 0, max: 120),
          ],
        ),
      ],
    );
  }

  @override
  Widget build(BuildContext context) {
    return Form(
      key: _formKey,
      child: Padding(
        padding: const EdgeInsets.all(16.0),
        child: Column(
          children: [
            Expanded(
              child: EasyForm(
                controller: _formController,
              ),
            ),
            SizedBox(height: 20),
            ElevatedButton(
              onPressed: () {
                if (_formKey.currentState!.validate()) {
                  _formKey.currentState!.save();
                  final formValues = _formController.getValues();
                  print('Form Values: $formValues');
                  // 这里可以添加提交表单的逻辑,比如发送到服务器
                }
              },
              child: Text('Submit'),
            ),
          ],
        ),
      ),
    );
  }

  @override
  void dispose() {
    _formController.dispose();
    super.dispose();
  }
}

在这个示例中,我们做了以下几件事:

  1. 创建一个EasyFormController实例,并定义了表单字段(包括文本字段和数字字段),以及相应的验证规则。
  2. 使用FormEasyForm小部件来渲染表单。
  3. 在提交按钮的onPressed回调中,验证表单字段并获取表单值。

这个示例展示了如何使用easy_form_kit快速创建和管理表单,包括字段验证和值获取。你可以根据需要扩展和自定义这个示例,以满足你的具体需求。

回到顶部