Flutter表单构建插件katana_form的使用

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

Flutter表单构建插件katana_form的使用

简介

表单实现是应用程序中非常重要的一部分。它已经成为用户将信息输入到应用程序中的不可或缺的接口。简化表单的实现可以大大加快应用程序的开发速度并提高安全性。

Flutter 提供了 FormField 类型的小部件,如 FormTextFormField。然而,这些小部件并没有解决数据处理的问题,数据获取和存储需要为每个状态管理系统进行实现。此外,虽然可以通过 InputDecoration 更改设计,但由于设置项较多,希望能够像 ButtonStyle 一样简化使用。

为此,创建了 katana_form 插件,它可以:

  • 通过将值存储在 FormController 中并在表单中传递来实现输入/输出。
  • 通过提供 FormStyle 统一所有表单小部件的设计规范,使设计统一更加简单。

安装

要使用 katana_form 插件,请执行以下命令:

flutter pub add katana_form

实现

创建控制器

首先,定义一个带有初始值的 FormController。对于新数据创建,传递一个空对象;对于现有数据,插入从数据库读取的值。此示例使用 Map<String, dynamic> 来处理数据库数据。

// 新数据
final form = FormController<Map<String, dynamic>>({});

// 现有数据
final Map<String, dynamic> data = getRepositoryData();
final form = FormController(data);

FormController 继承自 ChangeNotifier,因此可以与 riverpodChangeNotifierProvider 等状态管理机制结合使用。

表单实现

不需要安装 Form 小部件。只需传递你创建的 FormController,并且如果传递了 FormController,则必须传递 onSaved(如果你只想使用 onChanged,则不需要传递 FormController)。

传递初始值时,直接传递从 FormController.value 获取的值。onSaved 是一个回调函数,接收当前输入的值,确保返回更新后的 FormController.value

FormTextField(
  form: form,
  initialValue: form.value["description"],
  onSaved: (value) => {...form.value, "description": value},
),
表单验证和存储

通过执行 FormController.validate 可以验证和保存表单。首先进行验证,如果失败则返回 null;如果成功,则返回每个 Form 小部件的 onSaved 修改后的值。基于该值更新数据库。

final value = form.validate(); // 验证并获取表单值
if (value == null) {
  return;
}
print(value);
// 保存值
示例代码

以下是一个完整的示例代码,展示了如何使用 katana_form 构建一个表单页面,并在页面销毁时正确释放 FormController

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

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

enum Selection {
  one,
  two,
  three,
}

class MyApp extends StatelessWidget {
  const MyApp({super.key});

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      home: const FormPage(),
      title: "Flutter Demo",
      theme: ThemeData(
        primarySwatch: Colors.blue,
      ),
    );
  }
}

class FormPage extends StatefulWidget {
  const FormPage({super.key});

  @override
  State<FormPage> createState() => FormPageState();
}

class FormPageState extends State<FormPage> {
  final form = FormController<Map<String, dynamic>>({
    "name": "aaaa",
    "description": "bbb",
    "date": DateTime.now(),
    "monthDay": DateTime.now(),
    "number": 100,
  });

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

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: const Text("App Demo")),
      body: ListView(
        padding: const EdgeInsets.only(bottom: 32),
        children: [
          FormMedia(
            form: form,
            onTap: (ref) {
              ref.update(Uri.parse("assets/default.png"), FormMediaType.image);
            },
            builder: (context, value) {
              return Image.asset(
                value.uri!.toString(),
                fit: BoxFit.cover,
              );
            },
            onSaved: (value) => {...form.value, "media": value},
          ),
          const FormLabel("Name"),
          FormTextField(
            form: form,
            initialValue: form.value["name"],
            onSaved: (value) => {...form.value, "name": value},
            style: const FormStyle(
              border: OutlineInputBorder(),
              padding: EdgeInsets.symmetric(horizontal: 16),
              contentPadding: EdgeInsets.all(16),
            ),
          ),
          const FormLabel("Description"),
          FormTextField(
            form: form,
            minLines: 5,
            initialValue: form.value["description"],
            onSaved: (value) => {...form.value, "description": value},
          ),
          const FormLabel("DateTime Form"),
          FormDateTimeField(
            form: form,
            initialValue: form.value["date"],
            onSaved: (value) => {...form.value, "date": value},
          ),
          const FormLabel("Date Form"),
          FormDateField(
            form: form,
            initialValue: form.value["monthDay"],
            onSaved: (value) => {...form.value, "monthDay": value},
          ),
          const FormLabel("Number Form"),
          FormNumField(
            form: form,
            initialValue: form.value["number"],
            onSaved: (value) => {...form.value, "number": value},
          ),
          const FormLabel("Enum Form"),
          FormEnumField(
            form: form,
            initialValue: Selection.one,
            picker: FormEnumFieldPicker(
              values: Selection.values,
            ),
            onSaved: (value) => {...form.value, "enumSelect": value},
          ),
          const FormLabel("Map Form"),
          FormMapField(
            form: form,
            initialValue: "one",
            picker: FormMapFieldPicker(
              defaultKey: "one",
              data: {"one": "one", "two": "two", "three": "three"},
            ),
            onSaved: (value) => {...form.value, "mapSelect": value},
          ),
          const FormLabel("Multimedia Form"),
          FormMultiMedia(
            form: form,
            onTap: (ref) {
              ref.update(Uri.parse("assets/default.png"), FormMediaType.image);
            },
            builder: (context, value) {
              return Image.asset(
                value.uri!.toString(),
                fit: BoxFit.cover,
              );
            },
            onSaved: (value) => {...form.value, "multiMedia": value},
          ),
          const SizedBox(height: 16),
          FormButton(
            "Submit",
            icon: const Icon(Icons.add),
            onPressed: () {
              final value = form.validate(); // 验证并获取表单值
              if (value == null) {
                return;
              }
              print(value);
              // 保存值
            },
          ),
        ],
      ),
      floatingActionButton: FloatingActionButton(
        onPressed: () {},
        child: const Icon(Icons.check),
      ),
    );
  }
}

样式更改

每个 FormWidget 的样式可以通过 FormStyle 进行统一更改。默认样式是简洁的,但可以通过指定以下内容将其更改为带有边框的 Material 设计样式。

FormTextField(
  form: form,
  initialValue: form.value["name"],
  onSaved: (value) => {...form.value, "name": value},
  style: FormStyle(
      border: OutlineInputBorder(),
      padding: const EdgeInsets.symmetric(horizontal: 16),
      contentPadding: const EdgeInsets.all(16)),
),

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

1 回复

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


当然,以下是一个关于如何使用 katana_form 插件来构建 Flutter 表单的示例代码。katana_form 是一个强大的 Flutter 插件,用于快速创建和管理表单。

首先,确保在你的 pubspec.yaml 文件中添加 katana_form 依赖:

dependencies:
  flutter:
    sdk: flutter
  katana_form: ^最新版本号 # 请替换为当前最新版本号

然后运行 flutter pub get 来获取依赖。

以下是一个简单的示例,展示如何使用 katana_form 来创建一个包含文本字段和密码字段的登录表单:

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

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

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

class MyHomePage extends StatefulWidget {
  @override
  _MyHomePageState createState() => _MyHomePageState();
}

class _MyHomePageState extends State<MyHomePage> {
  final _formKey = GlobalKey<FormState>();
  late KatanaFormController _katanaFormController;

  @override
  void initState() {
    super.initState();
    _katanaFormController = KatanaFormController(
      fields: [
        KatanaFormField(
          name: 'email',
          type: KatanaFieldType.text,
          validators: [
            KatanaValidators.required('Email is required'),
            KatanaValidators.email('Invalid email address'),
          ],
          decoration: InputDecoration(labelText: 'Email'),
        ),
        KatanaFormField(
          name: 'password',
          type: KatanaFieldType.password,
          validators: [
            KatanaValidators.required('Password is required'),
            KatanaValidators.minLength(6, 'Password must be at least 6 characters long'),
          ],
          decoration: InputDecoration(labelText: 'Password'),
        ),
      ],
    );
  }

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

  void _submitForm() {
    if (_formKey.currentState!.validate()) {
      _katanaFormController.validateFields().then((result) {
        if (result.isValid) {
          // Handle valid form submission
          print('Form is valid: ${_katanaFormController.toMap()}');
        } else {
          // Handle invalid form fields
          result.errors.forEach((field, error) {
            ScaffoldMessenger.of(context).showSnackBar(SnackBar(
              content: Text("$field: $error"),
            ));
          });
        }
      });
    }
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text('Katana Form Example'),
      ),
      body: Padding(
        padding: const EdgeInsets.all(16.0),
        child: Form(
          key: _formKey,
          child: KatanaForm(
            controller: _katanaFormController,
            onFieldChanged: (field) {
              // Handle field changes if needed
            },
            child: Column(
              crossAxisAlignment: CrossAxisAlignment.start,
              children: [
                // KatanaForm will automatically generate fields based on the controller's configuration
              ],
            ),
          ),
        ),
      ),
      floatingActionButton: FloatingActionButton(
        onPressed: _submitForm,
        tooltip: 'Submit',
        child: Icon(Icons.send),
      ),
    );
  }
}

在这个示例中,我们创建了一个简单的登录表单,包含电子邮件和密码字段。KatanaFormController 用于定义和管理表单字段及其验证规则。KatanaForm 组件将根据 KatanaFormController 中的配置自动生成表单字段。

请注意,由于 katana_form 插件可能会不断更新,因此上述代码可能需要根据插件的最新文档进行调整。确保查看 katana_form 的官方文档以获取最新的使用指南和 API 参考。

回到顶部