Flutter使用window_manager实现无边框窗口和圆角

532 阅读5分钟

用到的插件

  1. window_manager 窗口管理插件: window_manager

flutter_svg 显示svg图标: flutter_svg
有个bug左边和上边缩放相对的边都会闪烁

废话不多说直接上代码 pubspec.yaml文件

# Dependencies specify other packages that your package needs in order to work.
# To automatically upgrade your package dependencies to the latest versions
# consider running `flutter pub upgrade --major-versions`. Alternatively,
# dependencies can be manually updated by changing the version numbers below to
# the latest version available on pub.dev. To see which dependencies have newer
# versions available, run `flutter pub outdated`.
dependencies:
  flutter:
    sdk: flutter

  # The following adds the Cupertino Icons font to your application.
  # Use with the CupertinoIcons class for iOS style icons.
  cupertino_icons: ^1.0.8
  window_manager: ^0.4.3
  flutter_svg: ^2.0.17

实现WindowUtil类

该类是一个静态工具类,包含了一系列静态方法,用于初始化窗口、设置窗口属性、获取窗口信息等操作。
window_util.dart

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

class WindowUtil{
  //初始化
  static  void init(
      {
        required Size  size,
        required Size minimumSize,
        Size? maximumSize
      })async{
    //
    WidgetsFlutterBinding.ensureInitialized();
    //
    await windowManager.ensureInitialized();
    WindowOptions windowOptions=WindowOptions(
      size:size,
      minimumSize:minimumSize,
      maximumSize:maximumSize,//设置窗口的最小尺寸
      center:true,
      backgroundColor:Colors.transparent,
      skipTaskbar:false,
      titleBarStyle:TitleBarStyle.hidden,
    );
    windowManager.waitUntilReadyToShow(windowOptions,()async{
      // await windowManager.setBackgroundColor(Colors.transparent);
      await windowManager.show();
      await windowManager.focus();
      await windowManager.setAsFrameless();
      await windowManager.setTitle("奇");
      await windowManager.setResizable(true);
      await windowManager.setMaximizable(true);
    });


  }

  /// 设置可调整窗口大小
  static  void  setResizable(bool reSize){
    windowManager.setResizable(reSize);
  }
  /// 获取窗口大小
  static  Future<Size>getSize(){
    return windowManager.getSize();
  }
  /// 设置窗口大小
  static  void  setSize(Size  size){
    windowManager.setSize(size);
  }
  /// 获取窗口位置
  static  Future<Offset>getPosition(){
    return windowManager.getPosition();
  }
  /// 设置窗口位置
  static  void setPosition(Offset offset){
    windowManager.setPosition(offset);
  }
  /// 判断是否最大化
  static  Future<bool>isMaximized(){
    return windowManager.isMaximized();
  }
  /// 退出
  static Future<void> close() async {
    await windowManager.close();
  }
  /// 设置最大化
  static Future<void> setMaximize() async {
    await windowManager.maximize();
  }
  /// 设置最小化
  static Future<void> setMinimize() async {
    await windowManager.minimize();
  }
  /// 设置窗口化
  static Future<void> setUnMaximize() async {
    await windowManager.unmaximize();
  }
  /// 指定边调整窗口大小
  static  void  startResizing(ResizeEdge  resizeEdge){
    windowManager.startResizing(resizeEdge);
  }
  /// 使用指定边调整窗口大小
  static void scaleWindow(int s){
    switch(s){
      // 左
      case(1):
        WindowUtil.startResizing(ResizeEdge.left);
      case(2):
        // 右
        WindowUtil.startResizing(ResizeEdge.right);
      case(3):
        // 上
        WindowUtil.startResizing(ResizeEdge.top);
      case(4):
        // 下
        WindowUtil.startResizing(ResizeEdge.bottom);
      case(5):
        // 左上
        WindowUtil.startResizing(ResizeEdge.topLeft);
      case(6):
        // 右上
        WindowUtil.startResizing(ResizeEdge.topRight);
      case(7):
        // 左下
        WindowUtil.startResizing(ResizeEdge.bottomLeft);
      case(8):
        // 右下
        WindowUtil.startResizing(ResizeEdge.bottomRight);
    }
  }
}

在main函数里调用WindowUtil


```dart import 'package:flutter/material.dart'; import 'package:flutter_grpc/view/component/windows/window_util.dart'; import 'package:flutter_grpc/view/index.dart'; Future main() async { // 启用硬件加速 WidgetsFlutterBinding.ensureInitialized(); // 确保窗口渲染使用硬件加速 if (const bool.fromEnvironment('FLUTTER_WEB_USE_SKIA', defaultValue: false)) { // 如果是 Web 环境使用 Skia 渲染 // 这里可以添加更多 Web 相关的硬件加速配置 } WindowUtil.init( size: const Size(800, 600), minimumSize: const Size(600, 400), );

runApp(App()); }

### 自由缩放实现
index.dart
```dart
import 'package:flutter/material.dart';
import 'package:flutter_grpc/view/component/windows/window_zoom.dart';
import 'package:provider/provider.dart';
import 'package:window_manager/window_manager.dart';
import '../view_model/todo_view_model.dart';
import 'component/windows/windows_control.dart';


class App extends StatelessWidget {
  const App({super.key});

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      debugShowCheckedModeBanner: false,  // 去dug标签
      home: ChangeNotifierProvider(
          create: (context) => TodoViewModel(),
          child: AppBox()
      ),
    );
  }
}

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

  @override
  State<AppBox> createState() => _AppBoxState();
}

class _AppBoxState extends State<AppBox> with SingleTickerProviderStateMixin {
  late AnimationController _controller;

  @override
  void initState() {
    super.initState();
    _controller = AnimationController(vsync: this);
  }

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


  @override
  Widget build(BuildContext context) {
    return WindowZoom(
        child: Scaffold(
          backgroundColor: Colors.white,
          body: Column(
            children: [
              Container(
                color: Colors.black12,
                height: 30,
                child: Row(
                  mainAxisAlignment: MainAxisAlignment.start,
                  children: [
                    Expanded(
                        child: DragToMoveArea(
                            child:Container(
                              width: 20,
                            )
                        )
                    ),
                    WindowsControl()
                  ],
                ),
              )
            ],
          ),
        )
    );
  }
}

实现八个方向自由缩放
window_zoom.dart

import 'dart:io';

import 'package:flutter/material.dart';
import 'package:flutter_grpc/view/component/windows/window_util.dart';

///WinZoom
///无边框(无窗体)放大、缩小
///包裹最大容器
class WindowZoom extends StatefulWidget {
  final Widget  child;
  const WindowZoom({super.key, required this.child});

  @override
  State<WindowZoom> createState() => _WindowZoomState();
}

class _WindowZoomState extends State<WindowZoom> with WidgetsBindingObserver {
   // 边宽圆角
  static double borderWidth = 0.0;
  @override
  void initState() {
    super.initState();
    // 注册监听器
    WidgetsBinding.instance.addObserver(this);
  }

  @override
  void dispose() {
    // 移除监听器
    WidgetsBinding.instance.removeObserver(this);
    super.dispose();
  }

  @override
  void didChangeMetrics() {
    super.didChangeMetrics();
    _is();
  }

  void _is(){
    WindowUtil.isMaximized().then((value) {
      if(value){
        setState(() {
          borderWidth = 0.0;
        });
        return;
      }
      setState(() {
        // 边宽圆角5.0
        borderWidth = 5.0;
      });
    });

  }

  @override
  Widget build(BuildContext context) {
    return Stack(
      children: [
        Container(
          width:double.infinity,
          height:double.infinity,
          //android伪全屏,加入边距
          padding:Platform.isAndroid
              ? const EdgeInsets.symmetric(horizontal:374,vertical:173)
              : EdgeInsets.zero,
          clipBehavior:Clip.antiAliasWithSaveLayer,
          //外边距
          margin:EdgeInsets.all(borderWidth),
          decoration:BoxDecoration(
            //圆弧度
              borderRadius:BorderRadius.all(Radius.circular(borderWidth)),
              boxShadow:[
                BoxShadow(color:Color(0x33000000),blurRadius:3),
              ]),
          child: widget.child,
        ),
        WindowFrameLeft(borderWidth: borderWidth),
        WindowFrameRight(borderWidth: borderWidth),
        WindowFrameTop(borderWidth: borderWidth),
        WindowFrameBottom(borderWidth: borderWidth),

      ],
    );
  }
}

class WindowFrameLeft extends StatefulWidget {
  final double  borderWidth;
  const WindowFrameLeft({super.key, required this.borderWidth});

  @override
  State<WindowFrameLeft> createState() => _WindowFrameLeftState();
}

class _WindowFrameLeftState extends State<WindowFrameLeft> with SingleTickerProviderStateMixin {
  late AnimationController _controller;
  dynamic mouse=SystemMouseCursors.basic;
  late Size _windowSize;
  final double  _horn = 5;
  late int  _scaleID = 0;



  @override
  void initState() {
    super.initState();
    WindowUtil.getSize().then((value)=>_windowSize=value);
    _controller = AnimationController(vsync: this);
  }

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

  // 箭头
  void _mouseUpdate(e){

    // print(e.position.dx);
    if(e.position.dy<3){
      setState(() {
        mouse = SystemMouseCursors.resizeUpLeft;
        _scaleID = 5;
      });
      return;
    }
    if(e.position.dy>(_windowSize.height-_horn)){
      setState(() {
        mouse = SystemMouseCursors.resizeDownLeft;
        _scaleID = 7;
      });
      return;
    }
    setState(() {
      mouse = SystemMouseCursors.resizeLeft;
      _scaleID = 1;
    });
  }
  // 恢复
  void _cursorRestoration(){
    WindowUtil.getSize().then((value)=>_windowSize=value);
    setState(() {
      mouse=SystemMouseCursors.basic;
    });
  }


  @override
  Widget build(BuildContext context) {
    return Positioned(
      left: 0,
      top: 0,
      bottom: 0,
      width: widget.borderWidth,
      child: MouseRegion(
        cursor: mouse,
        onHover: (e)=>_mouseUpdate(e),
        onExit: (e)=>_cursorRestoration(),
        child: GestureDetector(
          onTapDown: (details){
            WindowUtil.scaleWindow(_scaleID);
          },
          child: Container(color: Colors.transparent),
        ),
      ),
    );
  }
}

class WindowFrameRight extends StatefulWidget {
  final double  borderWidth;
  const WindowFrameRight({super.key, required this.borderWidth});

  @override
  State<WindowFrameRight> createState() => _WindowFrameRightState();
}

class _WindowFrameRightState extends State<WindowFrameRight> with SingleTickerProviderStateMixin {
  late AnimationController _controller;
  dynamic mouse=SystemMouseCursors.basic;
  late Size _windowSize;
  final double  _horn = 5;
  late int  _scaleID = 0;

  @override
  void initState() {
    super.initState();
    WindowUtil.getSize().then((value)=>_windowSize=value);
    _controller = AnimationController(vsync: this);
  }

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


  // 箭头
  void _mouseUpdate(e){

    // print(e.position.dy);
    if(e.position.dy<3){
      setState(() {
        mouse = SystemMouseCursors.resizeUpRight;
        _scaleID = 6;
      });
      return;
    }
    if(e.position.dy>(_windowSize.height-_horn)){
      setState(() {
        mouse = SystemMouseCursors.resizeDownRight;
        _scaleID = 8;
      });
      return;
    }
    setState(() {
      mouse = SystemMouseCursors.resizeRight;
      _scaleID = 2;
    });
  }
  // 恢复
  void _cursorRestoration(){
    WindowUtil.getSize().then((value)=>_windowSize=value);
    setState(() {
      mouse=SystemMouseCursors.basic;
    });
  }

  @override
  Widget build(BuildContext context) {
    return Positioned(
      right: 0,
      top: 0,
      bottom: 0,
      width: widget.borderWidth,
      child: MouseRegion(
        cursor: mouse,
        onHover: (e)=>_mouseUpdate(e),
        onExit: (e)=>_cursorRestoration(),
        child: GestureDetector(
          onTapDown: (details) => {
            WindowUtil.scaleWindow(_scaleID)
          },
          child: Container(color: Colors.transparent),
        ),
      ),
    );
  }
}

class WindowFrameTop extends StatefulWidget {
  final double  borderWidth;
  const WindowFrameTop({super.key, required this.borderWidth});

  @override
  State<WindowFrameTop> createState() => _WindowFrameTopState();
}

class _WindowFrameTopState extends State<WindowFrameTop> with SingleTickerProviderStateMixin {
  late AnimationController _controller;
  dynamic mouse=SystemMouseCursors.basic;
  late Size _windowSize;
  final double  _horn = 5;
  late int  _scaleID = 0;

  @override
  void initState() {
    super.initState();
    WindowUtil.getSize().then((value)=>_windowSize=value);
    _controller = AnimationController(vsync: this);
  }

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


  // 箭头
  void _mouseUpdate(e){

    // print(e.position.dy);
    if(e.position.dx<3){
      setState(() {
        mouse = SystemMouseCursors.resizeUpLeft;
        _scaleID = 5;
      });
      return;
    }
    if(e.position.dy>(_windowSize.width-_horn)){
      setState(() {
        mouse = SystemMouseCursors.resizeUpRight;
        _scaleID = 6;
      });
      return;
    }
    setState(() {
      mouse = SystemMouseCursors.resizeUp;
      _scaleID = 3;
    });
  }
  // 恢复
  void _cursorRestoration(){
    WindowUtil.getSize().then((value)=>_windowSize=value);
    setState(() {
      mouse=SystemMouseCursors.basic;
    });
  }

  @override
  Widget build(BuildContext context) {
    return Positioned(
      right: 0,
      left: 0,
      top: 0,
      height: widget.borderWidth,
      child: MouseRegion(
        cursor: mouse,
        onHover: (e)=>_mouseUpdate(e),
        onExit: (e)=>_cursorRestoration(),
        child: GestureDetector(
          onTapDown: (details) => {
            WindowUtil.scaleWindow(_scaleID)
          },
          child: Container(color: Colors.transparent),
        ),
      ),
    );
  }
}

class WindowFrameBottom extends StatefulWidget {
  final double  borderWidth;
  const WindowFrameBottom({super.key, required this.borderWidth});

  @override
  State<WindowFrameBottom> createState() => _WindowFrameBottomState();
}

class _WindowFrameBottomState extends State<WindowFrameBottom> with SingleTickerProviderStateMixin {
  late AnimationController _controller;
  dynamic mouse=SystemMouseCursors.basic;
  late Size _windowSize;
  final double  _horn = 5;
  late int  _scaleID = 0;

  @override
  void initState() {
    super.initState();
    WindowUtil.getSize().then((value)=>_windowSize=value);
    _controller = AnimationController(vsync: this);
  }

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


  // 箭头
  void _mouseUpdate(e){

    // print(e.position.dy);
    if(e.position.dx<3){
      setState(() {
        mouse = SystemMouseCursors.resizeDownLeft;
        _scaleID = 7;
      });
      return;
    }
    if(e.position.dy>(_windowSize.width-_horn)){
      setState(() {
        mouse = SystemMouseCursors.resizeDownRight;
        _scaleID = 8;
      });
      return;
    }
    setState(() {
      mouse = SystemMouseCursors.resizeDown;
      _scaleID = 4;
    });
  }
  // 恢复
  void _cursorRestoration(){
    WindowUtil.getSize().then((value)=>_windowSize=value);
    setState(() {
      mouse=SystemMouseCursors.basic;
    });
  }

  @override
  Widget build(BuildContext context) {
    return Positioned(
      right: 0,
      left: 0,
      bottom: -5,
      height: widget.borderWidth,
      child: MouseRegion(
        cursor: mouse,
        onHover: (e)=>_mouseUpdate(e),
        onExit: (e)=>_cursorRestoration(),
        child: GestureDetector(
          onTapDown: (details) => {
            WindowUtil.scaleWindow(_scaleID)
          },
          child: Container(color: Colors.transparent),
        ),
      ),
    );
  }
}

实现窗口三个控制键

windows_control.dart

import 'package:flutter/material.dart';
import 'package:flutter_grpc/view/component/windows/window_util.dart';
import 'package:flutter_svg/svg.dart';
import 'package:window_manager/window_manager.dart';



// 显示退出确认对话框
Future<bool> showExitConfirmationDialog(BuildContext context) async {
  return await showDialog(
    context: context,
    builder: (BuildContext context) {
      return AlertDialog(
        title: const Text('确认退出'),
        content: const Text('你确定要退出应用程序吗?'),
        actions: <Widget>[
          TextButton(
            child: const Text('取消'),
            onPressed: () {
              Navigator.of(context).pop(false);
            },
          ),
          TextButton(
            child: const Text('确认'),
            onPressed: () {
              Navigator.of(context).pop(true);
            },
          ),
        ],
      );
    },
  )??false;
}

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

  @override
  State<WindowsControl> createState() => _WindowsControlState();
}

class _WindowsControlState extends State<WindowsControl> with WindowListener {
  // 窗口状态
  bool isMaximized = false;
  // 关闭
  String closeSvg = '''<svg xmlns="http://www.w3.org/2000/svg" width="512" height="512" viewBox="0 0 24 24"><path fill="#333333" d="m12 13.4l-4.9 4.9q-.275.275-.7.275t-.7-.275t-.275-.7t.275-.7l4.9-4.9l-4.9-4.9q-.275-.275-.275-.7t.275-.7t.7-.275t.7.275l4.9 4.9l4.9-4.9q.275-.275.7-.275t.7.275t.275.7t-.275.7L13.4 12l4.9 4.9q.275.275.275.7t-.275.7t-.7.275t-.7-.275z"/></svg>''';
  String closeSvgs = '''<svg xmlns="http://www.w3.org/2000/svg" width="512" height="512" viewBox="0 0 24 24"><g fill="none" fill-rule="evenodd"><path d="m12.593 23.258l-.011.002l-.071.035l-.02.004l-.014-.004l-.071-.035q-.016-.005-.024.005l-.004.01l-.017.428l.005.02l.01.013l.104.074l.015.004l.012-.004l.104-.074l.012-.016l.004-.017l-.017-.427q-.004-.016-.017-.018m.265-.113l-.013.002l-.185.093l-.01.01l-.003.011l.018.43l.005.012l.008.007l.201.093q.019.005.029-.008l.004-.014l-.034-.614q-.005-.018-.02-.022m-.715.002a.02.02 0 0 0-.027.006l-.006.014l-.034.614q.001.018.017.024l.015-.002l.201-.093l.01-.008l.004-.011l.017-.43l-.003-.012l-.01-.01z"/><path fill="#ffffff" d="m12 14.122l5.303 5.303a1.5 1.5 0 0 0 2.122-2.122L14.12 12l5.304-5.303a1.5 1.5 0 1 0-2.122-2.121L12 9.879L6.697 4.576a1.5 1.5 0 1 0-2.122 2.12L9.88 12l-5.304 5.304a1.5 1.5 0 1 0 2.122 2.12z"/></g></svg>''';
  // 最大
  String maximumSvg = '''<svg xmlns="http://www.w3.org/2000/svg" width="512" height="512" viewBox="0 0 24 24"><g fill="none" stroke="#333333" stroke-linecap="round" stroke-linejoin="round" stroke-width="2"><path d="M3 17a1 1 0 0 1 1-1h3a1 1 0 0 1 1 1v3a1 1 0 0 1-1 1H4a1 1 0 0 1-1-1zm1-5V6a2 2 0 0 1 2-2h12a2 2 0 0 1 2 2v12a2 2 0 0 1-2 2h-6"/><path d="M12 8h4v4m0-4l-5 5"/></g></svg>''';
  // 恢复窗口
  String restoreSvg = '''<svg xmlns="http://www.w3.org/2000/svg" width="512" height="512" viewBox="0 0 24 24"><g fill="none" stroke="#333333" stroke-linecap="round" stroke-linejoin="round" stroke-width="2"><path d="M3 17a1 1 0 0 1 1-1h3a1 1 0 0 1 1 1v3a1 1 0 0 1-1 1H4a1 1 0 0 1-1-1zm1-5V6a2 2 0 0 1 2-2h12a2 2 0 0 1 2 2v12a2 2 0 0 1-2 2h-6"/><path d="M15 13h-4V9m0 4l5-5"/></g></svg>''';
  // 最小
  String minimumSvg = '''<svg xmlns="http://www.w3.org/2000/svg" width="512" height="512" viewBox="0 0 24 24"><path fill="#333333" d="M7 21q-.425 0-.712-.288T6 20t.288-.712T7 19h10q.425 0 .713.288T18 20t-.288.713T17 21z"/></svg>''';
  // is关闭按钮悬浮
  bool isHovered = false;

  /// 启动页面
  @override
  void initState() {
    super.initState();
    windowManager.addListener(this);
    _checkMaximized();
  }

  /// 退出应用
  @override
  void onWindowClose(){
    bool isConfirmed = showExitConfirmationDialog(context) as bool;
    if (isConfirmed) {
      windowManager.destroy();
    }
  }

  /// 关闭页面
  @override
  void dispose() {
    windowManager.removeListener(this);
    super.dispose();
  }

  Future<void> _checkMaximized() async {
    bool maximized = await windowManager.isMaximized();
    // setState(() {
    //   isMaximized = maximized;
    // });
  }
  /// 最大化
  @override
  void onWindowMaximize() {
    setState(() {
      isMaximized = true;
    });
  }
  /// 恢复
  @override
  void onWindowUnmaximize() {
    setState(() {
      isMaximized = false;
    });
  }



  @override
  Widget build(BuildContext context) {
    return Row(
      children: [
        SizedBox(
          width: 35,
          child: RawMaterialButton(
            splashColor: Colors.transparent,
            onPressed: ()=> WindowUtil.setMinimize(),
            child: Center(
              child: SvgPicture.string(minimumSvg, width: 15, height: 15,)
            ),

          ),
        ),
        Container(
          width: 35,
          child: RawMaterialButton(
            splashColor: Colors.transparent,
            onPressed: ()=> isMaximized?WindowUtil.setUnMaximize():WindowUtil.setMaximize(),
            child: Center(
              child: SvgPicture.string(isMaximized?restoreSvg:maximumSvg, width: 15, height: 15,)
            ),

          ),
        ),
        Container(
          width: 35,
          child: MouseRegion(
            onEnter: (_) => setState(() => isHovered = true),
            onExit: (_) => setState(() => isHovered = false),
            child: RawMaterialButton(
              // 水波纹透明
              splashColor: Colors.transparent,
              elevation: 0,
              highlightElevation:0,
              // 按下
              highlightColor: Colors.red.shade900,
              // 进入
              hoverColor: Colors.red,
              onPressed: () async {
                Future<bool>isConfirmed = showExitConfirmationDialog(context);
                if(await isConfirmed){
                  WindowUtil.close();
                }
              },
              child: Center(
                  child: SvgPicture.string(isHovered ? closeSvgs : closeSvg, width: 15, height: 15,)
              ),

            ),
          ),
        ),
      ],
    );
  }
}

实现效果

image.png