Flutter表单处理与验证完全指南
·
Flutter表单处理与验证完全指南
引言
表单是移动应用中最常见的交互元素之一,用于收集用户输入并进行验证。Flutter提供了强大的表单处理能力,从基础的TextField到复杂的表单验证,都有很好的支持。本文将深入探讨Flutter表单处理的各种技术和最佳实践。
基础表单组件
TextField组件
TextField(
decoration: InputDecoration(
labelText: '用户名',
hintText: '请输入用户名',
prefixIcon: const Icon(Icons.person),
border: const OutlineInputBorder(),
),
keyboardType: TextInputType.text,
textInputAction: TextInputAction.next,
onChanged: (value) {
// 实时处理输入
print('输入: $value');
},
onSubmitted: (value) {
// 提交时处理
print('提交: $value');
},
)
TextFormField组件
TextFormField是带有验证功能的TextField:
TextFormField(
decoration: const InputDecoration(
labelText: '邮箱',
hintText: '请输入邮箱地址',
),
validator: (value) {
if (value == null || value.isEmpty) {
return '请输入邮箱';
}
final emailRegex = RegExp(r'^[\w-\.]+@([\w-]+\.)+[\w-]{2,4}$');
if (!emailRegex.hasMatch(value)) {
return '请输入有效的邮箱地址';
}
return null;
},
)
Form组件
Form组件用于管理一组表单字段:
final _formKey = GlobalKey<FormState>();
Form(
key: _formKey,
autovalidateMode: AutovalidateMode.onUserInteraction,
child: Column(
children: [
TextFormField(
decoration: const InputDecoration(labelText: '用户名'),
validator: (value) {
if (value == null || value.isEmpty) {
return '请输入用户名';
}
return null;
},
),
const SizedBox(height: 16),
ElevatedButton(
onPressed: () {
if (_formKey.currentState!.validate()) {
// 表单验证通过
print('表单验证通过');
}
},
child: const Text('提交'),
),
],
),
)
表单验证
内置验证器
TextFormField(
// 必填验证
validator: (value) {
if (value == null || value.isEmpty) {
return '此字段必填';
}
return null;
},
)
TextFormField(
// 最小长度验证
validator: (value) {
if (value == null || value.length < 6) {
return '最少需要6个字符';
}
return null;
},
)
TextFormField(
// 最大长度验证
validator: (value) {
if (value != null && value.length > 50) {
return '最多允许50个字符';
}
return null;
},
)
正则表达式验证
// 邮箱验证
final emailRegex = RegExp(r'^[\w-\.]+@([\w-]+\.)+[\w-]{2,4}$');
TextFormField(
validator: (value) {
if (value == null || value.isEmpty) {
return '请输入邮箱';
}
if (!emailRegex.hasMatch(value)) {
return '请输入有效的邮箱地址';
}
return null;
},
)
// 手机号码验证
final phoneRegex = RegExp(r'^1[3-9]\d{9}$');
TextFormField(
keyboardType: TextInputType.phone,
validator: (value) {
if (value == null || value.isEmpty) {
return '请输入手机号';
}
if (!phoneRegex.hasMatch(value)) {
return '请输入有效的手机号';
}
return null;
},
)
// URL验证
final urlRegex = RegExp(r'https?:\/\/[\w-]+(\.[\w-]+)+([\w.,@?^=%&:/~+#-]*[\w@?^=%&/~+#-])?');
TextFormField(
validator: (value) {
if (value == null || value.isEmpty) {
return '请输入URL';
}
if (!urlRegex.hasMatch(value)) {
return '请输入有效的URL';
}
return null;
},
)
自定义验证器
class PasswordValidator {
static String? validate(String? value) {
if (value == null || value.isEmpty) {
return '请输入密码';
}
// 检查长度
if (value.length < 8) {
return '密码至少需要8个字符';
}
// 检查是否包含数字
if (!value.contains(RegExp(r'\d'))) {
return '密码必须包含至少一个数字';
}
// 检查是否包含字母
if (!value.contains(RegExp(r'[a-zA-Z]'))) {
return '密码必须包含至少一个字母';
}
// 检查是否包含特殊字符
if (!value.contains(RegExp(r'[!@#$%^&*(),.?":{}|<>]'))) {
return '密码必须包含至少一个特殊字符';
}
return null;
}
}
// 使用自定义验证器
TextFormField(
obscureText: true,
decoration: const InputDecoration(labelText: '密码'),
validator: PasswordValidator.validate,
)
表单状态管理
使用StatefulWidget管理状态
class LoginForm extends StatefulWidget {
const LoginForm({super.key});
@override
State<LoginForm> createState() => _LoginFormState();
}
class _LoginFormState extends State<LoginForm> {
final _formKey = GlobalKey<FormState>();
String _email = '';
String _password = '';
void _submitForm() {
if (_formKey.currentState!.validate()) {
_formKey.currentState!.save();
// 处理表单数据
print('邮箱: $_email, 密码: $_password');
}
}
@override
Widget build(BuildContext context) {
return Form(
key: _formKey,
child: Column(
children: [
TextFormField(
decoration: const InputDecoration(labelText: '邮箱'),
validator: (value) {
if (value == null || value.isEmpty) {
return '请输入邮箱';
}
return null;
},
onSaved: (value) {
_email = value ?? '';
},
),
TextFormField(
obscureText: true,
decoration: const InputDecoration(labelText: '密码'),
validator: (value) {
if (value == null || value.isEmpty) {
return '请输入密码';
}
return null;
},
onSaved: (value) {
_password = value ?? '';
},
),
ElevatedButton(
onPressed: _submitForm,
child: const Text('登录'),
),
],
),
);
}
}
使用TextEditingController
class MyForm extends StatefulWidget {
const MyForm({super.key});
@override
State<MyForm> createState() => _MyFormState();
}
class _MyFormState extends State<MyForm> {
final _emailController = TextEditingController();
final _passwordController = TextEditingController();
@override
void dispose() {
_emailController.dispose();
_passwordController.dispose();
super.dispose();
}
void _submit() {
final email = _emailController.text;
final password = _passwordController.text;
print('邮箱: $email, 密码: $password');
}
@override
Widget build(BuildContext context) {
return Column(
children: [
TextField(
controller: _emailController,
decoration: const InputDecoration(labelText: '邮箱'),
),
TextField(
controller: _passwordController,
obscureText: true,
decoration: const InputDecoration(labelText: '密码'),
),
ElevatedButton(
onPressed: _submit,
child: const Text('提交'),
),
],
);
}
}
高级表单功能
表单联动验证
class PasswordForm extends StatefulWidget {
const PasswordForm({super.key});
@override
State<PasswordForm> createState() => _PasswordFormState();
}
class _PasswordFormState extends State<PasswordForm> {
final _formKey = GlobalKey<FormState>();
final _passwordController = TextEditingController();
final _confirmController = TextEditingController();
@override
void dispose() {
_passwordController.dispose();
_confirmController.dispose();
super.dispose();
}
String? _validateConfirmPassword(String? value) {
if (value == null || value.isEmpty) {
return '请再次输入密码';
}
if (value != _passwordController.text) {
return '两次输入的密码不一致';
}
return null;
}
@override
Widget build(BuildContext context) {
return Form(
key: _formKey,
child: Column(
children: [
TextFormField(
controller: _passwordController,
obscureText: true,
decoration: const InputDecoration(labelText: '密码'),
validator: (value) {
if (value == null || value.isEmpty) {
return '请输入密码';
}
if (value.length < 8) {
return '密码至少需要8个字符';
}
return null;
},
),
TextFormField(
controller: _confirmController,
obscureText: true,
decoration: const InputDecoration(labelText: '确认密码'),
validator: _validateConfirmPassword,
),
ElevatedButton(
onPressed: () {
if (_formKey.currentState!.validate()) {
print('表单验证通过');
}
},
child: const Text('提交'),
),
],
),
);
}
}
动态表单字段
class DynamicForm extends StatefulWidget {
const DynamicForm({super.key});
@override
State<DynamicForm> createState() => _DynamicFormState();
}
class _DynamicFormState extends State<DynamicForm> {
final _formKey = GlobalKey<FormState>();
final List<TextEditingController> _controllers = [];
@override
void initState() {
super.initState();
_controllers.add(TextEditingController());
}
@override
void dispose() {
for (var controller in _controllers) {
controller.dispose();
}
super.dispose();
}
void _addField() {
setState(() {
_controllers.add(TextEditingController());
});
}
void _removeField(int index) {
setState(() {
_controllers[index].dispose();
_controllers.removeAt(index);
});
}
void _submit() {
if (_formKey.currentState!.validate()) {
final values = _controllers.map((c) => c.text).toList();
print('表单值: $values');
}
}
@override
Widget build(BuildContext context) {
return Form(
key: _formKey,
child: Column(
children: [
for (var i = 0; i < _controllers.length; i++)
Row(
children: [
Expanded(
child: TextFormField(
controller: _controllers[i],
decoration: InputDecoration(labelText: '字段 ${i + 1}'),
validator: (value) {
if (value == null || value.isEmpty) {
return '此字段必填';
}
return null;
},
),
),
IconButton(
icon: const Icon(Icons.remove),
onPressed: _controllers.length > 1 ? () => _removeField(i) : null,
),
],
),
ElevatedButton(
onPressed: _addField,
child: const Text('添加字段'),
),
ElevatedButton(
onPressed: _submit,
child: const Text('提交'),
),
],
),
);
}
}
异步验证
class AsyncValidationForm extends StatefulWidget {
const AsyncValidationForm({super.key});
@override
State<AsyncValidationForm> createState() => _AsyncValidationFormState();
}
class _AsyncValidationFormState extends State<AsyncValidationForm> {
final _formKey = GlobalKey<FormState>();
final _usernameController = TextEditingController();
bool _isChecking = false;
String? _validationError;
Future<void> _checkUsername() async {
setState(() {
_isChecking = true;
_validationError = null;
});
// 模拟API调用
await Future.delayed(const Duration(seconds: 1));
// 检查用户名是否已存在
final username = _usernameController.text;
if (username == 'admin') {
setState(() {
_validationError = '用户名已存在';
});
}
setState(() {
_isChecking = false;
});
}
@override
void dispose() {
_usernameController.dispose();
super.dispose();
}
@override
Widget build(BuildContext context) {
return Form(
key: _formKey,
child: Column(
children: [
TextFormField(
controller: _usernameController,
decoration: InputDecoration(
labelText: '用户名',
errorText: _validationError,
suffixIcon: _isChecking
? const CircularProgressIndicator()
: null,
),
onChanged: (_) => _checkUsername(),
),
ElevatedButton(
onPressed: () {
if (_formKey.currentState!.validate() && !_isChecking && _validationError == null) {
print('表单验证通过');
}
},
child: const Text('提交'),
),
],
),
);
}
}
表单样式定制
自定义输入框样式
TextField(
decoration: InputDecoration(
labelText: '自定义输入框',
hintText: '请输入内容',
labelStyle: const TextStyle(
color: Colors.blue,
fontSize: 16,
),
hintStyle: const TextStyle(
color: Colors.grey,
fontSize: 14,
),
border: OutlineInputBorder(
borderRadius: BorderRadius.circular(12),
borderSide: const BorderSide(
color: Colors.grey,
width: 2,
),
),
focusedBorder: OutlineInputBorder(
borderRadius: BorderRadius.circular(12),
borderSide: const BorderSide(
color: Colors.blue,
width: 2,
),
),
errorBorder: OutlineInputBorder(
borderRadius: BorderRadius.circular(12),
borderSide: const BorderSide(
color: Colors.red,
width: 2,
),
),
filled: true,
fillColor: Colors.grey[100],
contentPadding: const EdgeInsets.all(16),
),
)
带图标的输入框
TextField(
decoration: InputDecoration(
labelText: '搜索',
prefixIcon: const Icon(Icons.search),
prefixIconColor: Colors.grey,
suffixIcon: const Icon(Icons.clear),
suffixIconColor: Colors.grey,
border: const OutlineInputBorder(),
),
)
密码可见性切换
class PasswordField extends StatefulWidget {
const PasswordField({super.key});
@override
State<PasswordField> createState() => _PasswordFieldState();
}
class _PasswordFieldState extends State<PasswordField> {
bool _obscureText = true;
@override
Widget build(BuildContext context) {
return TextField(
obscureText: _obscureText,
decoration: InputDecoration(
labelText: '密码',
suffixIcon: IconButton(
icon: Icon(
_obscureText ? Icons.visibility : Icons.visibility_off,
),
onPressed: () {
setState(() {
_obscureText = !_obscureText;
});
},
),
border: const OutlineInputBorder(),
),
);
}
}
实战案例
案例1:登录表单
class LoginScreen extends StatelessWidget {
const LoginScreen({super.key});
@override
Widget build(BuildContext context) {
final emailController = TextEditingController();
final passwordController = TextEditingController();
return Scaffold(
appBar: AppBar(title: const Text('登录')),
body: Padding(
padding: const EdgeInsets.all(16),
child: Form(
child: Column(
children: [
TextFormField(
controller: emailController,
decoration: const InputDecoration(
labelText: '邮箱',
prefixIcon: Icon(Icons.email),
border: OutlineInputBorder(),
),
keyboardType: TextInputType.emailAddress,
validator: (value) {
if (value == null || value.isEmpty) {
return '请输入邮箱';
}
final emailRegex = RegExp(r'^[\w-\.]+@([\w-]+\.)+[\w-]{2,4}$');
if (!emailRegex.hasMatch(value)) {
return '请输入有效的邮箱';
}
return null;
},
),
const SizedBox(height: 16),
TextFormField(
controller: passwordController,
decoration: const InputDecoration(
labelText: '密码',
prefixIcon: Icon(Icons.lock),
border: OutlineInputBorder(),
),
obscureText: true,
validator: (value) {
if (value == null || value.isEmpty) {
return '请输入密码';
}
if (value.length < 6) {
return '密码至少6个字符';
}
return null;
},
),
const SizedBox(height: 24),
ElevatedButton(
onPressed: () {
// 处理登录逻辑
print('登录: ${emailController.text}');
},
style: ElevatedButton.styleFrom(
minimumSize: const Size.fromHeight(50),
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(12),
),
),
child: const Text('登录'),
),
],
),
),
),
);
}
}
案例2:注册表单
class RegisterScreen extends StatefulWidget {
const RegisterScreen({super.key});
@override
State<RegisterScreen> createState() => _RegisterScreenState();
}
class _RegisterScreenState extends State<RegisterScreen> {
final _formKey = GlobalKey<FormState>();
final _emailController = TextEditingController();
final _passwordController = TextEditingController();
final _confirmController = TextEditingController();
bool _agreed = false;
@override
void dispose() {
_emailController.dispose();
_passwordController.dispose();
_confirmController.dispose();
super.dispose();
}
void _submit() {
if (_formKey.currentState!.validate() && _agreed) {
// 处理注册逻辑
print('注册成功');
} else if (!_agreed) {
ScaffoldMessenger.of(context).showSnackBar(
const SnackBar(content: Text('请同意服务条款')),
);
}
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: const Text('注册')),
body: Padding(
padding: const EdgeInsets.all(16),
child: Form(
key: _formKey,
child: ListView(
children: [
TextFormField(
controller: _emailController,
decoration: const InputDecoration(
labelText: '邮箱',
border: OutlineInputBorder(),
),
keyboardType: TextInputType.emailAddress,
validator: (value) {
if (value == null || value.isEmpty) return '请输入邮箱';
if (!RegExp(r'^[\w-\.]+@([\w-]+\.)+[\w-]{2,4}$').hasMatch(value)) {
return '请输入有效的邮箱';
}
return null;
},
),
const SizedBox(height: 12),
TextFormField(
controller: _passwordController,
decoration: const InputDecoration(
labelText: '密码',
border: OutlineInputBorder(),
),
obscureText: true,
validator: (value) {
if (value == null || value.isEmpty) return '请输入密码';
if (value.length < 8) return '密码至少8个字符';
return null;
},
),
const SizedBox(height: 12),
TextFormField(
controller: _confirmController,
decoration: const InputDecoration(
labelText: '确认密码',
border: OutlineInputBorder(),
),
obscureText: true,
validator: (value) {
if (value == null || value.isEmpty) return '请再次输入密码';
if (value != _passwordController.text) return '两次密码不一致';
return null;
},
),
const SizedBox(height: 16),
Row(
children: [
Checkbox(
value: _agreed,
onChanged: (value) {
setState(() {
_agreed = value ?? false;
});
},
),
const Text('我已阅读并同意服务条款'),
],
),
const SizedBox(height: 24),
ElevatedButton(
onPressed: _submit,
style: ElevatedButton.styleFrom(
minimumSize: const Size.fromHeight(50),
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(12),
),
),
child: const Text('注册'),
),
],
),
),
),
);
}
}
表单验证最佳实践
1. 实时验证
TextFormField(
autovalidateMode: AutovalidateMode.onUserInteraction,
// ...
)
2. 错误信息清晰
// 好的错误信息
validator: (value) {
if (value == null || value.isEmpty) {
return '请输入用户名';
}
if (value.length < 3) {
return '用户名至少需要3个字符';
}
return null;
}
// 避免模糊的错误信息
validator: (value) {
if (value == null || value.isEmpty || value.length < 3) {
return '输入无效'; // 不好:用户不知道具体哪里错了
}
return null;
}
3. 输入限制
TextField(
maxLength: 50,
inputFormatters: [
FilteringTextInputFormatter.allow(RegExp(r'[a-zA-Z0-9]')),
],
)
4. 键盘类型适配
// 邮箱
TextField(keyboardType: TextInputType.emailAddress)
// 手机号
TextField(keyboardType: TextInputType.phone)
// 数字
TextField(keyboardType: TextInputType.number)
// 多行文本
TextField(
keyboardType: TextInputType.multiline,
maxLines: null,
)
5. 表单焦点管理
final _emailFocus = FocusNode();
final _passwordFocus = FocusNode();
TextField(
focusNode: _emailFocus,
textInputAction: TextInputAction.next,
onSubmitted: (_) {
FocusScope.of(context).requestFocus(_passwordFocus);
},
)
TextField(
focusNode: _passwordFocus,
textInputAction: TextInputAction.done,
onSubmitted: (_) {
_passwordFocus.unfocus();
// 提交表单
},
)
总结
Flutter提供了强大的表单处理能力,通过本文的学习,你应该掌握了:
- 基础组件:TextField、TextFormField、Form
- 表单验证:内置验证、正则表达式验证、自定义验证器
- 状态管理:StatefulWidget、TextEditingController
- 高级功能:表单联动、动态字段、异步验证
- 样式定制:自定义输入框样式、图标、密码可见性切换
核心要点:
- 使用
Form组件管理表单状态 - 使用
TextFormField进行表单验证 - 使用
TextEditingController控制输入内容 - 使用
validator函数进行验证 - 使用
GlobalKey<FormState>管理表单
掌握这些技能后,你可以创建出功能完善、用户体验良好的表单界面。
更多推荐
所有评论(0)