如何在Flutter桌面应用中创建类似Discord的动画启动屏
Flutter桌面应用实现Discord风格动画启动屏方案
因为flutter_native_splash暂不支持Windows和Mac平台,以下是几种可行的实现方式:
方案1:纯Flutter路由实现(跨平台通用)
这是最快落地的方案,无需修改原生代码,直接在Flutter层面做启动屏动画:
- 核心思路:将启动屏设为App的第一个路由,在该页面内实现渐变背景、Logo动画,同时执行初始化逻辑,完成后跳转到主页面。
- 示例代码:
import 'package:flutter/material.dart'; void main() => runApp(const MyApp()); class MyApp extends StatelessWidget { const MyApp({super.key}); @override Widget build(BuildContext context) { return MaterialApp( home: const SplashScreen(), routes: { '/home': (context) => const HomePage(), }, ); } } class SplashScreen extends StatefulWidget { const SplashScreen({super.key}); @override State<SplashScreen> createState() => _SplashScreenState(); } class _SplashScreenState extends State<SplashScreen> with SingleTickerProviderStateMixin { late AnimationController _controller; late Animation<double> _fadeAnimation; @override 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(); _doInitialization(); } // 模拟初始化逻辑(替换为你的实际代码) void _doInitialization() async { await Future.delayed(const Duration(seconds: 3)); if (mounted) { Navigator.pushReplacementNamed(context, '/home'); } } @override void dispose() { _controller.dispose(); super.dispose(); } @override Widget build(BuildContext context) { return Scaffold( body: Container( // 实现Discord风格渐变背景 decoration: const BoxDecoration( gradient: LinearGradient( colors: [Color(0xFF5865F2), Color(0xFF23272A)], begin: Alignment.topLeft, end: Alignment.bottomRight, ), ), child: Center( // Logo淡入动画 child: FadeTransition( opacity: _fadeAnimation, child: Image.asset('assets/discord_logo.png', width: 120), ), ), ), ); } } class HomePage extends StatelessWidget { const HomePage({super.key}); @override Widget build(BuildContext context) { return Scaffold( appBar: AppBar(title: const Text('主页面')), body: const Center(child: Text('启动完成')), ); } }
- 注意:Flutter引擎启动瞬间会有短暂空白,若要完全消除这种空白,需要用原生方案。
方案2:原生层面实现(极致原生体验)
Discord的启动屏是原生窗口渲染的,完全没有Flutter启动空白,需要分别处理Windows和Mac:
Windows平台
修改windows/runner/main.cpp,在Flutter引擎初始化前创建无边框原生窗口显示启动屏:
- 用Win32 API创建窗口,通过GDI+绘制渐变背景和Logo动画;
- 等Flutter引擎初始化完成后,关闭原生启动窗口,显示Flutter主窗口。
核心代码片段示例:
// 创建启动屏窗口 HWND splashWindow = CreateWindowEx( 0, L"SplashWindowClass", L"", WS_POPUP | WS_VISIBLE, CW_USEDEFAULT, CW_USEDEFAULT, 400, 400, NULL, NULL, hInstance, NULL ); // 绘制渐变背景和Logo(GDI+实现) // ... // 初始化Flutter引擎 FlutterEngineRunArguments args = {}; // 填充初始化参数 flutter_run_engine(args); // 引擎启动完成后关闭启动屏 DestroyWindow(splashWindow);
Mac平台
修改macos/Runner/MainFlutterWindow.swift,在FlutterViewController加载前显示原生启动屏窗口:
- 创建无边框NSWindow,设置渐变背景,添加CAAnimation实现Logo动画;
- 监听FlutterViewController加载完成事件,关闭启动屏窗口。
核心代码片段示例:
class SplashWindow: NSWindow { override init(contentRect: NSRect, styleMask style: NSWindow.StyleMask, backing backingStoreType: NSWindow.BackingStoreType, defer flag: Bool) { super.init(contentRect: contentRect, styleMask: .borderless, backing: backingStoreType, defer: flag) // 设置渐变背景 let gradient = NSGradient(colors: [NSColor(hex: 0x5865F2), NSColor(hex: 0x23272A)])! let view = NSView(frame: contentRect) view.wantsLayer = true gradient.draw(in: view.bounds, angle: 45) contentView = view // 添加Logo和淡入动画 let logoImageView = NSImageView(image: NSImage(named: "discord_logo")!) logoImageView.frame = CGRect(x: contentRect.midX - 60, y: contentRect.midY - 60, width: 120, height: 120) view.addSubview(logoImageView) let fadeAnimation = CABasicAnimation(keyPath: "opacity") fadeAnimation.fromValue = 0 fadeAnimation.toValue = 1 fadeAnimation.duration = 2 logoImageView.layer?.add(fadeAnimation, forKey: "fadeIn") } } // 在MainFlutterWindow中 override func awakeFromNib() { // 显示启动屏 let splashRect = NSRect(x: NSScreen.main!.frame.midX - 200, y: NSScreen.main!.frame.midY - 200, width: 400, height: 400) let splashWindow = SplashWindow(contentRect: splashRect, styleMask: .borderless, backing: .buffered, defer: false) splashWindow.makeKeyAndOrderFront(nil) super.awakeFromNib() // 监听Flutter视图加载完成 flutterViewController?.view.addObserver(self, forKeyPath: "superview", options: .new, context: nil) } override func observeValue(forKeyPath keyPath: String?, of object: Any?, change: [NSKeyValueChangeKey : Any]?, context: UnsafeMutableRawPointer?) { if keyPath == "superview" { splashWindow.close() flutterViewController?.view.removeObserver(self, forKeyPath: "superview") } }
方案3:使用桌面专用第三方包
可以尝试专门支持桌面平台的启动屏包,比如desktop_splash,支持Windows和Mac,可配置静态启动屏并结合自定义动画,只需按文档配置原生项目即可快速实现。
总结
- 追求快速跨平台统一:选方案1;
- 追求原生级无空白体验:选方案2;
- 平衡效率与体验:选方案3的第三方包。
内容的提问来源于stack exchange,提问作者Parthish Joshi
相关产品推荐
相关产品推荐

