Flutter表单输入简化插件easy_formz_inputs的使用
Flutter表单输入简化插件easy_formz_inputs的使用
easy_formz_inputs
库为 Dart 和 Flutter 应用程序提供了预构建且经过验证的输入字段。目前提供的输入字段包括电子邮件、密码、确认密码、URL、日期、电话和非空字段。
安装
要使用 easy_formz_inputs
库,首先在 pubspec.yaml
文件中添加相应的依赖:
dependencies:
easy_formz_inputs: ^0.1.1
然后,在代码中导入库:
import 'package:easy_formz_inputs/easy_formz_inputs.dart';
使用
easy_formz_inputs
库提供了预构建且经过验证的输入字段。每个输入字段都定义为一个表单字段,可以用于验证和管理用户输入数据。
EmailInput
EmailInput
是一个表单字段,用于验证用户输入的电子邮件地址。使用它时,只需创建 EmailInput
类的一个实例:
final email = EmailInput.dirty();
然后可以在表单中使用 email
表单字段:
TextField(
decoration: InputDecoration(labelText: 'Email'),
onChanged: (value) {
email.value = value;
},
keyboardType: TextInputType.emailAddress,
),
PasswordInput
PasswordInput
是一个表单字段,用于验证用户输入的密码长度和复杂性。使用它时,只需创建 PasswordInput
类的一个实例:
final password = PasswordInput.dirty();
然后可以在表单中使用 password
表单字段:
TextField(
decoration: InputDecoration(labelText: 'Password'),
onChanged: (value) {
password.value = value;
},
obscureText: true,
),
ConfirmPasswordInput
ConfirmPasswordInput
是一个表单字段,用于比较两个密码字段以确保它们匹配。使用它时,只需创建 ConfirmPasswordInput
类的一个实例,并将原始密码字段作为参数传递:
final confirmPassword = ConfirmPasswordInput.dirty(password: password, value: '');
然后可以在表单中使用 confirmPassword
表单字段:
TextField(
decoration: InputDecoration(labelText: 'Confirm password'),
onChanged: (value) {
confirmPassword.value = value;
},
obscureText: true,
),
UrlInput
UrlInput
是一个表单字段,用于验证用户输入的 URL。使用它时,只需创建 UrlInput
类的一个实例:
final url = UrlInput.dirty();
然后可以在表单中使用 url
表单字段:
TextField(
decoration: InputDecoration(labelText: 'URL'),
onChanged: (value) {
url.value = value;
},
keyboardType: TextInputType.url,
),
NonEmptyInput
NonEmptyInput
是一个表单字段,用于验证用户是否输入了非空值。使用它时,只需创建 NonEmptyInput
类的一个实例:
final nonEmpty = NonEmptyInput.dirty();
然后可以在表单中使用 nonEmpty
表单字段:
TextField(
decoration: InputDecoration(labelText: 'Non-empty field'),
onChanged: (value) {
nonEmpty.value = value;
},
),
DateInput
DateInput
是一个表单字段,用于验证用户输入的日期格式,格式为 ‘DD/MM/YYYY’。
final dateInput = DateInput.dirty(value: '09/03/2022');
然后可以在表单中使用 dateInput
表单字段:
TextField(
decoration: InputDecoration(labelText: 'Birthdate'),
onChanged: (value) {
dateInput.value = value;
},
),
PhoneInput
PhoneInput
是一个表单字段,用于验证用户输入的电话号码。使用它时,只需创建 PhoneInput
类的一个实例,并传入国家信息:
final phoneInput = PhoneInput.dirty(value: '12312312',
country: CountryModel(
name: 'Venezuela, Bolivarian Republic of Venezuela',
flag: '🇻🇪',
code: 'VE',
dialCode: '58',
minLength: 10,
maxLength: 10,
)
);
然后可以在表单中使用 phoneInput
表单字段:
TextField(
decoration: InputDecoration(labelText: 'Phone'),
onChanged: (value) {
phoneInput.value = value;
},
),
完整示例
以下是一个完整的示例,展示了如何使用上述各个输入字段:
// 忽略对于文件的公共成员API文档和构造函数排序规则
import 'dart:math';
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:formz/formz.dart';
import 'package:easy_formz_inputs/easy_formz_inputs.dart';
void main() => runApp(const MyApp());
class MyApp extends StatelessWidget {
const MyApp({Key? key}) : super(key: key);
[@override](/user/override)
Widget build(BuildContext context) {
return MaterialApp(
home: Scaffold(
appBar: AppBar(title: const Text('Easy Formz Inputs Example')),
body: const Padding(
padding: EdgeInsets.all(24),
child: SingleChildScrollView(child: MyForm()),
),
),
);
}
}
class MyForm extends StatefulWidget {
const MyForm({Key? key}) : super(key: key);
[@override](/user/override)
State<MyForm> createState() => _MyFormState();
}
class _MyFormState extends State<MyForm> {
final _key = GlobalKey<FormState>();
final country = const CountryModel(
name: 'Venezuela, Bolivarian Republic of Venezuela',
flag: '🇻🇪',
code: 'VE',
dialCode: '58',
minLength: 10,
maxLength: 10,
);
late MyFormState _state;
late final TextEditingController _emailController;
late final TextEditingController _passwordController;
late final TextEditingController _confirmPasswordController;
late final TextEditingController _websiteController;
late final TextEditingController _nameController;
late final TextEditingController _phoneController;
late final TextEditingController _birthdateController;
void _onEmailChanged() {
setState(() {
_state = _state.copyWith(
email: EmailInput.dirty(value: _emailController.text));
});
}
void _onPasswordChanged() {
setState(() {
_state = _state.copyWith(
password: PasswordInput.dirty(value: _passwordController.text),
);
});
}
void _onConfirmPasswordChanged() {
setState(() {
_state = _state.copyWith(
confirmPassword: ConfirmPasswordInput.dirty(
value: _confirmPasswordController.text,
password: _passwordController.text),
);
});
}
void _onNameChanged() {
setState(() {
_state = _state.copyWith(
name: NonEmptyInput.dirty(
value: _nameController.text,
));
});
}
void _onBirthdateChanged() {
setState(() {
_state = _state.copyWith(
birthdate: DateInput.dirty(
value: _birthdateController.text,
));
});
}
void _onPhoneChanged() {
setState(() {
_state = _state.copyWith(
phone: PhoneInput.dirty(
value: _phoneController.text,
country: country,
));
});
}
Future<void> _onSubmit() async {
if (!_key.currentState!.validate()) return;
setState(() {
_state = _state.copyWith(status: FormzSubmissionStatus.inProgress);
});
try {
await _submitForm();
_state = _state.copyWith(status: FormzSubmissionStatus.success);
} catch (_) {
_state = _state.copyWith(status: FormzSubmissionStatus.failure);
}
if (!mounted) return;
setState(() {});
FocusScope.of(context)
..nextFocus()
..unfocus();
const successSnackBar = SnackBar(
content: Text('Submitted successfully! 🎉'),
);
const failureSnackBar = SnackBar(
content: Text('Something went wrong... 🚨'),
);
ScaffoldMessenger.of(context)
..hideCurrentSnackBar()
..showSnackBar(
_state.status.isSuccess ? successSnackBar : failureSnackBar,
);
if (_state.status.isSuccess) _resetForm();
}
Future<void> _submitForm() async {
await Future<void>.delayed(const Duration(seconds: 1));
if (Random().nextInt(2) == 0) throw Exception();
}
void _resetForm() {
_key.currentState!.reset();
_emailController.clear();
_passwordController.clear();
setState(() => _state = MyFormState());
}
[@override](/user/override)
void initState() {
super.initState();
_state = MyFormState();
_emailController = TextEditingController(text: _state.email.value)
..addListener(_onEmailChanged);
_passwordController = TextEditingController(text: _state.password.value)
..addListener(_onPasswordChanged);
_confirmPasswordController =
TextEditingController(text: _state.confirmPassword.value)
..addListener(_onConfirmPasswordChanged);
_websiteController =
TextEditingController(text: _state.confirmPassword.value)
..addListener(_onConfirmPasswordChanged);
_phoneController = TextEditingController(text: _state.phone.value)
..addListener(_onPhoneChanged);
_birthdateController = TextEditingController(text: _state.birthdate.value)
..addListener(_onBirthdateChanged);
_nameController = TextEditingController(text: _state.name.value)
..addListener(_onNameChanged);
}
[@override](/user/override)
void dispose() {
_emailController.dispose();
_passwordController.dispose();
_confirmPasswordController.dispose();
_nameController.dispose();
_birthdateController.dispose();
_phoneController.dispose();
_websiteController.dispose();
super.dispose();
}
[@override](/user/override)
Widget build(BuildContext context) {
return Form(
key: _key,
child: Column(
children: [
TextFormField(
controller: _nameController,
decoration: const InputDecoration(
icon: Icon(Icons.person),
helperText: 'Name',
helperMaxLines: 2,
labelText: 'Name',
errorMaxLines: 2,
),
validator: (_) => _state.name.displayError.toString(),
textInputAction: TextInputAction.done,
),
Row(
children: [
InkWell(
borderRadius: BorderRadius.circular(10),
onTap: () async {},
child: Padding(
padding: const EdgeInsets.only(left: 10, bottom: 10),
child: Row(
mainAxisSize: MainAxisSize.min,
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
ClipRRect(
borderRadius: BorderRadius.circular(100),
child: Image.asset(
'assets/flags/${country.code.toLowerCase()}.png',
package: 'intl_phone_field',
width: 22,
height: 22,
fit: BoxFit.cover,
),
),
const SizedBox(width: 8),
FittedBox(
child: Text(
'+${country.dialCode}',
// style: widget.dropdownTextStyle,
),
),
const Icon(Icons.arrow_drop_down),
const SizedBox(width: 8),
],
),
),
),
Expanded(
child: TextFormField(
controller: _phoneController,
decoration: const InputDecoration(
helperText: 'Phone',
helperMaxLines: 2,
labelText: 'Phone',
errorMaxLines: 2,
),
inputFormatters: [FilteringTextInputFormatter.digitsOnly],
validator: (_) => _state.phone.displayError.toString(),
textInputAction: TextInputAction.done,
),
),
],
),
TextFormField(
controller: _emailController,
decoration: const InputDecoration(
icon: Icon(Icons.email),
labelText: 'Email',
helperText: 'A valid email e.g. joe.doe@gmail.com',
),
validator: (_) => _state.email.displayError.toString(),
keyboardType: TextInputType.emailAddress,
textInputAction: TextInputAction.next,
),
TextFormField(
controller: _passwordController,
decoration: const InputDecoration(
icon: Icon(Icons.lock),
helperText:
'At least 8 characters including one letter and number',
helperMaxLines: 2,
labelText: 'Password',
errorMaxLines: 2,
),
validator: (_) => _state.password.displayError.toString(),
obscureText: true,
textInputAction: TextInputAction.done,
),
TextFormField(
controller: _confirmPasswordController,
decoration: const InputDecoration(
icon: Icon(Icons.lock),
helperText: 'Confirm password',
helperMaxLines: 2,
labelText: 'Confirm Password',
errorMaxLines: 2,
),
validator: (_) => _state.confirmPassword.displayError.toString(),
obscureText: true,
textInputAction: TextInputAction.done,
),
TextFormField(
controller: _websiteController,
decoration: const InputDecoration(
icon: Icon(Icons.web),
helperText: 'Website',
helperMaxLines: 2,
labelText: 'Website',
errorMaxLines: 2,
),
validator: (_) => _state.website.displayError.toString(),
textInputAction: TextInputAction.done,
),
TextFormField(
controller: _birthdateController,
decoration: const InputDecoration(
hintText: 'DD/MM/YYYY',
icon: Icon(
Icons.calendar_month,
),
),
keyboardType: TextInputType.datetime,
inputFormatters: [
FilteringTextInputFormatter.digitsOnly,
LengthLimitingTextInputFormatter(8),
DateTextFormatter()
],
validator: (_) => _state.birthdate.displayError.toString(),
textInputAction: TextInputAction.next,
),
const SizedBox(height: 24),
if (_state.status.isInProgress)
const CircularProgressIndicator()
else
ElevatedButton(
onPressed: _onSubmit,
child: const Text('Submit'),
),
],
),
);
}
}
class MyFormState with FormzMixin {
MyFormState({
this.email = const EmailInput.pure(),
this.password = const PasswordInput.pure(),
this.confirmPassword = const ConfirmPasswordInput.pure(),
this.website = const UrlInput.pure(),
this.birthdate = const DateInput.pure(),
this.phone = const PhoneInput.pure(),
this.name = const NonEmptyInput.pure(),
this.status = FormzSubmissionStatus.initial,
});
final EmailInput email;
final PasswordInput password;
final ConfirmPasswordInput confirmPassword;
final UrlInput website;
final DateInput birthdate;
final PhoneInput phone;
final NonEmptyInput name;
final FormzSubmissionStatus status;
MyFormState copyWith({
EmailInput? email,
PasswordInput? password,
ConfirmPasswordInput? confirmPassword,
UrlInput? website,
DateInput? birthdate,
PhoneInput? phone,
NonEmptyInput? name,
FormzSubmissionStatus? status,
}) {
return MyFormState(
email: email ?? this.email,
password: password ?? this.password,
confirmPassword: confirmPassword ?? this.confirmPassword,
website: website ?? this.website,
birthdate: birthdate ?? this.birthdate,
phone: phone ?? this.phone,
name: name ?? this.name,
status: status ?? this.status,
);
}
[@override](/user/override)
List<FormzInput<dynamic, dynamic>> get inputs => [email, password];
}
class DateTextFormatter extends TextInputFormatter {
static const _maxChars = 8;
[@override](/user/override)
TextEditingValue formatEditUpdate(
TextEditingValue oldValue,
TextEditingValue newValue,
) {
const separator = '/';
final text = _format(
newValue.text,
oldValue.text,
separator,
);
return newValue.copyWith(
text: text,
selection: updateCursorPosition(
oldValue,
text,
),
);
}
String _format(
String value,
String oldValue,
String separator,
) {
final isErasing = value.length < oldValue.length;
final isComplete = value.length > _maxChars + 2;
if (!isErasing && isComplete) {
return oldValue;
}
value = value.replaceAll(separator, '');
final result = <String>[];
for (var i = 0; i < min(value.length, _maxChars); i++) {
result.add(value[i]);
if ((i == 1 || i == 3) && i != value.length - 1) {
result.add(separator);
}
}
return result.join();
}
TextSelection updateCursorPosition(
TextEditingValue oldValue,
String text,
) {
final endOffset = max(
oldValue.text.length - oldValue.selection.end,
0,
);
final selectionEnd = text.length - endOffset;
return TextSelection.fromPosition(TextPosition(offset: selectionEnd));
}
}
更多关于Flutter表单输入简化插件easy_formz_inputs的使用的实战教程也可以访问 https://www.itying.com/category-92-b0.html
更多关于Flutter表单输入简化插件easy_formz_inputs的使用的实战系列教程也可以访问 https://www.itying.com/category-92-b0.html
当然,以下是如何在Flutter项目中使用easy_formz_inputs
插件来简化表单输入的示例代码。这个插件通常与Formz
库一起使用,以更好地管理表单状态和验证。
首先,确保在你的pubspec.yaml
文件中添加必要的依赖项:
dependencies:
flutter:
sdk: flutter
formz: ^x.y.z # 替换为最新版本号
easy_formz_inputs: ^x.y.z # 替换为最新版本号
然后,运行flutter pub get
来安装这些依赖项。
示例代码
以下是一个完整的示例,展示了如何使用easy_formz_inputs
来创建一个简单的登录表单。
1. 定义表单模型
首先,我们需要定义表单的模型。使用Formz
库来创建表单状态和输入类。
// models/authentication_form.dart
import 'package:formz/formz.dart';
enum AuthenticationFormError {
invalidEmail,
invalidPassword,
}
class AuthenticationForm extends FormzInput<Map<String, String>, AuthenticationFormError> {
AuthenticationForm() : super(Map.fromEntries([
MapEntry('email', ''),
MapEntry('password', ''),
]));
String get email => value['email'] ?? '';
String get password => value['password'] ?? '';
AuthenticationForm.from(Map<String, String> value) : super(value);
@override
Map<AuthenticationFormError, String> validate() {
final errors = <AuthenticationFormError, String>{};
if (!RegExp(r"^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$").hasMatch(email)) {
errors[AuthenticationFormError.invalidEmail] = 'Email must be valid.';
}
if (password.length < 6) {
errors[AuthenticationFormError.invalidPassword] = 'Password must be at least 6 characters.';
}
return errors;
}
}
2. 创建UI
接下来,我们创建UI并使用easy_formz_inputs
来简化表单输入。
// main.dart
import 'package:flutter/material.dart';
import 'package:easy_formz_inputs/easy_formz_inputs.dart';
import 'models/authentication_form.dart';
void main() {
runApp(MyApp());
}
class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return MaterialApp(
home: AuthenticationScreen(),
);
}
}
class AuthenticationScreen extends StatefulWidget {
@override
_AuthenticationScreenState createState() => _AuthenticationScreenState();
}
class _AuthenticationScreenState extends State<AuthenticationScreen> {
final _form = AuthenticationForm();
void _submit() {
if (_form.validate().isEmpty) {
// Handle valid form submission
print('Form submitted: $_form.value');
}
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('Authentication'),
),
body: Padding(
padding: const EdgeInsets.all(16.0),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
EasyFormzTextFormField<AuthenticationForm, AuthenticationFormError>(
form: _form,
field: 'email',
label: 'Email',
errorMessages: {
AuthenticationFormError.invalidEmail: 'Email must be valid.',
},
keyboardType: TextInputType.emailAddress,
autovalidateMode: AutovalidateMode.onUserInteraction,
),
SizedBox(height: 16),
EasyFormzTextFormField<AuthenticationForm, AuthenticationFormError>(
form: _form,
field: 'password',
label: 'Password',
errorMessages: {
AuthenticationFormError.invalidPassword: 'Password must be at least 6 characters.',
},
obscureText: true,
autovalidateMode: AutovalidateMode.onUserInteraction,
),
SizedBox(height: 24),
ElevatedButton(
onPressed: _submit,
child: Text('Submit'),
),
],
),
),
);
}
}
解释
-
定义表单模型:我们创建了一个
AuthenticationForm
类,它继承自FormzInput
。我们定义了两个字段email
和password
,并实现了验证逻辑。 -
创建UI:我们使用
EasyFormzTextFormField
来创建表单输入字段。这个组件自动处理与Formz
模型的交互,包括验证和显示错误信息。 -
提交表单:当用户点击提交按钮时,我们检查表单是否有效,并在控制台中打印表单值。
这个示例展示了如何使用easy_formz_inputs
来简化表单输入和验证过程。你可以根据需要进一步扩展和自定义这个示例。