【开源鸿蒙跨平台开发先锋训练营】DAY15~DAY19为开源鸿蒙跨平台应用全面集成添加核心场景-注册页面
有了登录页面 怎么可能没有注册页面呢 所以它来了
1.注册页面的开发先看截图效果

该页面包含手机号、密码、验证码输入,短信验证码发送与倒计时,表单验证,隐私政策同意与否。
2.功能部分
注册页面主要包含以下几个部分:
标题区域:用户注册标题
手机号输入框:带图标的手机号输入
密码输入框:支持显示/隐藏密码
验证码输入框:验证码输入 + 获取验证码按钮(带倒计时)
短信帮助链接:解决验证码问题的帮助信息
操作按钮:返回登录 + 立即注册
隐私政策同意:复选框 + 隐私政策页面
3.状态
class RegisterPage extends StatefulWidget { const RegisterPage({super.key}); @override State<RegisterPage> createState() => _RegisterPageState(); } class _RegisterPageState extends State<RegisterPage> { // 输入控制器 final TextEditingController _phoneController = TextEditingController(); final TextEditingController _passwordController = TextEditingController(); final TextEditingController _codeController = TextEditingController(); // 状态变量 bool _obscurePassword = true; // 密码是否隐藏 bool _agreedToPrivacy = false; // 是否同意隐私政策 int _countdown = 0; // 验证码倒计时 @override void dispose() { _phoneController.dispose(); _passwordController.dispose(); _codeController.dispose(); super.dispose(); } }代码解释
三个TextEditingController分别为手机号、密码、验证码输入
_countdown是验证码按钮的倒计时状态
在 dispose中释放资源
4.页面整体代码结构
@override Widget build(BuildContext context) { return Scaffold( backgroundColor: Colors.white, body: SafeArea( child: SingleChildScrollView( padding: const EdgeInsets.symmetric(horizontal: 32), child: Column( crossAxisAlignment: CrossAxisAlignment.stretch, children: [ const SizedBox(height: 60), // 标题 const Text( '用户注册', style: TextStyle( fontSize: 28, fontWeight: FontWeight.bold, color: Color(0xFF1F2937), ), textAlign: TextAlign.center, ), const SizedBox(height: 60), _buildPhoneInput(), const SizedBox(height: 24), _buildPasswordInput(), const SizedBox(height: 24), _buildCodeInput(), const SizedBox(height: 8), _buildSmsHelpLink(), const SizedBox(height: 24), _buildActionButtons(), const SizedBox(height: 40), _buildPrivacyAgreement(), ], ), ), ), ); }5.手机号输入框
Widget _buildPhoneInput() { return Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Row( children: [ Icon(Icons.phone_outlined, color: Colors.grey[600], size: 20), const SizedBox(width: 8), Text( '手机号', style: TextStyle( fontSize: 16, color: Colors.grey[700], fontWeight: FontWeight.w500, ), ), ], ), const SizedBox(height: 12), TextField( controller: _phoneController, keyboardType: TextInputType.phone, decoration: InputDecoration( hintText: '请输入手机号', hintStyle: TextStyle(color: Colors.grey[400]), filled: true, fillColor: const Color(0xFFF9FAFB), border: OutlineInputBorder( borderRadius: BorderRadius.circular(8), borderSide: BorderSide.none, ), contentPadding: const EdgeInsets.symmetric( horizontal: 16, vertical: 14, ), ), ), ], ); }代码解释
1.与登录页面保持一致的设计风格
2.使用数字键盘类型TextInputType.phone
3.浅灰色背景
6.密码输入框
Widget _buildPasswordInput() { return Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Row( children: [ Icon(Icons.lock_outline, color: Colors.grey[600], size: 20), const SizedBox(width: 8), Text( '密码', style: TextStyle( fontSize: 16, color: Colors.grey[700], fontWeight: FontWeight.w500, ), ), ], ), const SizedBox(height: 12), TextField( controller: _passwordController, obscureText: _obscurePassword, decoration: InputDecoration( hintText: '请输入密码', hintStyle: TextStyle(color: Colors.grey[400]), filled: true, fillColor: const Color(0xFFF9FAFB), border: OutlineInputBorder( borderRadius: BorderRadius.circular(8), borderSide: BorderSide.none, ), contentPadding: const EdgeInsets.symmetric( horizontal: 16, vertical: 14, ), suffixIcon: IconButton( icon: Icon( _obscurePassword ? Icons.visibility_off : Icons.visibility, color: Colors.grey[400], size: 20, ), onPressed: () { setState(() { _obscurePassword = !_obscurePassword; }); }, ), ), ), ], ); }7.验证码输入框
Widget _buildCodeInput() { return Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Row( children: [ Icon(Icons.shield_outlined, color: Colors.grey[600], size: 20), const SizedBox(width: 8), Text( '验证码', style: TextStyle( fontSize: 16, color: Colors.grey[700], fontWeight: FontWeight.w500, ), ), ], ), const SizedBox(height: 12), Row( children: [ // 验证码输入框 Expanded( child: TextField( controller: _codeController, keyboardType: TextInputType.number, decoration: InputDecoration( hintText: '请输入验证码', hintStyle: TextStyle(color: Colors.grey[400]), filled: true, fillColor: const Color(0xFFF9FAFB), border: OutlineInputBorder( borderRadius: BorderRadius.circular(8), borderSide: BorderSide.none, ), contentPadding: const EdgeInsets.symmetric( horizontal: 16, vertical: 14, ), ), ), ), const SizedBox(width: 12), // 获取验证码按钮 ElevatedButton( onPressed: _countdown > 0 ? null : _sendVerificationCode, style: ElevatedButton.styleFrom( backgroundColor: const Color(0xFF4CAF50), foregroundColor: Colors.white, disabledBackgroundColor: Colors.grey[300], disabledForegroundColor: Colors.grey[600], padding: const EdgeInsets.symmetric( horizontal: 20, vertical: 14, ), shape: RoundedRectangleBorder( borderRadius: BorderRadius.circular(8), ), elevation: 0, ), child: Text( _countdown > 0 ? '${_countdown}s' : '获取验证码', style: const TextStyle( fontSize: 14, fontWeight: FontWeight.w500, ), ), ), ], ), ], ); } 代码解释一下
输入框和按钮并排布局
按钮根据倒计时状态动态显示文字
倒计时期间按钮禁用(onPressed: null)
禁用状态使用灰色
8.短信帮助功能
这个我要额外说一下 由于本人是个人开发者 所以懂得都懂 只能借鉴第3方 这个时候 这个功能我觉得是非常重要的
Align( alignment: Alignment.centerRight, child: TextButton( onPressed: _showSmsHelpDialog, child: const Text( '短信遇到问题?', style: TextStyle( color: Color(0xFF9CA3AF), fontSize: 13, decoration: TextDecoration.underline, decorationColor: Color(0xFF9CA3AF), ), ), ), )9.返回登录和立即注册功能
Row( children: [ // 返回登录按钮 Expanded( child: OutlinedButton( onPressed: () { Navigator.pop(context); }, style: OutlinedButton.styleFrom( padding: const EdgeInsets.symmetric(vertical: 16), side: const BorderSide(color: Color(0xFFE5E7EB)), shape: RoundedRectangleBorder( borderRadius: BorderRadius.circular(8), ), ), child: const Row( mainAxisAlignment: MainAxisAlignment.center, children: [ Icon(Icons.arrow_back, size: 18, color: Color(0xFF6B7280)), SizedBox(width: 8), Text( '返回登录', style: TextStyle( fontSize: 15, color: Color(0xFF6B7280), fontWeight: FontWeight.w500, ), ), ], ), ), ), const SizedBox(width: 12), // 立即注册按钮 Expanded( flex: 2, child: ElevatedButton( onPressed: _handleRegister, style: ElevatedButton.styleFrom( backgroundColor: const Color(0xFF4CAF50), foregroundColor: Colors.white, padding: const EdgeInsets.symmetric(vertical: 16), shape: RoundedRectangleBorder( borderRadius: BorderRadius.circular(8), ), elevation: 0, ), child: const Row( mainAxisAlignment: MainAxisAlignment.center, children: [ Icon(Icons.person_add, size: 18), SizedBox(width: 8), Text( '立即注册', style: TextStyle( fontSize: 15, fontWeight: FontWeight.w600, ), ), ], ), ), ), ], ) 10.校验是否为空
void _handleRegister() async {
// 验证手机号
if (_phoneController.text.isEmpty) {
_showMessage('请输入手机号');
return;
}
// 验证密码
if (_passwordController.text.isEmpty) {
_showMessage('请输入密码');
return;
}
// 验证验证码
if (_codeController.text.isEmpty) {
_showMessage('请输入验证码');
return;
}
// 验证隐私政策
if (!_agreedToPrivacy) {
_showMessage('请先阅读并同意隐私政策');
return;
}
}
10.调用注册的API
final result = await UserApi.register(
phone: _phoneController.text,
password: _passwordController.text,
code: _codeController.text,
);
11.注册成功之后的处理逻辑
if (result != null && result['success'] == true) {
// 注册成功,跳转到主页面,其实我觉得调回登录页蛮好的 ,后续改改吧
if (mounted) {
Navigator.pushAndRemoveUntil(
context,
MaterialPageRoute(builder: (context) => const MainPage()),
(route) => false,
);
}
} else {
// 注册失败,显示错误信息
final message = result?['message'] ?? '注册失败,请重试';
_showMessage(message);
}
12.颜色推荐
| 颜色用途 | 颜色值 | 说明 |
| 主题色 | 0xFF4CAF50 | 按钮,复选框 |
| 主文本 | 0xFF1F2937 | 标题文字 |
| 次要文本 | Colors.grey[700] | |
| 提示文本 | Colors.grey[400] | 输入框占位符 |
| 帮助文本 | 0xFF9CA3AF | |
| 内容文本 | 0xFF4B5563 | 对话框内容 |
| 输入框背景 | 0xFFF9FAFB | 浅灰色背景 |
| 边框颜色 | 0xFFE5E7EB | 按钮边框 |
| 禁用背景 | Colors.grey[300] | 禁用按钮背景 |
13.最后
欢迎加入开源鸿蒙跨平台社区:
https://openharmonycrossplatform.ZEEKLOG.net