如何在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
相关产品推荐
相关产品推荐

