在这里插入图片描述

前言

登录功能是大多数App的标配,用户登录后可以同步收藏、播放记录等个人数据。本篇我们来实现一个手机验证码登录页面,包含手机号输入、验证码获取与倒计时、第三方登录入口等常见功能。整个页面采用渐变背景设计,视觉上更有层次感。

需求梳理

登录页面需要实现以下功能:手机号输入框、验证码输入框、获取验证码按钮(带60秒倒计时)、登录按钮、第三方登录入口(微信、Apple、邮箱)、用户协议和隐私政策提示。页面顶部还需要一个关闭按钮,方便用户返回上一页。

页面框架搭建

登录页面涉及到输入框状态和倒计时逻辑,所以我们使用 StatefulWidget 来实现:

class LoginPage extends StatefulWidget {
  const LoginPage({super.key});

  
  State<LoginPage> createState() => _LoginPageState();
}

在状态类中,我们需要定义几个关键变量来管理页面状态:

class _LoginPageState extends State<LoginPage> {
  final _phoneController = TextEditingController();
  final _codeController = TextEditingController();
  bool _isCodeSent = false;
  int _countdown = 0;

_phoneController_codeController 分别用于获取手机号和验证码输入框的内容。_isCodeSent 标记验证码是否已发送,_countdown 用于倒计时显示。使用 TextEditingController 可以方便地读取和清空输入框内容。

别忘了在页面销毁时释放控制器资源:

  
  void dispose() {
    _phoneController.dispose();
    _codeController.dispose();
    super.dispose();
  }

这是个好习惯,可以避免内存泄漏。

渐变背景设计

登录页面使用渐变背景,从顶部的粉色渐变到底部的深色,营造出沉浸式的视觉效果:

  
  Widget build(BuildContext context) {
    return Scaffold(
      body: Container(
        decoration: BoxDecoration(
          gradient: LinearGradient(
            begin: Alignment.topCenter, 
            end: Alignment.bottomCenter, 
            colors: [
              const Color(0xFFE91E63).withOpacity(0.3), 
              const Color(0xFF121212)
            ]
          )
        ),

LinearGradient 创建线性渐变,beginend 指定渐变方向为从上到下。顶部使用主题色的30%透明度,底部使用深灰色,过渡自然柔和。

        child: SafeArea(
          child: SingleChildScrollView(
            padding: const EdgeInsets.all(24),
            child: Column(
              crossAxisAlignment: CrossAxisAlignment.start,
              children: [
                IconButton(icon: const Icon(Icons.close), onPressed: () => Get.back()),
                const SizedBox(height: 40),

SafeArea 确保内容不会被状态栏遮挡。SingleChildScrollView 让页面在键盘弹出时可以滚动,避免输入框被遮挡。顶部的关闭按钮使用 Get.back() 返回上一页。

Logo和欢迎语

页面中央展示App的Logo和欢迎语,给用户一个友好的第一印象:

                const Center(
                  child: Icon(Icons.music_note, size: 80, color: Color(0xFFE91E63))
                ),
                const SizedBox(height: 16),
                const Center(
                  child: Text('音乐播放器', 
                    style: TextStyle(fontSize: 28, fontWeight: FontWeight.bold))
                ),
                const SizedBox(height: 8),
                const Center(
                  child: Text('登录后享受更多功能', style: TextStyle(color: Colors.grey))
                ),
                const SizedBox(height: 60),

Logo使用音符图标,大小设为80像素,颜色为主题粉色。标题使用28号粗体字,副标题使用灰色小字。这种布局简洁大方,符合现代App的设计风格。

手机号输入框

手机号输入框是登录的第一步,我们把它封装成独立的方法:

  Widget _buildPhoneInput() {
    return TextField(
      controller: _phoneController,
      keyboardType: TextInputType.phone,
      decoration: InputDecoration(
        hintText: '请输入手机号',
        prefixIcon: const Icon(Icons.phone_android, color: Colors.grey),
        filled: true,
        fillColor: const Color(0xFF1E1E1E),
        border: OutlineInputBorder(
          borderRadius: BorderRadius.circular(12), 
          borderSide: BorderSide.none
        ),
      ),
    );
  }

keyboardType: TextInputType.phone 让系统弹出数字键盘,方便用户输入手机号。prefixIcon 在输入框左侧显示手机图标,增强视觉提示。filled: true 配合 fillColor 设置输入框背景色为深灰色,与整体深色主题协调。borderSide: BorderSide.none 去掉边框线,让输入框看起来更简洁。

验证码输入框

验证码输入框比手机号输入框多了一个获取验证码按钮:

  Widget _buildCodeInput() {
    return TextField(
      controller: _codeController,
      keyboardType: TextInputType.number,
      decoration: InputDecoration(
        hintText: '请输入验证码',
        prefixIcon: const Icon(Icons.lock_outline, color: Colors.grey),
        suffixIcon: TextButton(
          onPressed: _countdown > 0 ? null : _sendCode,
          child: Text(
            _countdown > 0 ? '${_countdown}s' : '获取验证码', 
            style: TextStyle(
              color: _countdown > 0 ? Colors.grey : const Color(0xFFE91E63)
            )
          ),
        ),
        filled: true,
        fillColor: const Color(0xFF1E1E1E),
        border: OutlineInputBorder(
          borderRadius: BorderRadius.circular(12), 
          borderSide: BorderSide.none
        ),
      ),
    );
  }

suffixIcon 放置获取验证码按钮。当 _countdown > 0 时,按钮显示倒计时秒数并禁用点击;倒计时结束后显示"获取验证码"文字并恢复点击。按钮颜色也会根据状态变化,倒计时中显示灰色,可点击时显示粉色。

登录按钮

登录按钮使用圆角胶囊形状,宽度撑满整个屏幕:

  Widget _buildLoginButton() {
    return SizedBox(
      width: double.infinity,
      height: 50,
      child: ElevatedButton(
        style: ElevatedButton.styleFrom(
          backgroundColor: const Color(0xFFE91E63), 
          shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(25))
        ),
        onPressed: () {},
        child: const Text('登录', style: TextStyle(fontSize: 18, color: Colors.white)),
      ),
    );
  }

width: double.infinity 让按钮宽度填满父容器。borderRadius: BorderRadius.circular(25) 配合50像素的高度,形成完美的胶囊形状。按钮背景使用主题粉色,文字使用白色,对比鲜明。

第三方登录

第三方登录区域包含分割线和三个社交登录按钮:

  Widget _buildOtherLogin() {
    return Column(
      children: [
        const Row(
          children: [
            Expanded(child: Divider(color: Colors.grey)), 
            Padding(
              padding: EdgeInsets.symmetric(horizontal: 16), 
              child: Text('其他登录方式', style: TextStyle(color: Colors.grey))
            ), 
            Expanded(child: Divider(color: Colors.grey))
          ]
        ),
        const SizedBox(height: 24),

分割线使用 Row 布局,两边是 Expanded 包裹的 Divider,中间是文字说明。这种设计在登录页面很常见,清晰地划分了主要登录方式和备选方式。

        Row(
          mainAxisAlignment: MainAxisAlignment.center,
          children: [
            _buildSocialButton(Icons.wechat, Colors.green, '微信'),
            const SizedBox(width: 40),
            _buildSocialButton(Icons.apple, Colors.white, 'Apple'),
            const SizedBox(width: 40),
            _buildSocialButton(Icons.email, Colors.blue, '邮箱'),
          ],
        ),
      ],
    );
  }

三个社交登录按钮水平排列,间距40像素。每个按钮使用不同的图标和颜色,微信用绿色,Apple用白色,邮箱用蓝色。

社交登录按钮的实现:

  Widget _buildSocialButton(IconData icon, Color color, String label) {
    return Column(
      children: [
        Container(
          width: 50, height: 50,
          decoration: BoxDecoration(
            color: const Color(0xFF1E1E1E), 
            shape: BoxShape.circle
          ),
          child: Icon(icon, color: color),
        ),
        const SizedBox(height: 8),
        Text(label, style: const TextStyle(color: Colors.grey, fontSize: 12)),
      ],
    );
  }

每个按钮是一个圆形容器加底部文字标签的组合。容器背景使用深灰色,与输入框保持一致。通过参数传入图标、颜色和标签,实现代码复用。

用户协议提示

页面底部显示用户协议和隐私政策的提示文字:

  Widget _buildAgreement() {
    return const Center(
      child: Text.rich(
        TextSpan(
          text: '登录即表示同意 ',
          style: TextStyle(color: Colors.grey, fontSize: 12),
          children: [
            TextSpan(text: '用户协议', style: TextStyle(color: Color(0xFFE91E63))),
            TextSpan(text: ' 和 '),
            TextSpan(text: '隐私政策', style: TextStyle(color: Color(0xFFE91E63))),
          ],
        ),
      ),
    );
  }

Text.rich 配合 TextSpan 可以在一段文字中使用不同的样式。普通文字用灰色,"用户协议"和"隐私政策"用粉色高亮显示,提示用户这些是可点击的链接。

验证码倒计时逻辑

获取验证码的核心逻辑包含手机号校验和倒计时:

  void _sendCode() {
    if (_phoneController.text.length != 11) {
      Get.snackbar('提示', '请输入正确的手机号', 
        backgroundColor: Colors.red.withOpacity(0.8), 
        colorText: Colors.white
      );
      return;
    }
    setState(() {
      _isCodeSent = true;
      _countdown = 60;
    });
    _startCountdown();
  }

首先检查手机号是否为11位,不符合则弹出提示。校验通过后设置 _countdown 为60,开始倒计时。Get.snackbar 是GetX提供的便捷方法,可以快速显示一个顶部提示条。

  void _startCountdown() {
    Future.delayed(const Duration(seconds: 1), () {
      if (_countdown > 0) {
        setState(() => _countdown--);
        _startCountdown();
      }
    });
  }

倒计时使用递归调用 Future.delayed 实现。每隔1秒将 _countdown 减1,直到归零。setState 触发界面刷新,让用户看到倒计时的变化。

页面入口

登录页面的入口在个人中心页面,用户点击头像区域即可进入:

GestureDetector(
  onTap: () => Get.to(() => const LoginPage()),
  child: Container(
    // 头像和"点击登录"文字
  ),
)

使用 Get.to() 进行页面跳转,这是GetX路由的标准用法。

小结

本篇我们实现了一个功能完整的登录页面,包含手机号验证码登录、第三方登录入口、用户协议提示等常见元素。渐变背景和统一的深色输入框让页面视觉效果更加协调。验证码倒计时功能通过递归调用 Future.delayed 实现,简单有效。在实际项目中,还需要对接后端接口完成真正的登录逻辑,并处理登录状态的持久化存储。


欢迎加入开源鸿蒙跨平台社区:https://openharmonycrossplatform.csdn.net

Logo

AtomGit 是由开放原子开源基金会联合 CSDN 等生态伙伴共同推出的新一代开源与人工智能协作平台。平台坚持“开放、中立、公益”的理念,把代码托管、模型共享、数据集托管、智能体开发体验和算力服务整合在一起,为开发者提供从开发、训练到部署的一站式体验。

更多推荐