Flutter表单生成插件cumulations_form_generators的使用
Flutter表单生成插件cumulations_form_generators的使用
Cumulations Form Generator
Cumulations Form Generator 用于为数据类生成表单。它使用注解来生成表单。该包还包括一个表单生成器,基于 cumulation_form_annotations
包生成表单。
注解列表及其用途:
-
@FormGenerate: 用于为数据类生成表单。只需在类上添加此注解即可创建表单。它接受以下参数:
name
: 表单的标题。maxWidth
: 如果你希望定义表单的宽度。
-
@Field: 用于声明类中的变量作为表单字段。要使用它,只需在变量上添加此注解。你可以通过以下参数指定输入字段的类型:
label
: 字段的标签。isRequired
: 字段是否必需,默认为false
。FieldType
: 字段的类型。FieldType.TextInput
FieldType.TextArea
FieldType.CheckBox
FieldType.SingleSelect
FieldType.MultiSelect
FieldType.DatePicker
-
@Options: 此注解允许你为单选和多选字段提供预定义选项。
-
@DynamicOptions: 使用此注解可以动态提供选项。为了实现这一点,你需要在
CategoryMasterDataMapper
类下创建一个static getDropdownItemByGroupId
函数,在运行时提供选项。
安装包
在你的 pubspec.yaml
文件中添加以下依赖项:
dependencies:
cumulation_form_annotations: ^1.0.0
dev_dependencies:
build_runner:
cumulations_form_generators: ^1.0.0
或者使用以下命令:
dart pub add dev:cumulations_form_generators
dart pub add cumulation_form_annotations
然后运行 flutter pub get
或 dart pub get
。
使用
创建一个数据模型文件,格式为 <FileName>_form.dart
,并在类上添加 [@FormGenerate](/user/FormGenerate)
注解,并在字段上添加 [@Field](/user/Field)
和 [@Options](/user/Options)
或 [@DynamicOptions](/user/DynamicOptions)
(如果需要)。
注意: 文件名应包含 _form
。
import 'package:flutter/material.dart'; // 导入这个包到你的数据模型类中
import 'package:cumulations_form_annotations/cumulations_form_annotations.dart';
import 'package:project_package/SearchableMultiSelect.dart'; // 如果你使用的是 FieldType.MultiSelect
import 'package:project_package/date_picker_textfield.dart'; // 如果你使用的是 FieldType.DatePicker
part 'FileName_form.g.dart'; // 在文件顶部添加这一行
[@FormGenerate](/user/FormGenerate)("News letter subscription")
class FileName {
// 对于表单中的文本字段
// String 是首选的 TextInput 和 TextArea。但是,你可以使用任何类型,但在提交表单时必须将其从 String 转换为你所需的类型
[@Field](/user/Field)("First Name", FieldType.TextInput, requiredFiled: true)
String? fName;
[@Field](/user/Field)("Last Name", FieldType.TextInput)
String? lName;
[@Field](/user/Field)("Email", FieldType.TextInput)
String? email;
// 对于 SingleSelect 或 MultiSelect,除了 [@Field](/user/Field) 还需要使用 [@Options](/user/Options) 或 [@DynamicOptions](/user/DynamicOptions)
// 如果你使用 [@Options](/user/Options),必须传递 `Map<T, String>` 作为选项。其中 T 是键的类型,String 是值的类型,在这种情况下我们使用 int 作为键的类型
// 请保持变量的数据类型为 T,在我们的例子中 T 是 int
// 如果你使用 [@DynamicOptions](/user/DynamicOptions),必须传递要在下拉菜单中显示的选项数量
// 在这种情况下,你必须在 CategoryMasterDataMapper 类下实现静态的 getDropdownItemByGroupId 函数
[@Field](/user/Field)("Gender", FieldType.SingleSelect, requiredFiled: true)
[@Options](/user/Options)(options: {1: "Male", 2: "Female"})
int? gender;
// 如果你想在表单中使用 MultiSelect,需要在项目中添加 SearchableMultiselect 的代码并导入到数据模型类中
[@Field](/user/Field)("Topic interested In", FieldType.MultiSelect, requiredFiled: true)
[@DynamicOptions](/user/DynamicOptions)(2)
List<int>? multiSelect;
// 对于 DatePicker,需要在项目中使用 DatePickerTextField 并导入到数据模型类中
[@Field](/user/Field)("Date of Birth", FieldType.DatePicker, requiredFiled: true)
DateTime? date;
// 对于 CheckBox,需要使用 bool? 作为数据类型
[@Field](/user/Field)("Do you want to opt in for promotions", FieldType.CheckBox)
bool? opt_for_promotion;
// 对于表单中的文本区域
[@Field](/user/Field)("Address", FieldType.TextArea, requiredFiled: true)
String? address;
Subscriber({this.fName,
this.lName,
this.email,
this.gender,
this.opt_for_promotion,
this.multiSelect,
this.date,
this.address});
}
如果你使用了 [@DynamicOptions](/user/DynamicOptions)
,需要在 CategoryMasterDataMapper
类下实现 static getDropdownItemByGroupId
函数。
class CategoryMasterDataMapper {
static List<Topics> getDropdownItemByGroupId(int formId) {
switch (formId) {
case 2:
return [
Topics(1, "Sports"),
// 请保持 T 数据类型与字段定义的数据类型相同
Topics(2, "Politics"),
Topics(3, "International")
];
// 根据 formId 返回选项列表
default:
return [];
}
}
}
class Topics<T> {
T id;
String name;
Topics(this.id, this.name);
}
对于多选字段,你需要在项目中添加以下代码:
import 'package:aligned_dialog/aligned_dialog.dart';
import 'package:equatable/equatable.dart';
import 'package:flutter/material.dart';
class SearchableMultiselect extends StatefulWidget {
final List<OptionType> items;
final List<OptionType> selectedValues;
final Function(List<dynamic>) onSelect;
const SearchableMultiselect({Key? key,
required this.items,
required this.selectedValues,
required this.onSelect})
: super(key: key);
@override
State<SearchableMultiselect> createState() => _SearchableMultiselectState();
}
class _SearchableMultiselectState extends State<SearchableMultiselect> {
final GlobalKey _widgetKey = GlobalKey();
late final TextEditingController _textEditingController =
TextEditingController();
final FocusNode _focusNode1 = FocusNode();
@override
void initState() {
super.initState();
_textEditingController.text = widget.selectedValues.isNotEmpty
? "${widget.selectedValues.length} Selected"
: "";
print("new render");
}
@override
Widget build(BuildContext context) {
return TextFormField(
style: TextStyle(fontSize: 12),
key: _widgetKey,
focusNode: _focusNode1,
keyboardType: TextInputType.none,
decoration: const InputDecoration(
hintText: "Select", suffixIcon: Icon(Icons.arrow_drop_down)),
controller: _textEditingController,
onTap: () {
showAlignedDialog(
context: context,
builder: _localDialogBuilder,
followerAnchor: Alignment.topCenter,
targetAnchor: Alignment.bottomCenter,
avoidOverflow: true,
barrierColor: Colors.transparent);
},
);
}
WidgetBuilder get _localDialogBuilder {
final RenderBox renderBox =
_widgetKey.currentContext?.findRenderObject() as RenderBox;
return (BuildContext context) {
return Container(
constraints:
BoxConstraints(maxWidth: renderBox.size.width, maxHeight: 250),
child: Material(
elevation: 4.0,
child: _OptionList(
items: widget.items,
selectedValues: widget.selectedValues,
onSelect: (value, data) {
widget.onSelect(value);
setState(() {
_textEditingController.text = data;
});
},
)),
);
};
}
}
class _OptionList extends StatefulWidget {
final List<OptionType> items;
final List<OptionType> selectedValues;
final Function(List<dynamic>, String) onSelect;
const _OptionList({Key? key,
required this.items,
required this.selectedValues,
required this.onSelect})
: super(key: key);
@override
State<_OptionList> createState() => _OptionListState();
}
class _OptionListState extends State<_OptionList> {
late final TextEditingController _textFieldController =
TextEditingController();
final FocusNode _focusNode2 = FocusNode();
List<OptionType> _searchResult = [];
List<OptionType> _selectedList = [];
@override
void initState() {
super.initState();
_selectedList = widget.selectedValues;
}
@override
Widget build(BuildContext context) {
return Column(
mainAxisSize: MainAxisSize.min,
children: [
Container(
child: Padding(
padding: const EdgeInsets.all(0.2),
child: Card(
child: ListTile(
leading: const Icon(Icons.search),
title: TextField(
style: TextStyle(fontSize: 12),
focusNode: _focusNode2,
controller: _textFieldController,
decoration: const InputDecoration(
hintText: 'Search', border: InputBorder.none),
onChanged: _onSearchTextChanged,
),
),
),
),
),
Container(
constraints: BoxConstraints(maxHeight: 180),
child: _searchResult.length != 0 ||
_textFieldController.text.isNotEmpty
? SingleChildScrollView(
child: Column(
children: [
ListView.builder(
itemCount: _searchResult.length,
shrinkWrap: true,
physics: NeverScrollableScrollPhysics(),
itemBuilder: (context1, i) {
return GestureDetector(
onTap: () async {
if (_selectedList.contains(_searchResult[i])) {
_selectedList.remove(_searchResult[i]);
} else {
_selectedList.add(_searchResult[i]);
}
var selectedValue = <dynamic>[];
_selectedList.forEach((element) {
selectedValue.add(element.value);
});
widget.onSelect(
selectedValue,
_selectedList.isNotEmpty
? "${_selectedList.length} Selected"
: "");
setState(() {});
},
child: Card(
color: _selectedList.contains(_searchResult[i])
? Colors.green
: Colors.white,
margin: const EdgeInsets.all(0.0),
child: ListTile(
title: Text(
style: TextStyle(
fontSize: 12,
),
_searchResult[i].label),
),
),
);
},
),
],
),
)
: SingleChildScrollView(
child: Column(
children: [
ListView.builder(
itemCount: widget.items.length,
shrinkWrap: true,
physics: NeverScrollableScrollPhysics(),
itemBuilder: (context, index) {
return GestureDetector(
onTap: () {
if (_selectedList.contains(widget.items[index])) {
_selectedList.remove(widget.items[index]);
} else {
_selectedList.add(widget.items[index]);
}
var selectedValue = <dynamic>[];
_selectedList.forEach((element) {
selectedValue.add(element.value);
});
widget.onSelect(
selectedValue,
_selectedList.isNotEmpty
? "${_selectedList.length} Selected"
: "");
setState(() {});
},
child: Card(
color: _selectedList.contains(widget.items[index])
? Colors.green
: Colors.white,
margin: const EdgeInsets.all(0.0),
child: ListTile(
title: Text(
style: TextStyle(
fontSize: 12,
),
widget.items[index].label),
),
),
);
},
),
],
),
),
),
],
);
}
_onSearchTextChanged(String text) async {
print("text: $text");
_searchResult.clear();
if (text.isEmpty) {
setState(() {});
return;
}
RegExp pattern = RegExp(text, caseSensitive: false);
widget.items.forEach((value) {
if (value.label.contains(pattern)) {
_searchResult.add(value);
}
});
setState(() {});
}
}
class _AlwaysDisabledFocusNode extends FocusNode {
@override
bool get hasFocus => false;
}
class OptionType extends Equatable {
final dynamic value;
final String label;
OptionType({required this.value, required this.label});
@override
List<Object> get props => [value];
}
对于日期选择字段,你需要在项目中添加以下代码:
import 'package:flutter/material.dart';
import 'package:intl/intl.dart';
class TestPickerWidget extends StatefulWidget {
final DateTime? selectedDate;
final ValueSetter<DateTime> onSelectDate;
final DateTime? initialDate;
final DateTime? firstData;
final DateTime? lastDate;
const TestPickerWidget({
Key? key,
this.selectedDate,
this.initialDate,
this.firstData,
this.lastDate,
required this.onSelectDate,
}) : super(key: key);
@override
_TestPickerWidgetState createState() => _TestPickerWidgetState();
}
class _TestPickerWidgetState extends State<TestPickerWidget> {
late DateTime _selectedDate;
late final TextEditingController _textEditingController =
TextEditingController(
text: (widget.selectedDate != null)
? DateFormat("MM/dd/yyy").format(widget.selectedDate!)
: "");
@override
Widget build(BuildContext context) {
return Center(
child: TextFormField(
focusNode: AlwaysDisabledFocusNode(),
controller: _textEditingController,
onTap: () {
_selectDate(context);
},
decoration: const InputDecoration(
suffixIcon: Icon(Icons.calendar_month), hintText: "MM/DD/YYYY"),
));
}
/// 显示日期选择器并返回所选日期
_selectDate(BuildContext context) async {
DateTime? newSelectedDate = await showDatePicker(
context: context,
initialDate:
widget.initialDate ?? widget.selectedDate ?? DateTime.now(),
firstDate: widget.firstData ??
DateTime.fromMillisecondsSinceEpoch(DateTime
.now()
.year - 10),
lastDate: widget.lastDate ?? DateTime(DateTime
.now()
.year + 10),
currentDate: DateTime.now(),
builder: (context, child) {
return Theme(
data: ThemeData.light().copyWith(
colorScheme: ColorScheme.light(
primary: Color(0xFF2E2E48),
onPrimary: Colors.white,
),
// 这里我更改了 overline 的样式
textTheme: TextTheme(
overline:
TextStyle(color: Color(0xFF2E2E48), fontSize: 12)),
primaryTextTheme: TextTheme(
overline: TextStyle(color: Color(0xFF2E2E48), fontSize: 12))
// dialogBackgroundColor: Colors.white,
),
child: child!,
);
});
if (newSelectedDate != null) {
_selectedDate = newSelectedDate;
_textEditingController
..text = DateFormat("MM/dd/yyyy").format(_selectedDate)
..selection = TextSelection.fromPosition(TextPosition(
offset: _textEditingController.text.length,
affinity: TextAffinity.upstream));
widget.onSelectDate(_selectedDate);
}
}
}
class AlwaysDisabledFocusNode extends FocusNode {
@override
bool get hasFocus => false;
}
生成表单的命令
flutter pub run build_runner build
一旦运行上述命令,将生成一个新的文件,名为 <FileName>_form.g.dart
,位于数据模型文件所在的同一目录中。
你可以通过调用 getForm
方法使用数据模型对象获取表单。
@override
Widget build(BuildContext context) {
return Scaffold(
body: SingleChildScrollView(
child: Column(
children: [
dataModelObject.getForm()
]
)));
}
如果你想从表单获取数据,可以调用 getFormData
方法。这将返回一个映射。
dataModelObject.getFormData()
更多关于Flutter表单生成插件cumulations_form_generators的使用的实战教程也可以访问 https://www.itying.com/category-92-b0.html
更多关于Flutter表单生成插件cumulations_form_generators的使用的实战系列教程也可以访问 https://www.itying.com/category-92-b0.html
当然,以下是一个关于如何使用 cumulations_form_generators
插件在 Flutter 中生成表单的示例代码。这个插件可以大大简化表单字段的生成和管理。
首先,确保你已经在 pubspec.yaml
文件中添加了 cumulations_form_generators
依赖:
dependencies:
flutter:
sdk: flutter
cumulations_form_generators: ^最新版本号 # 请替换为实际的最新版本号
然后,运行 flutter pub get
来获取依赖。
接下来,你可以使用 CumulationsFormBuilders
来生成表单字段。以下是一个完整的示例代码:
import 'package:flutter/material.dart';
import 'package:cumulations_form_generators/cumulations_form_generators.dart';
void main() {
runApp(MyApp());
}
class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Flutter Demo',
theme: ThemeData(
primarySwatch: Colors.blue,
),
home: MyHomePage(),
);
}
}
class MyHomePage extends StatefulWidget {
@override
_MyHomePageState createState() => _MyHomePageState();
}
class _MyHomePageState extends State<MyHomePage> {
final _formKey = GlobalKey<FormState>();
// 用于存储表单数据
final Map<String, dynamic> _formData = {
'name': '',
'email': '',
'age': '',
};
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('Form Example'),
),
body: Padding(
padding: const EdgeInsets.all(16.0),
child: Form(
key: _formKey,
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: <Widget>[
// 使用 CumulationsFormBuilders 生成文本表单字段
CumulationsFormBuilders.textFormField(
'name',
labelText: 'Name',
validator: (value) {
if (value == null || value.isEmpty) {
return 'Please enter your name';
}
return null;
},
onSaved: (value) {
_formData['name'] = value;
},
),
CumulationsFormBuilders.textFormField(
'email',
labelText: 'Email',
keyboardType: TextInputType.emailAddress,
validator: (value) {
if (value == null || value.isEmpty || !value.contains('@')) {
return 'Please enter a valid email address';
}
return null;
},
onSaved: (value) {
_formData['email'] = value;
},
),
CumulationsFormBuilders.numberFormField(
'age',
labelText: 'Age',
validator: (value) {
if (value == null || value.isEmpty) {
return 'Please enter your age';
}
if (int.tryParse(value) == null || int.parse(value) <= 0) {
return 'Please enter a valid age';
}
return null;
},
onSaved: (value) {
_formData['age'] = value;
},
),
SizedBox(height: 20),
ElevatedButton(
onPressed: () {
if (_formKey.currentState!.validate()) {
_formKey.currentState!.save();
// 在这里处理表单数据,例如提交到服务器
print('Form Data: $_formData');
}
},
child: Text('Submit'),
),
],
),
),
),
);
}
}
在这个示例中,我们使用了 CumulationsFormBuilders
来生成三个表单字段:文本字段(用于名称和电子邮件),以及数字字段(用于年龄)。每个字段都包含了标签文本、验证器和保存回调。当用户点击提交按钮时,表单数据会被验证并保存,然后打印到控制台。
请确保你已经安装了最新版本的 cumulations_form_generators
插件,并根据需要调整字段和验证逻辑。