Flutter的表单系统基于Form + TextFormField组合,Form充当状态管理容器,TextFormField负责输入和验证。
// ✅ 验证通过:Flutter 3.22
class FormDemo extends StatefulWidget {
const FormDemo({super.key});
@override
State<FormDemo> createState() => _FormDemoState();
}
class _FormDemoState extends State<FormDemo> {
final _formKey = GlobalKey<FormState>();
final _nameCtl = TextEditingController();
final _emailCtl = TextEditingController();
final _passCtl = TextEditingController();
bool _obscure = true;
String _role = 'user';
bool _agree = false;
String? _vName(String? v) {
if (v == null || v.trim().isEmpty) return '请输入姓名';
if (v.trim().length < 2) return '姓名至少2个字符';
return null;
}
String? _vEmail(String? v) {
if (v == null || v.isEmpty) return '请输入邮箱';
if (!RegExp(r'^[\w-\.]+@([\w-]+\.)+[\w-]+$').hasMatch(v)) return '邮箱格式不正确';
return null;
}
String? _vPass(String? v) {
if (v == null || v.isEmpty) return '请输入密码';
if (v.length < 6) return '密码至少6位';
if (!v.contains(RegExp(r'[A-Z]'))) return '需含大写字母';
if (!v.contains(RegExp(r'[0-9]'))) return '需含数字';
return null;
}
void _submit() {
if (_formKey.currentState!.validate() && _agree) {
ScaffoldMessenger.of(context).showSnackBar(SnackBar(content: Text('注册成功!${_nameCtl.text}')));
} else if (!_agree) {
ScaffoldMessenger.of(context).showSnackBar(const SnackBar(content: Text('请同意条款')));
}
}
@override
void dispose() { _nameCtl.dispose(); _emailCtl.dispose(); _passCtl.dispose(); super.dispose(); }
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: const Text('注册表单')),
body: SingleChildScrollView(padding: const EdgeInsets.all(16), child: Form(key: _formKey, child: Column(
crossAxisAlignment: CrossAxisAlignment.stretch,
children: [
TextFormField(controller: _nameCtl, validator: _vName, decoration: const InputDecoration(labelText: '姓名', prefixIcon: Icon(Icons.person), border: OutlineInputBorder()), textInputAction: TextInputAction.next),
const SizedBox(height: 16),
TextFormField(controller: _emailCtl, validator: _vEmail, keyboardType: TextInputType.emailAddress, decoration: const InputDecoration(labelText: '邮箱', prefixIcon: Icon(Icons.email), border: OutlineInputBorder()), textInputAction: TextInputAction.next),
const SizedBox(height: 16),
TextFormField(controller: _passCtl, validator: _vPass, obscureText: _obscure, decoration: InputDecoration(labelText: '密码', prefixIcon: const Icon(Icons.lock), suffixIcon: IconButton(icon: Icon(_obscure ? Icons.visibility : Icons.visibility_off), onPressed: () => setState(() => _obscure = !_obscure)), border: const OutlineInputBorder())),
const SizedBox(height: 16),
DropdownButtonFormField<String>(value: _role, decoration: const InputDecoration(labelText: '角色', prefixIcon: Icon(Icons.badge), border: OutlineInputBorder()), items: const [DropdownMenuItem(value: 'user', child: Text('用户')), DropdownMenuItem(value: 'admin', child: Text('管理员'))], onChanged: (v) => setState(() => _role = v!)),
const SizedBox(height: 16),
CheckboxListTile(value: _agree, onChanged: (v) => setState(() => _agree = v ?? false), title: const Text('同意服务条款'), controlAffinity: ListTileControlAffinity.leading),
const SizedBox(height: 24),
FilledButton(onPressed: _submit, child: const Text('注册')),
],
))),
);
}
}
// ✅ 验证通过
class RatingFormField extends FormField<int> {
RatingFormField({super.key, int initialValue = 0, FormFieldSetter<int>? onSaved, FormFieldValidator<int>? validator}) : super(
initialValue: initialValue, onSaved: onSaved,
validator: validator ?? (v) => v == null || v == 0 ? '请选择评分' : null,
builder: (field) => Column(crossAxisAlignment: CrossAxisAlignment.start, children: [
Row(children: List.generate(5, (i) => IconButton(icon: Icon(i < (field.value ?? 0) ? Icons.star : Icons.star_border, color: i < (field.value ?? 0) ? Colors.amber : Colors.grey), onPressed: () => field.didChange(i + 1)))),
if (field.hasError) Padding(padding: const EdgeInsets.only(left: 12), child: Text(field.errorText!, style: const TextStyle(color: Colors.red, fontSize: 12))),
]),
);
}
class ImagePickerFormField extends FormField<String> {
ImagePickerFormField({super.key, FormFieldValidator<String>? validator}) : super(
validator: validator ?? (v) => v == null || v.isEmpty ? '请选择图片' : null,
builder: (field) => Column(crossAxisAlignment: CrossAxisAlignment.start, children: [
GestureDetector(onTap: () => field.didChange('image_selected.jpg'), child: Container(height: 150, width: double.infinity, decoration: BoxDecoration(color: Colors.grey[200], borderRadius: BorderRadius.circular(12), border: field.hasError ? Border.all(color: Colors.red) : null), child: field.value != null ? const Icon(Icons.image, size: 48) : const Column(mainAxisAlignment: MainAxisAlignment.center, children: [Icon(Icons.add_photo_alternate, size: 36, color: Colors.grey), SizedBox(height: 8), Text('点击选择图片')]))),
if (field.hasError) Padding(padding: const EdgeInsets.only(top: 8, left: 12), child: Text(field.errorText!, style: const TextStyle(color: Colors.red, fontSize: 12))),
]),
);
}
// 使用示例
class ReviewForm extends StatefulWidget {
const ReviewForm({super.key});
@override
State<ReviewForm> createState() => _ReviewFormState();
}
class _ReviewFormState extends State<ReviewForm> {
final _formKey = GlobalKey<FormState>();
@override
Widget build(BuildContext context) {
return Form(key: _formKey, child: Column(children: [
const Text('评分', style: TextStyle(fontWeight: FontWeight.bold)),
const RatingFormField(),
const SizedBox(height: 16),
const Text('图片', style: TextStyle(fontWeight: FontWeight.bold)),
const ImagePickerFormField(),
const SizedBox(height: 24),
FilledButton(onPressed: () { if (_formKey.currentState!.validate()) { ScaffoldMessenger.of(context).showSnackBar(const SnackBar(content: Text('提交成功!'))); } }, child: const Text('提交评价')),
]));
}
}// ✅ 验证通过
/// 1. 实时验证 vs 提交验证
// 实时验证:autovalidateMode
Form(
autovalidateMode: AutovalidateMode.onUserInteraction, // 用户交互后自动验证
// AutovalidateMode.always — 始终验证
// AutovalidateMode.disabled — 只在submit时验证(默认)
child: ...,
)
/// 2. 表单保存
void _saveForm() {
if (_formKey.currentState!.validate()) {
_formKey.currentState!.save(); // 调用每个字段的onSaved
}
}
TextFormField(
onSaved: (value) => _formData['name'] = value ?? '',
)
/// 3. 输入格式化
TextFormField(
inputFormatters: [
FilteringTextInputFormatter.digitsOnly, // 只允许数字
LengthLimitingTextInputFormatter(11), // 限制长度
// 自定义格式化
TextInputFormatter.withFunction((oldValue, newValue) {
// 手机号格式化:138 0000 0000
var text = newValue.text.replaceAll(' ', '');
if (text.length > 11) return oldValue;
var buffer = StringBuffer();
for (var i = 0; i < text.length; i++) {
if (i == 3 || i == 7) buffer.write(' ');
buffer.write(text[i]);
}
return TextEditingValue(text: buffer.toString(), selection: TextSelection.collapsed(offset: buffer.length));
}),
],
)
/// 4. 表单焦点管理
class FormWithFocus extends StatefulWidget {
const FormWithFocus({super.key});
@override
State<FormWithFocus> createState() => _FormWithFocusState();
}
class _FormWithFocusState extends State<FormWithFocus> {
final _nameFocus = FocusNode();
final _emailFocus = FocusNode();
final _passwordFocus = FocusNode();
@override
void dispose() {
_nameFocus.dispose(); _emailFocus.dispose(); _passwordFocus.dispose();
super.dispose();
}
@override
Widget build(BuildContext context) {
return Column(children: [
TextFormField(
focusNode: _nameFocus,
onFieldSubmitted: (_) => _emailFocus.requestFocus(), // 回车跳到邮箱
),
TextFormField(
focusNode: _emailFocus,
onFieldSubmitted: (_) => _passwordFocus.requestFocus(),
),
TextFormField(
focusNode: _passwordFocus,
onFieldSubmitted: (_) => _submit(), // 回车提交
),
]);
}
}
/// 5. 异步验证
Future<String?> validateUsername(String? value) async {
if (value == null || value.isEmpty) return '请输入用户名';
await Future.delayed(const Duration(milliseconds: 500)); // 模拟网络检查
if (['admin', 'root', 'test'].contains(value.toLowerCase())) {
return '用户名已被占用';
}
return null;
}
// ✅ 验证通过:Flutter 3.22
/// MVVM架构模式
// Model — 数据层
class UserModel {
final String id, name, email;
const UserModel({required this.id, required this.name, required this.email});
factory UserModel.fromJson(Map json) => UserModel(id: json['id'], name: json['name'], email: json['email']);
Map<String, dynamic> toJson() => {'id': id, 'name': name, 'email': email};
}
// ViewModel — 业务逻辑层(ChangeNotifier)
class UserViewModel extends ChangeNotifier {
final UserRepository _repo;
User? _user; bool _isLoading = false; String? _error;
User? get user => _user; bool get isLoading => _isLoading; String? get error => _error;
UserViewModel(this._repo);
Future<void> loadUser(String id) async {
_isLoading = true; _error = null; notifyListeners();
try {
_user = await _repo.getUser(id);
_isLoading = false;
} catch (e) {
_error = e.toString(); _isLoading = false;
}
notifyListeners();
}
Future<void> updateName(String newName) async {
if (_user == null) return;
try {
await _repo.updateUser(_user!.id, {'name': newName});
_user = UserModel(id: _user!.id, name: newName, email: _user!.email);
notifyListeners();
} catch (e) {
_error = e.toString(); notifyListeners();
}
}
}
// View — UI层
class UserProfilePage extends StatelessWidget {
const UserProfilePage({super.key});
@override
Widget build(BuildContext context) {
var vm = context.watch<UserViewModel>();
if (vm.isLoading) return const Center(child: CircularProgressIndicator());
if (vm.error != null) return Center(child: Text('Error: ${vm.error}'));
return Column(children: [
Text(vm.user?.name ?? 'Unknown'),
ElevatedButton(onPressed: () => vm.updateName('New Name'), child: const Text('Update')),
]);
}
}
/// Repository模式——隔离数据源
abstract class UserRepository {
Future<User> getUser(String id);
Future<void> updateUser(String id, Map data);
}
class ApiUserRepository implements UserRepository {
final ApiClient _api;
ApiUserRepository(this._api);
@override Future<User> getUser(String id) async { var r = await _api._dio.get('/users/$id'); return User.fromJson(r.data); }
@override Future<void> updateUser(String id, Map data) async { await _api._dio.put('/users/$id', data: data); }
}
class MockUserRepository implements UserRepository {
@override Future<User> getUser(String id) async { await Future.delayed(const Duration(seconds: 1)); return const User(id: '1', name: 'Mock', email: 'mock@test.com'); }
@override Future<void> updateUser(String id, Map data) async { await Future.delayed(const Duration(milliseconds: 500)); }
}
// 推荐项目结构:
// lib/
// ├── main.dart — 入口
// ├── app.dart — App配置
// ├── core/ — 核心工具
// │ ├── theme.dart
// │ ├── constants.dart
// │ ├── extensions.dart
// │ └── utils.dart
// ├── models/ — 数据模型
// │ ├── user.dart
// │ └── product.dart
// ├── repositories/ — 数据仓库
// │ ├── user_repository.dart
// │ └── product_repository.dart
// ├── providers/ — 状态管理
// │ ├── auth_provider.dart
// │ └── cart_provider.dart
// ├── screens/ — 页面
// │ ├── home/
// │ │ ├── home_screen.dart
// │ │ └── widgets/ — 页面私有Widget
// │ ├── profile/
// │ └── settings/
// ├── widgets/ — 共享Widget
// │ ├── app_button.dart
// │ ├── app_card.dart
// │ └── loading_indicator.dart
// └── services/ — 外部服务
// ├── api_client.dart
// ├── storage_service.dart
// └── analytics_service.dart
实现用户名实时查重:输入时异步检查是否已被占用。
实现3步注册向导:基本信息→详细信息→确认提交,每步独立验证。
实现可动态添加/删除的表单字段列表(如多个联系方式)。
表单大师——精通Flutter表单验证系统
下一课将继续深入Flutter开发。