Flutter FormBuilder – flutter_form_builder

该包通过消除构建表单、验证字段、响应更改以及收集最终用户输入所需的样板代码,帮助创建 Flutter 中的数据收集表单。

其中还包括 FormBuilder 的常见现成表单输入字段。这为您提供了一种便捷的方式来添加常见的现成输入字段,而不是从头开始创建自己的 FormBuilderField。


Pub Version GitHub Workflow Status Codecov CodeFactor Grade GitHub OSS Lifecycle

Buy me a coffee


示例

import 'package:flutter_form_builder/flutter_form_builder.dart';

...

final _formKey = GlobalKey<FormBuilderState>();

...

@override
Widget build(BuildContext context) {
  return Column(
    children: <Widget>[
      FormBuilder(
        key: _formKey,
        autovalidate: true,
        child: Column(
          children: <Widget>[
            FormBuilderFilterChip(
              name: 'filter_chip',
              decoration: InputDecoration(
                labelText: 'Select many options',
              ),
              options: [
                FormBuilderFieldOption(
                    value: 'Test', child: Text('Test')),
                FormBuilderFieldOption(
                    value: 'Test 1', child: Text('Test 1')),
                FormBuilderFieldOption(
                    value: 'Test 2', child: Text('Test 2')),
                FormBuilderFieldOption(
                    value: 'Test 3', child: Text('Test 3')),
                FormBuilderFieldOption(
                    value: 'Test 4', child: Text('Test 4')),
              ],
            ),
            FormBuilderChoiceChip(
              name: 'choice_chip',
              decoration: InputDecoration(
                labelText: 'Select an option',
              ),
              options: [
                FormBuilderFieldOption(
                    value: 'Test', child: Text('Test')),
                FormBuilderFieldOption(
                    value: 'Test 1', child: Text('Test 1')),
                FormBuilderFieldOption(
                    value: 'Test 2', child: Text('Test 2')),
                FormBuilderFieldOption(
                    value: 'Test 3', child: Text('Test 3')),
                FormBuilderFieldOption(
                    value: 'Test 4', child: Text('Test 4')),
              ],
            ),
            FormBuilderDateTimePicker(
              name: 'date',
              // onChanged: _onChanged,
              inputType: InputType.time,
              decoration: InputDecoration(
                labelText: 'Appointment Time',
              ),
              initialTime: TimeOfDay(hour: 8, minute: 0),
              // initialValue: DateTime.now(),
              // enabled: true,
            ),
            FormBuilderDateRangePicker(
              name: 'date_range',
              firstDate: DateTime(1970),
              lastDate: DateTime(2030),
              format: DateFormat('yyyy-MM-dd'),
              onChanged: _onChanged,
              decoration: InputDecoration(
                labelText: 'Date Range',
                helperText: 'Helper text',
                hintText: 'Hint text',
              ),
            ),
            FormBuilderSlider(
              name: 'slider',
              validator: FormBuilderValidators.compose([
                FormBuilderValidators.min(context, 6),
              ]),
              onChanged: _onChanged,
              min: 0.0,
              max: 10.0,
              initialValue: 7.0,
              divisions: 20,
              activeColor: Colors.red,
              inactiveColor: Colors.pink[100],
              decoration: InputDecoration(
                labelText: 'Number of things',
              ),
            ),
            FormBuilderCheckbox(
              name: 'accept_terms',
              initialValue: false,
              onChanged: _onChanged,
              title: RichText(
                text: TextSpan(
                  children: [
                    TextSpan(
                      text: 'I have read and agree to the ',
                      style: TextStyle(color: Colors.black),
                    ),
                    TextSpan(
                      text: 'Terms and Conditions',
                      style: TextStyle(color: Colors.blue),
                    ),
                  ],
                ),
              ),
              validator: FormBuilderValidators.equal(
                context,
                true,
                errorText:
                    'You must accept terms and conditions to continue',
              ),
            ),
            FormBuilderTextField(
              name: 'age',
              decoration: InputDecoration(
                labelText:
                    'This value is passed along to the [Text.maxLines] attribute of the [Text] widget used to display the hint text.',
              ),
              onChanged: _onChanged,
              // valueTransformer: (text) => num.tryParse(text),
              validator: FormBuilderValidators.compose([
                FormBuilderValidators.required(context),
                FormBuilderValidators.numeric(context),
                FormBuilderValidators.max(context, 70),
              ]),
              keyboardType: TextInputType.number,
            ),
            FormBuilderDropdown(
              name: 'gender',
              decoration: InputDecoration(
                labelText: 'Gender',
              ),
              // initialValue: 'Male',
              allowClear: true,
              hint: Text('Select Gender'),
              validator: FormBuilderValidators.compose(
                  [FormBuilderValidators.required(context)]),
              items: genderOptions
                  .map((gender) => DropdownMenuItem(
                        value: gender,
                        child: Text('$gender'),
                      ))
                  .toList(),
            ),
          ],
        ),
      ),
      Row(
        children: <Widget>[
          Expanded(
            child: MaterialButton(
              color: Theme.of(context).colorScheme.secondary,
              child: Text(
                "Submit",
                style: TextStyle(color: Colors.white),
              ),
              onPressed: () {
                _formKey.currentState.save();
                if (_formKey.currentState.validate()) {
                  print(_formKey.currentState.value);
                } else {
                  print("validation failed");
                }
              },
            ),
          ),
          SizedBox(width: 20),
          Expanded(
            child: MaterialButton(
              color: Theme.of(context).colorScheme.secondary,
              child: Text(
                "Reset",
                style: TextStyle(color: Colors.white),
              ),
              onPressed: () {
                _formKey.currentState.reset();
              },
            ),
          ),
        ],
      )
    ],
  );
}

输入控件

当前支持的字段包括

  • FormBuilderCheckbox – 单个复选框字段
  • FormBuilderCheckboxGroup – 用于多选的复选框列表
  • FormBuilderChoiceChip – 创建一个充当单选按钮的芯片。
  • FormBuilderDateRangePicker – 用于选择日期范围
  • FormBuilderDateTimePicker – 用于 `Date`、`Time` 和 `DateTime` 输入
  • FormBuilderDropdown – 用于从列表下拉中选择一个值
  • FormBuilderFilterChip – 创建一个充当复选框的芯片。
  • FormBuilderRadioGroup – 用于从单选控件列表中选择一个值
  • FormBuilderRangeSlider – 用于从一系列值中选择一个范围
  • FormBuilderSegmentedControl – 用于使用 `CupertinoSegmentedControl` 控件作为输入来选择一个值
  • FormBuilderSlider – 用于在滑块上选择一个数值
  • FormBuilderSwitch – 开/关切换字段
  • FormBuilderTextField – Material Design 文本框输入。

为了创建表单中的输入字段,以及标签和任何适用的验证,所有类型的输入都支持几个属性,即

属性 类型 默认值 必需 描述
名称 字符串 这将形成表单值 Map 中的键
initialValue T 输入字段的初始值
enabled 布尔值 确定字段控件是否接受用户输入。
装饰 InputDecoration InputDecoration() 定义用于装饰字段的边框、标签、图标和样式。
validator FormFieldValidator<T> 一个 `FormFieldValidator`,它将检查 `FormField` 中值的有效性
onChanged ValueChanged<T> 当字段值更改时,此事件函数将立即触发
valueTransformer ValueTransformer<T> 在保存到表单值之前转换字段值的功能。例如,将数字字段的 TextField 值从 `String` 转换为 `num`
其余属性将由使用的控件类型决定。

构建自己的自定义字段

要在 `FormBuilder` 中构建自己的字段,我们使用 `FormBuilderField`,它要求您定义自己的字段。

var options = ["Option 1", "Option 2", "Option 3"];

FormBuilderField(
  name: "name",
  validator: FormBuilderValidators.compose([
    FormBuilderValidators.required(context),
  ]),
  builder: (FormFieldState<dynamic> field) {
    return InputDecorator(
      decoration: InputDecoration(
        labelText: "Select option",
        contentPadding:
            EdgeInsets.only(top: 10.0, bottom: 0.0),
        border: InputBorder.none,
        errorText: field.errorText,
      ),
      child: Container(
        height: 200,
        child: CupertinoPicker(
          itemExtent: 30,
          children: options.map((c) => Text(c)).toList(),
          onSelectedItemChanged: (index) {
            field.didChange(options[index]);
          },
        ),
      ),
    );
  },
),

以编程方式更改字段值

您可以一次更改一个字段的值,如下所示

_formKey.currentState.fields['color_picker'].didChange(Colors.black);

或者一次更改多个字段的值,如下所示

_formKey.currentState.patchValue({
  'age': '50',
  'slider': 6.7,
  'filter_chip': ['Test 1'],
  'choice_chip': 'Test 2',
  'rate': 4,
  'chips_test': [
    Contact('Andrew', '[email protected]', 'https://d2gg9evh47fn9z.cloudfront.net/800px_COLOURBOX4057996.jpg'),
  ],
});

以编程方式触发错误

选项 1 – 使用 FormBuilder / FieldBuilderField 键

final _formKey = GlobalKey<FormBuilderState>();
final _emailFieldKey = GlobalKey<FormBuilderFieldState>();
...
FormBuilder(
  key: _formKey,
  child: Column(
    children: [
      FormBuilderTextField(
        key: _emailFieldKey
        name: 'email',
        decoration: InputDecoration(labelText: 'Email'),
        validator: FormBuilderValidators.compose([
          FormBuilderValidators.required(context),
          FormBuilderValidators.email(context),
        ]),
      ),
      RaisedButton(
        child: Text('Submit'),
        onPressed: () async {
          if(await checkIfEmailExists()){
            // Either invalidate using Form Key
            _formKey.currentState?.invalidateField(name: 'email', errorText: 'Email already taken.');
            // OR invalidate using Field Key
            _emailFieldKey.currentState?.invalidate('Email already taken.');
          }
        },
      ),
    ],
  ),
),

选项 2 – 使用 InputDecoration.errorText

声明一个变量来存储您的错误

String _emailError;

在 `InputDecoration` 中使用该变量作为 `errorText`

FormBuilderTextField(
  name: 'email',
  decoration: InputDecoration(
    labelText: 'Email',
    errorText: _emailError,
  ),
  validator: FormBuilderValidators.compose([
      FormBuilderValidators.required(context),
      FormBuilderValidators.email(context),
  ]),
),

设置错误文本

RaisedButton(
  child: Text('Submit'),
  onPressed: () async {
    setState(() => _emailError = null);
    if(await checkIfEmailExists()){
      setState(() => _emailError = 'Email already taken.');
    }
  },
),

条件验证

您还可以根据另一个字段的值来验证一个字段

FormBuilderRadioGroup(
  decoration: InputDecoration(labelText: 'My best language'),
  name: 'my_language',
  validator: FormBuilderValidators.required(context),
  options: [
    'Dart',
    'Kotlin',
    'Java',
    'Swift',
    'Objective-C',
    'Other'
  ]
    .map((lang) => FormBuilderFieldOption(value: lang))
    .toList(growable: false),
  ),
  FormBuilderTextField(
    name: 'specify',
    decoration:
        InputDecoration(labelText: 'If Other, please specify'),
    validator: (val) {
      if (_formKey.currentState.fields['my_language']?.value ==
              'Other' &&
          (val == null || val.isEmpty)) {
        return 'Kindly specify your language';
      }
      return null;
    },
  ),

生态系统

以下是可用于扩展 `flutter_form_builder` 功能的其他包。

支持

问题与 PRs

以报告错误、回答问题或 PRs 的形式提供的任何支持都非常感谢。

咖啡?

如果这个包在您的项目交付中对您有所帮助,或者您只是想支持这个包,一杯咖啡将不胜感激?

Buy me a coffee

GitHub

查看 Github