用到的插件
- 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,)
),
),
),
),
],
);
}
}