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'),
              ),
          ],
        ),
      ),
    );
  }
}
相关推荐
Kapaseker8 小时前
一杯美式搞懂 Any、Unit、Nothing
android·kotlin
黄林晴8 小时前
你的 Android App 还没接 AI?Gemini API 接入全攻略
android
恋猫de小郭19 小时前
2026 Flutter VS React Native ,同时在 AI 时代 VS Native 开发,你没见过的版本
android·前端·flutter
冬奇Lab20 小时前
PowerManagerService(上):电源状态与WakeLock管理
android·源码阅读
明君879971 天前
Flutter 如何给图片添加多行文字水印
前端·flutter
BoomHe1 天前
Now in Android 架构模式全面分析
android·android jetpack
ssshooter1 天前
Tauri 踩坑 appLink 修改后闪退
前端·ios·rust
四眼肥鱼1 天前
flutter 利用flutter_libserialport 实现SQ800 串口通信
前端·flutter
二流小码农1 天前
鸿蒙开发:上传一张参考图片便可实现页面功能
android·ios·harmonyos
鹏程十八少1 天前
4.Android 30分钟手写一个简单版shadow, 从零理解shadow插件化零反射插件化原理
android·前端·面试