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

如何在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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.08 11:01:35