在这里插入图片描述

启动页是用户打开应用看到的第一个界面,需要实现Logo展示、淡入动画和自动跳转功能。一个设计精美、过渡流畅的启动页,能让用户感觉应用很专业。

启动页功能需求

在动手写代码之前,先明确启动页要实现的功能:

视觉展示 - 显示应用Logo和名称,传达品牌形象
过渡动画 - 淡入动画让过渡更自然,提升用户体验
自动跳转 - 延时几秒后自动跳转到主页面
资源释放 - 正确释放动画资源,避免内存泄漏

创建启动页文件

lib/screens目录下创建splash_screen.dart

import 'package:flutter/material.dart';
import 'main_screen.dart';

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

  
  State<SplashScreen> createState() => _SplashScreenState();
}

代码解析

  • 使用StatefulWidget因为需要处理动画和延时跳转
  • 导入main_screen.dart用于跳转到主页面
  • 启动页也是一个页面,所以放在screens目录

配置动画控制器

class _SplashScreenState extends State<SplashScreen> 
    with SingleTickerProviderStateMixin {
  late AnimationController _controller;
  late Animation<double> _fadeAnimation;

代码解析

  • SingleTickerProviderStateMixin - 提供动画"心跳",驱动动画运行
  • late - 延迟初始化,在initState中赋值
  • AnimationController - 控制动画的播放、暂停、停止
  • Animation<double> - 透明度动画,值从0到1

为什么需要SingleTickerProviderStateMixin:动画需要一个"心跳"来驱动,这个mixin就提供了这个心跳。如果页面有多个动画,用TickerProviderStateMixin。

初始化动画和跳转

  
  void initState() {
    super.initState();
    
    // 创建动画控制器
    _controller = AnimationController(
      duration: const Duration(seconds: 2),
      vsync: this,
    );
    
    // 创建淡入动画
    _fadeAnimation = Tween<double>(begin: 0.0, end: 1.0).animate(_controller);
    
    // 启动动画
    _controller.forward();
    
    // 延时跳转
    Future.delayed(const Duration(seconds: 3), () {
      if (mounted) {
        Navigator.of(context).pushReplacement(
          MaterialPageRoute(builder: (_) => const MainScreen()),
        );
      }
    });
  }

代码解析

  • duration: Duration(seconds: 2) - 动画持续2秒,时间要把握好
  • vsync: this - 传入"心跳",this就是SingleTickerProviderStateMixin
  • Tween<double>(begin: 0.0, end: 1.0) - 透明度从0(完全透明)到1(完全不透明)
  • _controller.forward() - 启动动画,让透明度从0变到1
  • Future.delayed - 延迟3秒后跳转,动画2秒+停留1秒
  • mounted - 检查Widget是否还在树中,避免跳转时报错
  • pushReplacement - 替换当前页面,用户无法返回启动页

为什么是3秒:动画2秒,再多停留1秒让用户看清楚Logo。太短看不清,太长用户会不耐烦。

释放资源

  
  void dispose() {
    _controller.dispose();
    super.dispose();
  }

代码解析

  • 在dispose中释放AnimationController,避免内存泄漏
  • 所有手动管理的资源(动画控制器、文本控制器、滚动控制器等)都要在dispose里释放
  • 这是个好习惯,养成后可以避免很多内存问题

构建UI界面

  
  Widget build(BuildContext context) {
    return Scaffold(
      body: Container(
        decoration: BoxDecoration(
          gradient: LinearGradient(
            begin: Alignment.topLeft,
            end: Alignment.bottomRight,
            colors: [
              Theme.of(context).colorScheme.primary,
              Theme.of(context).colorScheme.secondary,
            ],
          ),
        ),

代码解析

  • Scaffold - Flutter的脚手架Widget,提供Material Design基本布局
  • BoxDecoration - 添加装饰效果
  • LinearGradient - 线性渐变背景,从左上到右下
  • 使用主题颜色,自动适配深色/浅色模式

为什么用渐变背景:纯色背景太单调,渐变背景更有设计感,既美观又能体现应用的品牌色。

添加Logo和文字

        child: Center(
          child: FadeTransition(
            opacity: _fadeAnimation,
            child: Column(
              mainAxisAlignment: MainAxisAlignment.center,
              children: const [
                Icon(
                  Icons.newspaper,
                  size: 100,
                  color: Colors.white,
                ),
                SizedBox(height: 24),
                Text(
                  '今日资讯',
                  style: TextStyle(
                    fontSize: 32,
                    fontWeight: FontWeight.bold,
                    color: Colors.white,
                  ),
                ),
                SizedBox(height: 8),
                Text(
                  '随时随地,掌握最新资讯',
                  style: TextStyle(
                    fontSize: 16,
                    color: Colors.white70,
                  ),
                ),
              ],
            ),
          ),
        ),
      ),
    );
  }
}

代码解析

  • FadeTransition - 淡入淡出动画Widget,根据opacity值改变透明度
  • Icon(Icons.newspaper) - 新闻图标,大小100,颜色白色
  • 主标题字号32,加粗显示,让标题更醒目
  • 副标题字号16,透明度70%,形成层次感
  • SizedBox - 控制间距,24和8是经过调整的最佳值

图标的选择:用Material Icons的newspaper图标,因为我们是新闻应用。实际项目中,可以用自己设计的Logo图片,用Image.asset()加载。

完整代码

完整的splash_screen.dart文件:

import 'package:flutter/material.dart';
import 'main_screen.dart';

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

  
  State<SplashScreen> createState() => _SplashScreenState();
}

class _SplashScreenState extends State<SplashScreen> 
    with SingleTickerProviderStateMixin {
  late AnimationController _controller;
  late Animation<double> _fadeAnimation;

  
  void initState() {
    super.initState();
    _controller = AnimationController(
      duration: const Duration(seconds: 2),
      vsync: this,
    );
    _fadeAnimation = Tween<double>(begin: 0.0, end: 1.0).animate(_controller);
    
    _controller.forward();
    
    Future.delayed(const Duration(seconds: 3), () {
      if (mounted) {
        Navigator.of(context).pushReplacement(
          MaterialPageRoute(builder: (_) => const MainScreen()),
        );
      }
    });
  }

  
  void dispose() {
    _controller.dispose();
    super.dispose();
  }

  
  Widget build(BuildContext context) {
    return Scaffold(
      body: Container(
        decoration: BoxDecoration(
          gradient: LinearGradient(
            begin: Alignment.topLeft,
            end: Alignment.bottomRight,
            colors: [
              Theme.of(context).colorScheme.primary,
              Theme.of(context).colorScheme.secondary,
            ],
          ),
        ),
        child: Center(
          child: FadeTransition(
            opacity: _fadeAnimation,
            child: Column(
              mainAxisAlignment: MainAxisAlignment.center,
              children: const [
                Icon(
                  Icons.newspaper,
                  size: 100,
                  color: Colors.white,
                ),
                SizedBox(height: 24),
                Text(
                  '今日资讯',
                  style: TextStyle(
                    fontSize: 32,
                    fontWeight: FontWeight.bold,
                    color: Colors.white,
                  ),
                ),
                SizedBox(height: 8),
                Text(
                  '随时随地,掌握最新资讯',
                  style: TextStyle(
                    fontSize: 16,
                    color: Colors.white70,
                  ),
                ),
              ],
            ),
          ),
        ),
      ),
    );
  }
}

整个文件不到100行,但实现了淡入动画、延时跳转、渐变背景等效果。代码简洁清晰,易于维护。

运行测试

flutter run

注意:要完整测试启动页,需要重新启动应用(按R热重启),热重载(r)看不到启动页,因为启动页只在应用启动时显示一次。

应用启动后会看到:

  1. 渐变背景从左上到右下
  2. Logo和文字淡入显示(2秒)
  3. 停留1秒后自动跳转到主页面

如果动画不够流畅,可能是模拟器性能不够。在真机上测试会流畅很多。

优化建议

添加版本号显示

可以在启动页底部显示应用版本号:

Positioned(
  bottom: 50,
  child: Text(
    'v1.0.0',
    style: TextStyle(color: Colors.white60, fontSize: 12),
  ),
)

实际项目中,版本号应该从package_info_plus包动态获取,而不是硬编码。

添加加载指示器

如果启动时要加载数据,可以在底部加个加载指示器:

CircularProgressIndicator(
  valueColor: AlwaysStoppedAnimation<Color>(Colors.white),
)

动态时长控制

实现动态时长,数据加载完就跳转,但至少显示2秒,最多5秒:

final minDuration = Duration(seconds: 2);
final maxDuration = Duration(seconds: 5);
final startTime = DateTime.now();

// 加载数据
await loadData().timeout(maxDuration);

// 确保至少显示minDuration
final elapsed = DateTime.now().difference(startTime);
if (elapsed < minDuration) {
  await Future.delayed(minDuration - elapsed);
}

这段代码确保启动页至少显示2秒(让用户看到品牌),但不超过5秒(避免等待太久)。

处理返回键

Android用户可能在启动页按返回键,这时候应该直接退出应用:

WillPopScope(
  onWillPop: () async {
    return true; // 允许返回,会退出应用
  },
  child: Scaffold(...),
)

设计原则

做启动页时要遵循的原则:

简洁至上 - 只需要Logo、名称、Slogan就够了,不要放太多内容
品牌一致 - 配色、字体、风格要和应用整体保持一致
性能优先 - 要快速加载,不要用太大的图片或复杂的动画
适配主题 - 支持深色模式,不要在深色模式下显示刺眼的白色背景
避免广告 - 不要在启动页放广告,这是最糟糕的做法

启动页时长的思考

启动页显示多久合适?这需要平衡用户体验和品牌展示:

太短的问题(1秒):用户可能还没看清Logo就跳转了,失去了品牌展示的意义
太长的问题(5秒以上):用户会觉得应用启动慢,体验很差
建议时长:2-3秒最合适,既能展示品牌,又不会让用户等太久

如果需要加载数据,最多5秒,超时就跳转。用户可以进入主页面后再加载数据。


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

在这里你可以找到更多Flutter开发资源,与其他开发者交流经验,共同进步。

Logo

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

更多推荐