flutter学习第 10 节:表单与输入

在移动应用开发中,表单是收集用户输入的重要方式,无论是登录注册、个人信息填写还是数据提交,都离不开表单组件。Flutter 提供了一套完整的表单处理机制,包括表单组件、输入验证、数据处理等功能。本节课将详细介绍 Flutter 中的表单与输入相关组件,帮助你构建功能完善、用户体验良好的表单界面。

一、表单基础组件

Flutter 提供了 Form 组件作为表单容器,配合各种输入控件(如 TextFormField)实现完整的表单功能。Form 组件本身不会渲染任何可见内容,它主要用于管理表单字段的状态、验证和提交。

1. Form 组件与 GlobalKey

Form 组件需要通过 GlobalKey<FormState> 来管理表单状态,实现表单验证和数据提交等操作。GlobalKey 是跨组件访问状态的一种方式,能够唯一标识一个组件并获取其状态。

基本用法示例:

dart 复制代码
class BasicFormExample extends StatefulWidget {
  const BasicFormExample({super.key});

  @override
  State<BasicFormExample> createState() => _BasicFormExampleState();
}

class _BasicFormExampleState extends State<BasicFormExample> {
  // 创建表单全局键
  final _formKey = GlobalKey<FormState>();

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: const Text('Basic Form')),
      body: Padding(
        padding: const EdgeInsets.all(16.0),
        // 表单组件
        child: Form(
          key: _formKey, // 关联全局键
          autovalidateMode: AutovalidateMode.onUserInteraction, // 验证模式
          child: Column(
            children: [
              // 表单字段
              TextFormField(
                decoration: const InputDecoration(
                  labelText: 'Username',
                  border: OutlineInputBorder(),
                ),
                // 验证器
                validator: (value) {
                  if (value == null || value.isEmpty) {
                    return 'Please enter your username';
                  }
                  return null; // 验证通过
                },
              ),
              const SizedBox(height: 16),
              ElevatedButton(
                onPressed: () {
                  // 验证表单
                  if (_formKey.currentState!.validate()) {
                    // 验证通过,处理数据
                    ScaffoldMessenger.of(context).showSnackBar(
                      const SnackBar(content: Text('Processing data')),
                    );
                  }
                },
                child: const Text('Submit'),
              ),
            ],
          ),
        ),
      ),
    );
  }
}

Form 组件的核心参数:

  • key:用于访问表单状态的 GlobalKey<FormState>

  • autovalidateMode:自动验证模式,决定何时自动验证输入

    • AutovalidateMode.disabled:禁用自动验证(默认)
    • AutovalidateMode.always:总是自动验证
    • AutovalidateMode.onUserInteraction:用户交互时(如输入、焦点变化)验证

2. TextFormField 组件

TextFormField 是表单中最常用的输入组件,继承自 TextField 并增加了表单验证功能。它支持各种输入类型(文本、数字、邮箱等),并可自定义外观和行为。

常用属性示例:

dart 复制代码
TextFormField(
  // 输入框装饰
  decoration: InputDecoration(
    labelText: 'Email',
    hintText: 'Enter your email address',
    prefixIcon: const Icon(Icons.email),
    border: const OutlineInputBorder(),
    // 错误提示样式
    errorBorder: OutlineInputBorder(
      borderSide: const BorderSide(color: Colors.red),
      borderRadius: BorderRadius.circular(4),
    ),
  ),
  // 输入类型
  keyboardType: TextInputType.emailAddress,
  // 输入格式器(可限制输入内容)
  inputFormatters: [
    FilteringTextInputFormatter.deny(RegExp(r'\s')), // 禁止输入空格
  ],
  // 文本大小写转换
  textCapitalization: TextCapitalization.none,
  // 密码隐藏
  obscureText: false, // 密码框设为true
  // 自动更正
  autocorrect: false,
  // 自动获取焦点
  autofocus: false,
  // 最大长度
  maxLength: 50,
  // 最大行数
  maxLines: 1,
  // 验证器
  validator: (value) {
    if (value == null || value.isEmpty) {
      return 'Please enter your email';
    }
    // 简单的邮箱格式验证
    if (!RegExp(r'^[\w-.]+@([\w-]+.)+[\w-]{2,4}$').hasMatch(value)) {
      return 'Please enter a valid email';
    }
    return null;
  },
  // 输入变化回调
  onChanged: (value) {
    print('Email changed: $value');
  },
  // 完成输入回调
  onFieldSubmitted: (value) {
    print('Email submitted: $value');
  },
)

3. 其他输入组件

除了文本输入,Flutter 还提供了其他常用的表单输入组件:

  • Checkbox:复选框,用于选择多个选项
  • Radio:单选按钮,用于从多个选项中选择一个
  • Switch:开关,用于开启 / 关闭某个功能
  • DropdownButton:下拉选择框,用于从预设选项中选择

这些组件可以通过 FormField 包装以集成到表单中:

dart 复制代码
// 复选框表单字段
FormField<bool>(
  initialValue: false,
  validator: (value) {
    if (value == false) {
      return 'Please agree to the terms';
    }
    return null;
  },
  builder: (formFieldState) {
    return Column(
      crossAxisAlignment: CrossAxisAlignment.start,
      children: [
        Row(
          children: [
            Checkbox(
              value: formFieldState.value,
              onChanged: (value) {
                formFieldState.didChange(value);
              },
            ),
            const Text('I agree to the terms and conditions'),
          ],
        ),
        if (formFieldState.hasError)
          Padding(
            padding: const EdgeInsets.only(top: 4, left: 4),
            child: Text(
              formFieldState.errorText!,
              style: const TextStyle(color: Colors.red, fontSize: 12),
            ),
          ),
      ],
    );
  },
)

// 下拉选择框
FormField<String>(
  initialValue: 'male',
  validator: (value) {
    if (value == null || value.isEmpty) {
      return 'Please select gender';
    }
    return null;
  },
  builder: (formFieldState) {
    return Column(
      crossAxisAlignment: CrossAxisAlignment.start,
      children: [
        DropdownButton<String>(
          value: formFieldState.value,
          isExpanded: true,
          items: const [
            DropdownMenuItem(value: 'male', child: Text('Male')),
            DropdownMenuItem(value: 'female', child: Text('Female')),
            DropdownMenuItem(value: 'other', child: Text('Other')),
          ],
          onChanged: (value) {
            formFieldState.didChange(value);
          },
        ),
        if (formFieldState.hasError)
          Padding(
            padding: const EdgeInsets.only(top: 4, left: 4),
            child: Text(
              formFieldState.errorText!,
              style: const TextStyle(color: Colors.red, fontSize: 12),
            ),
          ),
      ],
    );
  },
)

二、输入验证与表单提交

表单验证是确保用户输入符合要求的重要环节,Flutter 提供了灵活的验证机制,可实现简单到复杂的各种验证逻辑。

1. 基本验证逻辑

每个 TextFormField 都可以通过 validator 属性指定验证函数,该函数接收输入值并返回错误信息(验证失败)或 null(验证成功)。

常见验证示例:

dart 复制代码
// 用户名验证
validator: (value) {
  if (value == null || value.isEmpty) {
    return 'Please enter username';
  }
  if (value.length < 3) {
    return 'Username must be at least 3 characters';
  }
  if (value.length > 20) {
    return 'Username cannot exceed 20 characters';
  }
  return null;
}

// 密码验证
validator: (value) {
  if (value == null || value.isEmpty) {
    return 'Please enter password';
  }
  if (value.length < 6) {
    return 'Password must be at least 6 characters';
  }
  if (!RegExp(r'[A-Z]').hasMatch(value)) {
    return 'Password must contain at least one uppercase letter';
  }
  return null;
}

// 确认密码验证(需要与密码字段比较)
validator: (value) {
  if (value == null || value.isEmpty) {
    return 'Please confirm password';
  }
  if (value != _passwordController.text) {
    return 'Passwords do not match';
  }
  return null;
}

2. 表单提交与数据处理

通过 GlobalKey<FormState> 可以触发表单验证和获取表单数据:

dart 复制代码
class FormSubmissionExample extends StatefulWidget {
  const FormSubmissionExample({super.key});

  @override
  State<FormSubmissionExample> createState() => _FormSubmissionExampleState();
}

class _FormSubmissionExampleState extends State<FormSubmissionExample> {
  final _formKey = GlobalKey<FormState>();
  final _usernameController = TextEditingController();
  final _emailController = TextEditingController();

  @override
  void dispose() {
    // 释放控制器资源
    _usernameController.dispose();
    _emailController.dispose();
    super.dispose();
  }

  // 提交表单
  void _submitForm() {
    // 验证所有字段
    if (_formKey.currentState!.validate()) {
      // 验证通过,保存表单状态
      _formKey.currentState!.save();

      // 获取输入值
      final username = _usernameController.text;
      final email = _emailController.text;

      // 处理表单数据(如提交到服务器)
      _processFormData(username, email);
    }
  }

  // 处理表单数据
  void _processFormData(String username, String email) {
    print('Username: $username');
    print('Email: $email');

    // 显示提交成功消息
    ScaffoldMessenger.of(context).showSnackBar(
      const SnackBar(content: Text('Form submitted successfully')),
    );

    // 可以在这里导航到其他页面
    // Navigator.pushNamed(context, '/success');
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: const Text('Form Submission')),
      body: Padding(
        padding: const EdgeInsets.all(16.0),
        child: Form(
          key: _formKey,
          autovalidateMode: AutovalidateMode.onUserInteraction,
          child: Column(
            children: [
              TextFormField(
                controller: _usernameController,
                decoration: const InputDecoration(
                  labelText: 'Username',
                  border: OutlineInputBorder(),
                ),
                validator: (value) {
                  if (value == null || value.isEmpty) {
                    return 'Please enter username';
                  }
                  if (value.length < 3) {
                    return 'Username must be at least 3 characters';
                  }
                  return null;
                },
                // 保存回调(当调用save()时触发)
                onSaved: (value) {
                  print('Username saved: $value');
                },
              ),
              const SizedBox(height: 16),
              TextFormField(
                controller: _emailController,
                decoration: const InputDecoration(
                  labelText: 'Email',
                  border: OutlineInputBorder(),
                ),
                keyboardType: TextInputType.emailAddress,
                validator: (value) {
                  if (value == null || value.isEmpty) {
                    return 'Please enter email';
                  }
                  if (!RegExp(
                    r'^[\w-.]+@([\w-]+.)+[\w-]{2,4}$',
                  ).hasMatch(value)) {
                    return 'Please enter a valid email';
                  }
                  return null;
                },
              ),
              const SizedBox(height: 24),
              ElevatedButton(
                onPressed: _submitForm,
                child: const Text('Submit'),
              ),
            ],
          ),
        ),
      ),
    );
  }
}

表单处理流程:

  1. 用户输入数据
  2. 触发验证(手动或自动)
  3. 用户点击提交按钮
  4. 调用 _formKey.currentState!.validate() 验证所有字段
  5. 验证通过后,调用 _formKey.currentState!.save() 保存所有字段值
  6. 获取输入数据并进行处理(如提交到服务器)

3. 手动控制验证时机

除了自动验证,也可以手动控制验证时机:

dart 复制代码
// 只在提交时验证
Form(
  key: _formKey,
  autovalidateMode: AutovalidateMode.disabled, // 禁用自动验证
  // ...
)

// 手动触发单个字段验证
void _validateUsername() {
  _usernameFieldKey.currentState?.validate();
}

// 重置表单
void _resetForm() {
  _formKey.currentState?.reset();
}

三、文本控制器:TextEditingController

TextEditingController 用于控制文本输入组件的内容,能够获取、设置和监听输入内容的变化,是处理表单数据的重要工具。

1. 基本用法

dart 复制代码
class TextEditingControllerExample extends StatefulWidget {
  const TextEditingControllerExample({super.key});

  @override
  State<TextEditingControllerExample> createState() =>
      _TextEditingControllerExampleState();
}

class _TextEditingControllerExampleState
    extends State<TextEditingControllerExample> {
  // 创建文本控制器
  final _controller = TextEditingController();

  @override
  void initState() {
    super.initState();

    // 设置初始值
    _controller.text = 'Initial value';

    // 监听文本变化
    _controller.addListener(_onTextChanged);
  }

  @override
  void dispose() {
    // 移除监听器并释放资源
    _controller.removeListener(_onTextChanged);
    _controller.dispose();
    super.dispose();
  }

  // 文本变化回调
  void _onTextChanged() {
    print('Text changed: ${_controller.text}');
  }

  // 获取输入值
  void _getValue() {
    final text = _controller.text;
    ScaffoldMessenger.of(
      context,
    ).showSnackBar(SnackBar(content: Text('Current value: $text')));
  }

  // 设置输入值
  void _setValue() {
    _controller.text = 'New value set programmatically';
  }

  // 清空输入
  void _clearValue() {
    _controller.clear();
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: const Text('Text Controller')),
      body: Padding(
        padding: const EdgeInsets.all(16.0),
        child: Column(
          children: [
            TextField(
              controller: _controller, // 关联控制器
              decoration: const InputDecoration(
                labelText: 'Enter text',
                border: OutlineInputBorder(),
              ),
            ),
            const SizedBox(height: 16),
            Row(
              mainAxisAlignment: MainAxisAlignment.spaceAround,
              children: [
                ElevatedButton(
                  onPressed: _getValue,
                  child: const Text('Get Value'),
                ),
                ElevatedButton(
                  onPressed: _setValue,
                  child: const Text('Set Value'),
                ),
                ElevatedButton(
                  onPressed: _clearValue,
                  child: const Text('Clear'),
                ),
              ],
            ),
          ],
        ),
      ),
    );
  }
}

2. 高级用法

TextEditingController 还提供了一些高级功能:

  • 选择文本
dart 复制代码
// 选择文本
_controller.selection = TextSelection(
  baseOffset: 2,
  extentOffset: 5,
);

// 选中所有文本
_controller.selection = TextSelection(
  baseOffset: 0,
  extentOffset: _controller.text.length,
);
  • 设置光标位置
dart 复制代码
// 设置光标位置到末尾
_controller.selection = TextSelection.fromPosition(
  TextPosition(offset: _controller.text.length),
);
  • 带样式的文本

使用 TextSpan 设置富文本:

dart 复制代码
final _richTextController = TextEditingController(
  text: 'Hello World',
);

// 在build方法中
TextField(
  controller: _richTextController,
  decoration: const InputDecoration(labelText: 'Rich Text'),
  style: const TextStyle(fontSize: 16),
  // 可以通过inputFormatters限制输入格式
)

四、实例:实现登录与注册表单

下面实现一个完整的登录和注册表单,包含输入验证、表单提交、密码显示 / 隐藏切换等功能。

1. 登录表单

dart 复制代码
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 _obscurePassword = true; // 控制密码是否显示
  bool _isLoading = false; // 控制加载状态

  @override
  void dispose() {
    _emailController.dispose();
    _passwordController.dispose();
    super.dispose();
  }

  // 切换密码显示状态
  void _togglePasswordVisibility() {
    setState(() {
      _obscurePassword = !_obscurePassword;
    });
  }

  // 提交登录表单
  Future<void> _submitLogin() async {
    if (_formKey.currentState!.validate()) {
      setState(() {
        _isLoading = true;
      });

      // 模拟登录请求
      try {
        await Future.delayed(const Duration(seconds: 2));
        // 登录成功,导航到主页
        if (mounted) {
          ScaffoldMessenger.of(
            context,
          ).showSnackBar(const SnackBar(content: Text('Login successful')));
          // Navigator.pushReplacementNamed(context, '/home');
        }
      } catch (e) {
        if (mounted) {
          ScaffoldMessenger.of(
            context,
          ).showSnackBar(SnackBar(content: Text('Login failed: $e')));
        }
      } finally {
        if (mounted) {
          setState(() {
            _isLoading = false;
          });
        }
      }
    }
  }

  @override
  Widget build(BuildContext context) {
    return Form(
      key: _formKey,
      autovalidateMode: AutovalidateMode.onUserInteraction,
      child: Column(
        children: [
          TextFormField(
            controller: _emailController,
            decoration: const InputDecoration(
              labelText: 'Email',
              prefixIcon: Icon(Icons.email),
              border: OutlineInputBorder(),
            ),
            keyboardType: TextInputType.emailAddress,
            enabled: !_isLoading,
            validator: (value) {
              if (value == null || value.isEmpty) {
                return 'Please enter your email';
              }
              if (!RegExp(
                r'^[\w-.]+@([\w-]+.)+[\w-]{2,4}$',
              ).hasMatch(value)) {
                return 'Please enter a valid email';
              }
              return null;
            },
          ),
          const SizedBox(height: 16),
          TextFormField(
            controller: _passwordController,
            decoration: InputDecoration(
              labelText: 'Password',
              prefixIcon: const Icon(Icons.lock),
              border: const OutlineInputBorder(),
              // 密码可见性切换按钮
              suffixIcon: IconButton(
                icon: Icon(
                  _obscurePassword ? Icons.visibility : Icons.visibility_off,
                ),
                onPressed: _togglePasswordVisibility,
              ),
            ),
            obscureText: _obscurePassword,
            enabled: !_isLoading,
            validator: (value) {
              if (value == null || value.isEmpty) {
                return 'Please enter your password';
              }
              if (value.length < 6) {
                return 'Password must be at least 6 characters';
              }
              return null;
            },
          ),
          const SizedBox(height: 8),
          // 忘记密码链接
          Align(
            alignment: Alignment.centerRight,
            child: TextButton(
              onPressed: () {
                // 导航到忘记密码页面
                // Navigator.pushNamed(context, '/forgot-password');
              },
              child: const Text('Forgot Password?'),
            ),
          ),
          const SizedBox(height: 16),
          // 登录按钮
          SizedBox(
            width: double.infinity,
            child: ElevatedButton(
              onPressed: _isLoading ? null : _submitLogin,
              style: ElevatedButton.styleFrom(
                padding: const EdgeInsets.symmetric(vertical: 16),
              ),
              child: _isLoading
                  ? const CircularProgressIndicator(color: Colors.white)
                  : const Text('Login'),
            ),
          ),
          const SizedBox(height: 16),
          // 注册链接
          Row(
            mainAxisAlignment: MainAxisAlignment.center,
            children: [
              const Text("Don't have an account?"),
              TextButton(
                onPressed: () {
                  // 导航到注册页面
                  // Navigator.pushNamed(context, '/register');
                },
                child: const Text('Register'),
              ),
            ],
          ),
        ],
      ),
    );
  }
}

2. 注册表单

dart 复制代码
class RegisterForm extends StatefulWidget {
  const RegisterForm({super.key});

  @override
  State<RegisterForm> createState() => _RegisterFormState();
}

class _RegisterFormState extends State<RegisterForm> {
  final _formKey = GlobalKey<FormState>();
  final _usernameController = TextEditingController();
  final _emailController = TextEditingController();
  final _passwordController = TextEditingController();
  final _confirmPasswordController = TextEditingController();

  bool _obscurePassword = true;
  bool _obscureConfirmPassword = true;
  bool _isLoading = false;
  String? _selectedGender;

  @override
  void dispose() {
    _usernameController.dispose();
    _emailController.dispose();
    _passwordController.dispose();
    _confirmPasswordController.dispose();
    super.dispose();
  }

  void _togglePasswordVisibility() {
    setState(() {
      _obscurePassword = !_obscurePassword;
    });
  }

  void _toggleConfirmPasswordVisibility() {
    setState(() {
      _obscureConfirmPassword = !_obscureConfirmPassword;
    });
  }

  Future<void> _submitRegistration() async {
    if (_formKey.currentState!.validate()) {
      setState(() {
        _isLoading = true;
      });

      // 模拟注册请求
      try {
        await Future.delayed(const Duration(seconds: 2));
        // 注册成功
        if (mounted) {
          ScaffoldMessenger.of(context).showSnackBar(
            const SnackBar(content: Text('Registration successful')),
          );
          Navigator.pop(context); // 返回登录页面
        }
      } catch (e) {
        if (mounted) {
          ScaffoldMessenger.of(
            context,
          ).showSnackBar(SnackBar(content: Text('Registration failed: $e')));
        }
      } finally {
        if (mounted) {
          setState(() {
            _isLoading = false;
          });
        }
      }
    }
  }

  @override
  Widget build(BuildContext context) {
    return Form(
      key: _formKey,
      autovalidateMode: AutovalidateMode.onUserInteraction,
      child: SingleChildScrollView(
        child: Column(
          children: [
            TextFormField(
              controller: _usernameController,
              decoration: const InputDecoration(
                labelText: 'Username',
                prefixIcon: Icon(Icons.person),
                border: OutlineInputBorder(),
              ),
              enabled: !_isLoading,
              validator: (value) {
                if (value == null || value.isEmpty) {
                  return 'Please enter your username';
                }
                if (value.length < 3) {
                  return 'Username must be at least 3 characters';
                }
                if (value.length > 20) {
                  return 'Username cannot exceed 20 characters';
                }
                return null;
              },
            ),
            const SizedBox(height: 16),
            TextFormField(
              controller: _emailController,
              decoration: const InputDecoration(
                labelText: 'Email',
                prefixIcon: Icon(Icons.email),
                border: OutlineInputBorder(),
              ),
              keyboardType: TextInputType.emailAddress,
              enabled: !_isLoading,
              validator: (value) {
                if (value == null || value.isEmpty) {
                  return 'Please enter your email';
                }
                if (!RegExp(
                  r'^[\w-.]+@([\w-]+.)+[\w-]{2,4}$',
                ).hasMatch(value)) {
                  return 'Please enter a valid email';
                }
                return null;
              },
            ),
            const SizedBox(height: 16),
            // 性别选择
            DropdownButtonFormField<String>(
              value: _selectedGender,
              decoration: const InputDecoration(
                labelText: 'Gender',
                prefixIcon: Icon(Icons.person_2),
                border: OutlineInputBorder(),
              ),
              items: const [
                DropdownMenuItem(value: 'male', child: Text('Male')),
                DropdownMenuItem(value: 'female', child: Text('Female')),
                DropdownMenuItem(value: 'other', child: Text('Other')),
              ],
              onChanged: (value) {
                setState(() {
                  _selectedGender = value;
                });
              },
              validator: (value) {
                if (value == null) {
                  return 'Please select your gender';
                }
                return null;
              },
            ),
            const SizedBox(height: 16),
            TextFormField(
              controller: _passwordController,
              decoration: InputDecoration(
                labelText: 'Password',
                prefixIcon: const Icon(Icons.lock),
                border: const OutlineInputBorder(),
                suffixIcon: IconButton(
                  icon: Icon(
                    _obscurePassword ? Icons.visibility : Icons.visibility_off,
                  ),
                  onPressed: _togglePasswordVisibility,
                ),
              ),
              obscureText: _obscurePassword,
              enabled: !_isLoading,
              validator: (value) {
                if (value == null || value.isEmpty) {
                  return 'Please enter your password';
                }
                if (value.length < 6) {
                  return 'Password must be at least 6 characters';
                }
                if (!RegExp(r'[A-Z]').hasMatch(value)) {
                  return 'Password must contain at least one uppercase letter';
                }
                if (!RegExp(r'[0-9]').hasMatch(value)) {
                  return 'Password must contain at least one number';
                }
                return null;
              },
            ),
            const SizedBox(height: 16),
            TextFormField(
              controller: _confirmPasswordController,
              decoration: InputDecoration(
                labelText: 'Confirm Password',
                prefixIcon: const Icon(Icons.lock_clock),
                border: const OutlineInputBorder(),
                suffixIcon: IconButton(
                  icon: Icon(
                    _obscureConfirmPassword
                        ? Icons.visibility
                        : Icons.visibility_off,
                  ),
                  onPressed: _toggleConfirmPasswordVisibility,
                ),
              ),
              obscureText: _obscureConfirmPassword,
              enabled: !_isLoading,
              validator: (value) {
                if (value == null || value.isEmpty) {
                  return 'Please confirm your password';
                }
                if (value != _passwordController.text) {
                  return 'Passwords do not match';
                }
                return null;
              },
            ),
            const SizedBox(height: 16),
            // 同意条款
            FormField<bool>(
              initialValue: false,
              validator: (value) {
                if (value == false) {
                  return 'Please agree to the terms';
                }
                return null;
              },
              builder: (formFieldState) {
                return Column(
                  crossAxisAlignment: CrossAxisAlignment.start,
                  children: [
                    Row(
                      children: [
                        Checkbox(
                          value: formFieldState.value,
                          onChanged: _isLoading
                              ? null
                              : (value) {
                                  formFieldState.didChange(value);
                                },
                        ),
                        const Text(
                          'I agree to the Terms\n of Service and Privacy Policy',
                        ),
                      ],
                    ),
                    if (formFieldState.hasError)
                      Padding(
                        padding: const EdgeInsets.only(top: 4, left: 4),
                        child: Text(
                          formFieldState.errorText!,
                          style: const TextStyle(
                            color: Colors.red,
                            fontSize: 12,
                          ),
                        ),
                      ),
                  ],
                );
              },
            ),
            const SizedBox(height: 24),
            // 注册按钮
            SizedBox(
              width: double.infinity,
              child: ElevatedButton(
                onPressed: _isLoading ? null : _submitRegistration,
                style: ElevatedButton.styleFrom(
                  padding: const EdgeInsets.symmetric(vertical: 16),
                ),
                child: _isLoading
                    ? const CircularProgressIndicator(color: Colors.white)
                    : const Text('Register'),
              ),
            ),
          ],
        ),
      ),
    );
  }
}

3. 表单页面集成

dart 复制代码
class AuthScreen extends StatefulWidget {
  const AuthScreen({super.key});

  @override
  State<AuthScreen> createState() => _AuthScreenState();
}

class _AuthScreenState extends State<AuthScreen> {
  bool _isLogin = true; // 切换登录/注册模式

  // 切换表单模式
  void _toggleFormMode() {
    setState(() {
      _isLogin = !_isLogin;
    });
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      body: Padding(
        padding: const EdgeInsets.all(24.0),
        child: Column(
          mainAxisAlignment: MainAxisAlignment.center,
          children: [
            // 标题
            Text(
              _isLogin ? 'Welcome Back' : 'Create Account',
              style: const TextStyle(
                fontSize: 28,
                fontWeight: FontWeight.bold,
              ),
            ),
            const SizedBox(height: 8),
            Text(
              _isLogin
                  ? 'Sign in to your account to continue'
                  : 'Fill in the details to create a new account',
              style: const TextStyle(
                color: Colors.grey,
                fontSize: 16,
              ),
            ),
            const SizedBox(height: 32),
            // 显示登录或注册表单
            _isLogin ? const LoginForm() : const RegisterForm(),
            // 切换表单按钮
            if (!_isLogin)
              TextButton(
                onPressed: _toggleFormMode,
                child: const Text('Already have an account? Login'),
              ),
          ],
        ),
      ),
    );
  }
}
相关推荐
tangweiguo030519871 小时前
Dart 单例模式:工厂构造、静态变量与懒加载
flutter
pengyu1 小时前
【Kotlin系统化精讲:伍】 | 数据类型之空安全:从防御性编程到类型革命🚀
android·kotlin
苏元1 小时前
从简易到通用:FunctionThrottleDebounce 升级全记录(支持同步 & 异步、任意参数、可取消)
flutter
叽哥1 小时前
flutter学习第 11 节:状态管理进阶:Provider
android·flutter·ios
猪哥帅过吴彦祖3 小时前
Flutter AnimatedList 完全指南:打造流畅的动态列表体验
flutter
2501_916013743 小时前
移动端网页调试实战,跨设备兼容与触控交互问题排查全流程
android·ios·小程序·https·uni-app·iphone·webview
顾林海3 小时前
深入理解Java内存屏障:从原理到实践
android·面试·性能优化
天岚4 小时前
温故知新-WidgetsBinding
flutter