Flutter 表单处理完全指南
·
Flutter 表单处理完全指南
引言
表单是移动应用中不可或缺的一部分,Flutter 提供了强大的表单处理能力。本文将深入探讨 Flutter 表单的各种用法和高级技巧。
基础概念回顾
核心组件
- Form: 表单容器
- TextFormField: 文本输入字段
- FormState: 表单状态管理
- GlobalKey: 全局键用于访问表单状态
基本语法
final _formKey = GlobalKey<FormState>();
Form(
key: _formKey,
child: Column(
children: [
TextFormField(
validator: (value) {
if (value == null || value.isEmpty) {
return 'Please enter some text';
}
return null;
},
),
ElevatedButton(
onPressed: () {
if (_formKey.currentState!.validate()) {
// 表单验证通过
}
},
child: const Text('Submit'),
),
],
),
)
高级技巧一:表单验证
基础验证
TextFormField(
decoration: const InputDecoration(labelText: 'Email'),
validator: (value) {
if (value == null || value.isEmpty) {
return 'Please enter email';
}
if (!RegExp(r'^[\w-\.]+@([\w-]+\.)+[\w-]{2,4}$').hasMatch(value)) {
return 'Please enter valid email';
}
return null;
},
)
自定义验证器
String? validatePassword(String? value) {
if (value == null || value.isEmpty) {
return 'Please enter password';
}
if (value.length < 8) {
return 'Password must be at least 8 characters';
}
if (!RegExp(r'[A-Z]').hasMatch(value)) {
return 'Password must contain uppercase letter';
}
return null;
}
TextFormField(
obscureText: true,
decoration: const InputDecoration(labelText: 'Password'),
validator: validatePassword,
)
验证多个字段
Form(
key: _formKey,
child: Column(
children: [
TextFormField(
decoration: const InputDecoration(labelText: 'Password'),
obscureText: true,
controller: _passwordController,
validator: (value) {
if (value == null || value.isEmpty) {
return 'Please enter password';
}
return null;
},
),
TextFormField(
decoration: const InputDecoration(labelText: 'Confirm Password'),
obscureText: true,
validator: (value) {
if (value != _passwordController.text) {
return 'Passwords do not match';
}
return null;
},
),
],
),
)
高级技巧二:表单状态管理
使用 TextEditingController
final _emailController = TextEditingController();
final _passwordController = TextEditingController();
@override
void dispose() {
_emailController.dispose();
_passwordController.dispose();
super.dispose();
}
TextFormField(
controller: _emailController,
decoration: const InputDecoration(labelText: 'Email'),
)
监听文本变化
@override
void initState() {
super.initState();
_emailController.addListener(() {
setState(() {
_isEmailValid = _emailController.text.isNotEmpty;
});
});
}
重置表单
ElevatedButton(
onPressed: () {
_formKey.currentState!.reset();
_emailController.clear();
_passwordController.clear();
},
child: const Text('Reset'),
)
高级技巧三:自定义表单字段
创建自定义字段
class CustomTextField extends StatelessWidget {
final String label;
final TextEditingController? controller;
final String? Function(String?)? validator;
const CustomTextField({
super.key,
required this.label,
this.controller,
this.validator,
});
@override
Widget build(BuildContext context) {
return TextFormField(
controller: controller,
decoration: InputDecoration(
labelText: label,
border: OutlineInputBorder(
borderRadius: BorderRadius.circular(8),
),
focusedBorder: OutlineInputBorder(
borderRadius: BorderRadius.circular(8),
borderSide: const BorderSide(color: Colors.blue),
),
),
validator: validator,
);
}
}
日期选择器字段
class DatePickerField extends StatefulWidget {
final String label;
final DateTime? initialDate;
const DatePickerField({
super.key,
required this.label,
this.initialDate,
});
@override
State<DatePickerField> createState() => _DatePickerFieldState();
}
class _DatePickerFieldState extends State<DatePickerField> {
DateTime? _selectedDate;
Future<void> _selectDate() async {
final picked = await showDatePicker(
context: context,
initialDate: _selectedDate ?? widget.initialDate ?? DateTime.now(),
firstDate: DateTime(2000),
lastDate: DateTime(2100),
);
if (picked != null) {
setState(() {
_selectedDate = picked;
});
}
}
@override
Widget build(BuildContext context) {
return TextFormField(
readOnly: true,
onTap: _selectDate,
decoration: InputDecoration(
labelText: widget.label,
hintText: _selectedDate?.toLocal().toString().split(' ')[0],
),
validator: (value) {
if (_selectedDate == null) {
return 'Please select a date';
}
return null;
},
);
}
}
实战案例:登录表单
class LoginForm extends StatefulWidget {
const LoginForm({super.key});
@override
State<LoginForm> createState() => _LoginFormState();
}
class _LoginFormState extends State<LoginForm> {
final _formKey = GlobalKey<FormState>();
final _emailController = TextEditingController();
final _passwordController = TextEditingController();
bool _isLoading = false;
Future<void> _submit() async {
if (_formKey.currentState!.validate()) {
setState(() => _isLoading = true);
try {
await authService.login(
_emailController.text,
_passwordController.text,
);
// 导航到主页
} catch (e) {
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(content: Text('Error: $e')),
);
} finally {
setState(() => _isLoading = false);
}
}
}
@override
void dispose() {
_emailController.dispose();
_passwordController.dispose();
super.dispose();
}
@override
Widget build(BuildContext context) {
return Form(
key: _formKey,
child: Column(
children: [
CustomTextField(
label: 'Email',
controller: _emailController,
validator: (value) {
if (value == null || value.isEmpty) {
return 'Please enter email';
}
if (!RegExp(r'^[\w-\.]+@([\w-]+\.)+[\w-]{2,4}$').hasMatch(value)) {
return 'Please enter valid email';
}
return null;
},
),
const SizedBox(height: 16),
CustomTextField(
label: 'Password',
controller: _passwordController,
validator: (value) {
if (value == null || value.isEmpty) {
return 'Please enter password';
}
if (value.length < 8) {
return 'Password must be at least 8 characters';
}
return null;
},
),
const SizedBox(height: 24),
ElevatedButton(
onPressed: _isLoading ? null : _submit,
child: _isLoading
? const CircularProgressIndicator()
: const Text('Login'),
),
],
),
);
}
}
实战案例:注册表单
class RegistrationForm extends StatefulWidget {
const RegistrationForm({super.key});
@override
State<RegistrationForm> createState() => _RegistrationFormState();
}
class _RegistrationFormState extends State<RegistrationForm> {
final _formKey = GlobalKey<FormState>();
final _emailController = TextEditingController();
final _passwordController = TextEditingController();
final _confirmPasswordController = TextEditingController();
String? _selectedRole;
@override
void dispose() {
_emailController.dispose();
_passwordController.dispose();
_confirmPasswordController.dispose();
super.dispose();
}
@override
Widget build(BuildContext context) {
return Form(
key: _formKey,
child: Column(
children: [
CustomTextField(
label: 'Email',
controller: _emailController,
validator: (value) {
if (value == null || value.isEmpty) return 'Please enter email';
if (!RegExp(r'^[\w-\.]+@([\w-]+\.)+[\w-]{2,4}$').hasMatch(value)) {
return 'Please enter valid email';
}
return null;
},
),
const SizedBox(height: 16),
CustomTextField(
label: 'Password',
controller: _passwordController,
validator: (value) {
if (value == null || value.isEmpty) return 'Please enter password';
if (value.length < 8) return 'Password must be at least 8 characters';
return null;
},
),
const SizedBox(height: 16),
CustomTextField(
label: 'Confirm Password',
controller: _confirmPasswordController,
validator: (value) {
if (value != _passwordController.text) {
return 'Passwords do not match';
}
return null;
},
),
const SizedBox(height: 16),
DropdownButtonFormField<String>(
value: _selectedRole,
hint: const Text('Select role'),
items: const [
DropdownMenuItem(value: 'user', child: Text('User')),
DropdownMenuItem(value: 'admin', child: Text('Admin')),
],
onChanged: (value) => setState(() => _selectedRole = value),
validator: (value) => value == null ? 'Please select role' : null,
),
const SizedBox(height: 24),
ElevatedButton(
onPressed: () {
if (_formKey.currentState!.validate()) {
// 提交表单
}
},
child: const Text('Register'),
),
],
),
);
}
}
实战案例:表单验证动画
class AnimatedFormField extends StatefulWidget {
final String label;
final TextEditingController? controller;
final String? Function(String?)? validator;
const AnimatedFormField({
super.key,
required this.label,
this.controller,
this.validator,
});
@override
State<AnimatedFormField> createState() => _AnimatedFormFieldState();
}
class _AnimatedFormFieldState extends State<AnimatedFormField> {
bool _isFocused = false;
bool _isValid = true;
@override
Widget build(BuildContext context) {
return FocusScope(
child: Focus(
onFocusChange: (focused) {
setState(() => _isFocused = focused);
if (!focused) {
setState(() => _isValid = widget.validator?.call(widget.controller?.text) == null);
}
},
child: AnimatedContainer(
duration: const Duration(milliseconds: 200),
padding: _isFocused ? const EdgeInsets.all(4) : const EdgeInsets.all(0),
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(8),
border: Border.all(
color: _isFocused
? (_isValid ? Colors.blue : Colors.red)
: Colors.grey,
width: _isFocused ? 2 : 1,
),
),
child: TextFormField(
controller: widget.controller,
decoration: InputDecoration(
labelText: widget.label,
border: InputBorder.none,
contentPadding: const EdgeInsets.all(12),
),
validator: widget.validator,
),
),
),
);
}
}
常见问题与解决方案
Q1:表单验证不生效?
A:确保使用 Form 包裹并提供 GlobalKey:
final _formKey = GlobalKey<FormState>();
Form(
key: _formKey,
child: TextFormField(validator: (value) {...}),
)
Q2:TextEditingController 内存泄漏?
A:在 dispose 中释放:
@override
void dispose() {
_controller.dispose();
super.dispose();
}
Q3:如何获取表单值?
A:使用 TextEditingController:
final value = _controller.text;
最佳实践
1. 分离验证逻辑
// validators.dart
String? validateEmail(String? value) {
if (value == null || value.isEmpty) return 'Please enter email';
if (!RegExp(r'^[\w-\.]+@([\w-]+\.)+[\w-]{2,4}$').hasMatch(value)) {
return 'Please enter valid email';
}
return null;
}
2. 使用 AutovalidateMode
Form(
autovalidateMode: AutovalidateMode.onUserInteraction,
child: TextFormField(validator: validateEmail),
)
3. 处理加载状态
ElevatedButton(
onPressed: _isLoading ? null : _submit,
child: _isLoading
? const CircularProgressIndicator()
: const Text('Submit'),
)
总结
Flutter 的表单处理功能非常强大。通过本文的学习,你应该能够:
- 创建和验证表单
- 使用 TextEditingController
- 创建自定义表单字段
- 实现表单验证动画
- 处理表单提交和加载状态
掌握这些技巧,能够帮助你创建更加用户友好的表单体验。
更多推荐
所有评论(0)