Flutter表单构建插件katana_form的使用
Flutter表单构建插件katana_form的使用
简介
表单实现是应用程序中非常重要的一部分。它已经成为用户将信息输入到应用程序中的不可或缺的接口。简化表单的实现可以大大加快应用程序的开发速度并提高安全性。
Flutter 提供了 FormField
类型的小部件,如 Form
和 TextFormField
。然而,这些小部件并没有解决数据处理的问题,数据获取和存储需要为每个状态管理系统进行实现。此外,虽然可以通过 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
,因此可以与 riverpod
的 ChangeNotifierProvider
等状态管理机制结合使用。
表单实现
不需要安装 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
更多关于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 参考。