You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何在Flutter应用中实现带自定义动画的Splash Screen启动页

Flutter 自定义动画启动页(Splash Screen)实现方案

核心实现逻辑

整个效果拆成三个独立阶段,不用复杂的自定义绘制,Flutter内置动画能力完全可以覆盖:

  • 启动衔接阶段:对齐原生启动页和Flutter启动页第一帧的视觉效果,彻底消除切页白屏
  • 动画播放阶段:按动效时序拆分每个UI元素的动画参数,独立控制位移、缩放、透明度、颜色变化
  • 路由跳转阶段:并行执行App初始化逻辑和动画播放,两个任务全部完成后平滑过渡到首页

分步实现步骤

1. 基础环境配置

  • 先在项目路由表中把初始路由指向自定义的SplashScreen页面,替换默认的首页占位
  • 在pubspec.yaml中注册启动页用到的logo、slogan、背景装饰等静态资源路径,避免运行时资源加载失败
  • 分别修改Android端android/app/src/main/res/drawable/launch_background.xml、iOS端ios/Runner/Base.lproj/LaunchScreen.storyboard,配置和Flutter启动页第一帧完全一致的静态背景+logo,原生启动页和Flutter页面切换时不会有任何视觉跳变

注意:不要在原生启动页放和Flutter启动页第一帧不一样的元素,否则会出现明显的闪跳

2. 动画控制器与动画实例初始化

在SplashScreen对应的State类中混入SingleTickerProviderStateMixin,在initState生命周期中初始化动画控制器,总时长和目标动效总时长对齐即可。
针对每个有动画效果的UI元素,单独创建动画实例,通过Interval控制每个动画的触发时间区间,通过CurvedAnimation配置动画的运动曲线,逐帧匹配参考效果:

late final AnimationController _animController;
// logo缩放弹入动画
late final Animation<double> logoScale;
// slogan文字淡入上移动画
late final Animation<Offset> sloganOffset;
late final Animation<double> sloganOpacity;
// 背景装饰渐显动画
late final Animation<double> bgDecorOpacity;

@override
void initState() {
  super.initState();
  // 总动画时长设为2200ms,和参考效果时长对齐
  _animController = AnimationController(
    vsync: this,
    duration: const Duration(milliseconds: 2200)
  );

  // logo动画:总时长前40%区间完成,带easeOutBack弹动效果
  logoScale = Tween<double>(begin: 0, end: 1).animate(
    CurvedAnimation(
      parent: _animController,
      curve: const Interval(0, 0.4, curve: Curves.easeOutBack)
    )
  );

  // slogan动画:总时长40%-70%区间完成,线性淡入上移
  sloganOffset = Tween<Offset>(begin: const Offset(0, 30), end: Offset.zero).animate(
    CurvedAnimation(
      parent: _animController,
      curve: const Interval(0.4, 0.7, curve: Curves.easeOut)
    )
  );
  sloganOpacity = Tween<double>(begin: 0, end: 1).animate(
    CurvedAnimation(
      parent: _animController,
      curve: const Interval(0.4, 0.7, curve: Curves.easeOut)
    )
  );

  // 背景装饰动画:总时长前20%区间完成淡入
  bgDecorOpacity = Tween<double>(begin: 0, end: 1).animate(
    CurvedAnimation(
      parent: _animController,
      curve: const Interval(0, 0.2, curve: Curves.easeIn)
    )
  );
}

// 记得页面销毁时释放控制器
@override
void dispose() {
  _animController.dispose();
  super.dispose();
}

如果有其他动效元素,按照相同逻辑新增动画实例即可:旋转效果用RotationTransition对应的Animation,颜色渐变用ColorTween生成动画值,路径动画可以用AlignPositioned配合Tween<Alignment>实现。

3. 动画视图组装

用AnimatedBuilder绑定动画控制器,避免动画刷新时整页重建,每个动效元素嵌套对应的动画过渡组件:

  • 缩放效果嵌套ScaleTransition
  • 平移效果嵌套SlideTransition
  • 透明度效果嵌套FadeTransition
  • 旋转效果嵌套RotationTransition

核心布局代码示例:

@override
Widget build(BuildContext context) {
  return Scaffold(
    backgroundColor: const Color(0xFFFFFFFF), // 和第一帧背景色完全一致
    body: AnimatedBuilder(
      animation: _animController,
      builder: (context, _) {
        return Stack(
          fit: StackFit.expand,
          children: [
            // 底层背景装饰
            Opacity(
              opacity: bgDecorOpacity.value,
              child: Image.asset('assets/images/splash_bg_decor.png', fit: BoxFit.cover,),
            ),
            // 居中logo
            Center(
              child: ScaleTransition(
                scale: logoScale,
                child: Image.asset('assets/images/splash_logo.png', width: 120, height: 120,),
              ),
            ),
            // 底部slogan
            Positioned(
              bottom: 128,
              left: 0,
              right: 0,
              child: FadeTransition(
                opacity: sloganOpacity,
                child: SlideTransition(
                  position: sloganOffset,
                  child: const Text(
                    '你的App标语',
                    textAlign: TextAlign.center,
                    style: TextStyle(fontSize: 16, color: Color(0xFF666666)),
                  ),
                ),
              ),
            )
          ],
        );
      }
    ),
  );
}

4. 动画播放与跳转逻辑

动画启动和App初始化任务并行执行,等两个任务全部完成后再执行路由跳转,既不会因为等初始化拖慢动画播放,也不会出现动画没播完就切页的问题。跳转时用自定义淡入过渡路由,和参考效果的收尾逻辑对齐:

// 初始化任务的完成标记
final Completer _initCompleter = Completer();

Future<void> _initAppData() async {
  // 这里放启动阶段要做的逻辑:检查登录态、拉取远程配置、预加载首屏资源等
  await Future.delayed(const Duration(milliseconds: 100)); // 替换成实际初始化逻辑
  _initCompleter.complete();
}

@override
void initState() {
  super.initState();
  // 两个任务并行执行
  _animController.forward();
  _initAppData();

  // 等待两个任务全部完成后跳转
  Future.wait([
    _animController.completed,
    _initCompleter.future
  ]).then((_) {
    Navigator.of(context).pushReplacement(
      PageRouteBuilder(
        transitionDuration: const Duration(milliseconds: 300),
        pageBuilder: (context, _, __) => const HomePage(), // 替换成你的首页
        transitionsBuilder: (context, anim, _, child) {
          return FadeTransition(opacity: anim, child: child);
        }
      )
    );
  });
}

效果调优技巧

  • 逐帧慢放参考视频,记录每个元素的动效开始/结束时间、运动幅度,调整对应动画的Interval区间、曲线参数、Tween起止值,很容易做到像素级对齐
  • 普通的位移、缩放、淡入淡出效果不要引入第三方动画库,Flutter内置组件完全覆盖,避免不必要的包体积增加;如果要做粒子、复杂路径动效,再考虑引入轻量的动画工具库
  • 启动页静态资源尽量压缩,单张图片大小控制在200KB以内,避免首帧加载慢导致启动卡顿

内容的提问来源于stack exchange,提问作者Akash Rajoria

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.27 21:27:45