Flutter desktop端多屏幕展示问题处理
目前越来越多的人用Flutter来做桌面程序的开发,很多应用场景在Flutter开发端还不是很成熟,有些场景目前还没有很好的插件来支持,所以落地Flutter桌面版还是要慎重。
下面来说一下近期我遇到的一个问题,之前遇到一个需要双屏展示的应用场景,而且双屏还要有交互,下面就介绍这种双屏的功能怎么实现。
首先介绍需要用到的插件:
desktop_multi_window
desktop_multi_window 用于实现一个应用可以打开多个窗口的功能,主要适配macOS、Windows以及Linux系统。
window_size
window_size 是google官方提供的一个插件,用于获取系统所有屏幕的信息,其中最重要的就是可以获取屏幕的位置,这个功能的作用是在使用desktop_multi_window打开一个新窗口时,通过window_size获取副屏的坐标位置,然后直接将新窗口定位到副屏上。
下面贴代码:
import 'dart:convert';
import 'dart:ui';import 'package:collection/collection.dart';
import 'package:desktop_lifecycle/desktop_lifecycle.dart';
import 'package:desktop_multi_window/desktop_multi_window.dart';
import 'package:flutter/material.dart';
import 'package:flutter/rendering.dart';
import 'package:flutter_multi_window_example/event_widget.dart';
import 'package:device_info_plus/device_info_plus.dart';
import 'package:window_size/window_size.dart';void main(List<String> args) {if (args.firstOrNull == 'multi_window') {final windowId = int.parse(args[1]);final argument = args[2].isEmpty? const {}: jsonDecode(args[2]) as Map<String, dynamic>;runApp(_ExampleSubWindow(windowController: WindowController.fromWindowId(windowId),args: argument,));} else {runApp(const _ExampleMainWindow());}
}class _ExampleMainWindow extends StatefulWidget {const _ExampleMainWindow({Key? key}) : super(key: key);@overrideState<_ExampleMainWindow> createState() => _ExampleMainWindowState();
}class _ExampleMainWindowState extends State<_ExampleMainWindow> {@overridevoid initState() {// TODO: implement initStatesuper.initState();}@overrideWidget build(BuildContext context) {return const MaterialApp(home: App(),);}
}class App extends StatefulWidget{const App({Key? key}) : super(key: key);@overrideState<StatefulWidget> createState() {return AppState();}}
class AppState extends State<App>{List<Screen> screenList=[];@overridevoid initState() {// TODO: implement initStatesuper.initState();initDevice();}void initDevice()async{screenList=await getScreenList();screenList.forEach((element) {print(element.frame);});}@overrideWidget build(BuildContext context) {return Scaffold(appBar: AppBar(title: const Text('Plugin example app'),),body: Column(children: [TextButton(onPressed: () async {final window =await DesktopMultiWindow.createWindow(jsonEncode({'args1': 'Sub window','args2': 100,'args3': true,'business': 'business_test',}));window..setFrame( screenList[screenList.length-1].frame)..setTitle('Another window')..show();},child: const Text('Create a new World!'),),TextButton(child: const Text('Send event to all sub windows'),onPressed: () async {final subWindowIds =await DesktopMultiWindow.getAllSubWindowIds();for (final windowId in subWindowIds) {DesktopMultiWindow.invokeMethod(windowId,'broadcast','Broadcast from main window',);}},),Expanded(child: EventWidget(controller: WindowController.fromWindowId(0)),)],),);}}class _ExampleSubWindow extends StatelessWidget {const _ExampleSubWindow({Key? key,required this.windowController,required this.args,}) : super(key: key);final WindowController windowController;final Map? args;@overrideWidget build(BuildContext context) {return MaterialApp(home: Scaffold(appBar: AppBar(title: const Text('Plugin example app'),),body: Column(children: [if (args != null)Text('Arguments: ${args.toString()}',style: const TextStyle(fontSize: 20),),ValueListenableBuilder<bool>(valueListenable: DesktopLifecycle.instance.isActive,builder: (context, active, child) {if (active) {return const Text('Window Active');} else {return const Text('Window Inactive');}},),TextButton(onPressed: () async {windowController.close();},child: const Text('Close this window'),),Expanded(child: EventWidget(controller: windowController)),],),),);}
}
event_widget.dart
import 'package:desktop_multi_window/desktop_multi_window.dart';
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';class EventWidget extends StatefulWidget {const EventWidget({Key? key, required this.controller}) : super(key: key);final WindowController controller;@overrideState<EventWidget> createState() => _EventWidgetState();
}class MessageItem {const MessageItem({this.content, required this.from, required this.method});final int from;final dynamic content;final String method;@overrideString toString() {return '$method($from): $content';}@overrideint get hashCode => Object.hash(from, content, method);@overridebool operator ==(Object other) {if (identical(this, other)) {return true;}if (other.runtimeType != runtimeType) {return false;}final MessageItem typedOther = other as MessageItem;return typedOther.from == from && typedOther.content == content;}
}class _EventWidgetState extends State<EventWidget> {final messages = <MessageItem>[];final textInputController = TextEditingController();final windowInputController = TextEditingController();@overridevoid initState() {super.initState();DesktopMultiWindow.setMethodHandler(_handleMethodCallback);}@overridedispose() {DesktopMultiWindow.setMethodHandler(null);super.dispose();}Future<dynamic> _handleMethodCallback(MethodCall call, int fromWindowId) async {if (call.arguments.toString() == "ping") {return "pong";}setState(() {messages.insert(0,MessageItem(from: fromWindowId,method: call.method,content: call.arguments,),);});}@overrideWidget build(BuildContext context) {void submit() async {final text = textInputController.text;if (text.isEmpty) {return;}final windowId = int.tryParse(windowInputController.text);textInputController.clear();final result =await DesktopMultiWindow.invokeMethod(windowId!, "onSend", text);debugPrint("onSend result: $result");}return Column(children: [Expanded(child: ListView.builder(itemCount: messages.length,reverse: true,itemBuilder: (context, index) =>_MessageItemWidget(item: messages[index]),),),Row(children: [SizedBox(width: 100,child: TextField(controller: windowInputController,decoration: const InputDecoration(labelText: 'Window ID',),inputFormatters: [FilteringTextInputFormatter.digitsOnly],),),Expanded(child: TextField(controller: textInputController,decoration: const InputDecoration(hintText: 'Enter message',),onSubmitted: (text) => submit(),),),IconButton(icon: const Icon(Icons.send),onPressed: submit,),],),],);}
}class _MessageItemWidget extends StatelessWidget {const _MessageItemWidget({Key? key, required this.item}) : super(key: key);final MessageItem item;@overrideWidget build(BuildContext context) {return ListTile(title: Text("${item.method}(${item.from})"),subtitle: Text(item.content.toString()),);}
}
重点代码位置:
void main(List<String> args) {if (args.firstOrNull == 'multi_window') {final windowId = int.parse(args[1]);final argument = args[2].isEmpty? const {}: jsonDecode(args[2]) as Map<String, dynamic>;runApp(_ExampleSubWindow(windowController: WindowController.fromWindowId(windowId),args: argument,));} else {runApp(const _ExampleMainWindow());}
}
这块是判断显示副屏还是主屏,副屏创建也会走main函数。
void initDevice()async{screenList=await getScreenList();screenList.forEach((element) {print(element.frame);});}
这个是用window_size 插件中的getScreenList(),获取系统的所有屏幕信息。
TextButton(onPressed: () async {final window =await DesktopMultiWindow.createWindow(jsonEncode({'args1': 'Sub window','args2': 100,'args3': true,'business': 'business_test',}));window..setFrame( screenList[screenList.length-1].frame)..setTitle('Another window')..show();},child: const Text('Create a new World!'),),
这块是开启新窗口的代码,其中setFrame( screenList[screenList.length-1].frame)是将副屏的frame给到窗口,这样创建出来的窗口就是直接在副屏的位置,同时是全屏的状态。
相关文章:
Flutter desktop端多屏幕展示问题处理
目前越来越多的人用Flutter来做桌面程序的开发,很多应用场景在Flutter开发端还不是很成熟,有些场景目前还没有很好的插件来支持,所以落地Flutter桌面版还是要慎重。 下面来说一下近期我遇到的一个问题,之前遇到一个需要双屏展示的…...

每天10个前端小知识 【Day 9】
👩 个人主页:不爱吃糖的程序媛 🙋♂️ 作者简介:前端领域新星创作者、CSDN内容合伙人,专注于前端各领域技术,成长的路上共同学习共同进步,一起加油呀! ✨系列专栏:前端…...
Elasticsearch的读写搜索过程
问题 Elasticsearch在读写数据的过程是什么样的?你该如何理解这个问题! Elasticsearch的写数据过程 客户端选择一个节点发送请求,这个时候我们所说的这个节点就是协调节点(coordinating node)协调节点对document进行了路由&am…...

线上服务质量的问题该如何去处理?你有什么思路?
线上服务质量的问题该如何去处理?你有什么思路? 目录:导读 发现线上故障 处理线上故障 修复线上故障 运营线上质量 就是前几天有个同学问了我一个问题:目前业内高可用部署主要采用方案? 看到这个问题,…...
IOC 配置,依赖注入的三种方式
xml 配置 顾名思义,就是将bean的信息配置.xml文件里,通过Spring加载文件为我们创建bean。这种方式出现很多早前的SSM项目中,将第三方类库或者一些配置工具类都以这种方式进行配置,主要原因是由于第三方类不支持Spring注解。 优点…...

自动机,即有限状态机
文章目录一、问题来源二、题目描述三、题解中的自动机四、自动机学习五、有限状态机的使用场景一、问题来源 今天做力克题目的时候看到了字符串转换整数的一道算法题,其中又看到了题解中有自动机的概念,所以在这里对自动机做个笔记。题目链接 二、题目描…...

第一部分:简单句——第一章:简单句的核心——二、简单句的核心变化(主语/宾语/表语的变化)
二、简单句的核心变化 简单句的核心变化其实就是 一主一谓(n. v.) 表达一件事情,谓语动词是其中最重要的部分,谓语动词的变化主要有四种:三态加一否(时态、语态、情态、否定),其中…...

VSCode Markdown写作引入符合规范的参考文献
Markdown可以用来写论文,写论文的时候无一例外要用到参考文献,今天来谈谈怎么自动生成参考文献。之前讲了怎么导出的pdf,文章在这里 VSCode vscode-pandoc插件将中文Markdown转换为好看的pdf文档(使用eisvogel模板) …...

电子学会2022年12月青少年软件编程(图形化)等级考试试卷(四级)答案解析
目录 一、单选题(共15题,共30分) 二、判断题(共10题,共20分) 三、编程题(共3题,共50分) 青少年软件编程(图形化)等级考试试卷(四级) 一、单选题(共15题,共30分) 1. 运行下列程序…...

JUC并发编程学习笔记(一)——知识补充(Threadlocal和引用类型)
强引用、弱引用、软引用、虚引用 Java执行 GC(垃圾回收)判断对象是否存活有两种方式,分别是引用计数法和引用链法(可达性分析法)。 **引用计数:**Java堆中给每个对象都有一个引用计数器,每当某个对象在其它地方被引用时,该对象的…...

2022级上岸浙理工MBA的复试经验提炼和备考建议
在等待联考成绩出来的那段时间,虽然内心很忐忑,但还是为复试在积极的做准备,虽然也进行了估分大概有201分,但成绩和分数线没下来之前,只能尽量多做些一些准备把。因为笔试报了达立易考的辅导班,对于浙江理工…...

人大金仓数据库索引的应用与日常运维
索引的应用 一、常见索引及适应场景 BTREE索引 是KES默认索引,采用B树实现。 适用场景 范围查询和优化排序操作。 不支持特别长的字段。 HASH索引 先对索引列计算一个散列值(类似md5、sha1、crc32),然后对这个散列值以顺序…...
20230211英语学习
Six Lifestyle Choices to Slow Memory Decline 研究发现,生活方式真能帮助记忆“抗衰”? A combination of healthy lifestyle choices such as eating well, regularly exercising, playing cards and socialising at least twice a week may help sl…...
5G图书推荐
无线通信专业书籍推荐 1.无线通信原理:基于MATLAB的实践,作者:李珊,出版社:清华大学出版社 2.无线通信系统:原理、设计与应用,作者:肖宇,出版社:电子工业出版…...

【Linux下代码调试工具】gdb 的基本使用
gdb的基本使用前言准备gdb工具调试须知gdb的基本指令进入调试退出调试显示代码及函数内容运行程序给程序打断点查看断点位置断点使能取消断点逐过程调试逐语句调试运行到下一个断点查看变量的值变量值常显示取消变量值常显示前言 在主页前面的几篇文章已经介绍了Vim编辑器及Ma…...

UART和RS232、RS485的联系和区别、以及对软件编程的影响
1、串口、UART、RS232、RS485概念的理解 (1)狭义上的串口:指的是串口协议,就是时序图、数据收发先后顺序等,是抽象出来的协议; (2)广义上的串口:指的是符合串口协议的接口,UART、RS232、RS485在实际工作中都…...
ajax是什么?咋实现的
创建交互式网页应用的网页开发技术 再不重新加载整个网页的前提下,与服务器交换数据并且更新部分内容 简单来说就是无页面刷新的数据交互 通过创建xmlhttprequest对象向服务器异步发送请求从而获取数据,然后操作dom更新内容 1,创建xmlhttpr…...

AI推理计算框架中的内存优化
背景 内存管理是AI计算中非常重要的一部分。我们希望模型计算时占用内存尽可能小,这样我们训练或推理时就可以用更大的batch size使其尽快收敛,或者提高吞吐率。又或者让我们可以使用参数更多、或更复杂的模型从而达到更好的准确率。由于现代深度学习模…...

C语言学习小结(1)——初认识C语言
一、C语言概念 C语言是一门通用计算机编程语言,广泛应用于底层开发。C语言的设计目标是提供一种能以简易 的方式编译、处理低级存储器、产生少量的机器码以及不需要任何运行环境支持便能运行的编程语言。尽管C语言提供了许多低级处理的功能,但仍然保持着…...

30分钟吃掉wandb可视化自动调参
wandb.sweep: 低代码,可视化,分布式 自动调参工具。使用wandb 的 sweep 进行超参调优,具有以下优点。(1)低代码:只需配置一个sweep.yaml配置文件,或者定义一个配置dict,几乎不用编写调参相关代码。(2)可视化…...
uniapp 对接腾讯云IM群组成员管理(增删改查)
UniApp 实战:腾讯云IM群组成员管理(增删改查) 一、前言 在社交类App开发中,群组成员管理是核心功能之一。本文将基于UniApp框架,结合腾讯云IM SDK,详细讲解如何实现群组成员的增删改查全流程。 权限校验…...

《Qt C++ 与 OpenCV:解锁视频播放程序设计的奥秘》
引言:探索视频播放程序设计之旅 在当今数字化时代,多媒体应用已渗透到我们生活的方方面面,从日常的视频娱乐到专业的视频监控、视频会议系统,视频播放程序作为多媒体应用的核心组成部分,扮演着至关重要的角色。无论是在个人电脑、移动设备还是智能电视等平台上,用户都期望…...
PHP和Node.js哪个更爽?
先说结论,rust完胜。 php:laravel,swoole,webman,最开始在苏宁的时候写了几年php,当时觉得php真的是世界上最好的语言,因为当初活在舒适圈里,不愿意跳出来,就好比当初活在…...
测试markdown--肇兴
day1: 1、去程:7:04 --11:32高铁 高铁右转上售票大厅2楼,穿过候车厅下一楼,上大巴车 ¥10/人 **2、到达:**12点多到达寨子,买门票,美团/抖音:¥78人 3、中饭&a…...

新能源汽车智慧充电桩管理方案:新能源充电桩散热问题及消防安全监管方案
随着新能源汽车的快速普及,充电桩作为核心配套设施,其安全性与可靠性备受关注。然而,在高温、高负荷运行环境下,充电桩的散热问题与消防安全隐患日益凸显,成为制约行业发展的关键瓶颈。 如何通过智慧化管理手段优化散…...

(一)单例模式
一、前言 单例模式属于六大创建型模式,即在软件设计过程中,主要关注创建对象的结果,并不关心创建对象的过程及细节。创建型设计模式将类对象的实例化过程进行抽象化接口设计,从而隐藏了类对象的实例是如何被创建的,封装了软件系统使用的具体对象类型。 六大创建型模式包括…...

Golang——7、包与接口详解
包与接口详解 1、Golang包详解1.1、Golang中包的定义和介绍1.2、Golang包管理工具go mod1.3、Golang中自定义包1.4、Golang中使用第三包1.5、init函数 2、接口详解2.1、接口的定义2.2、空接口2.3、类型断言2.4、结构体值接收者和指针接收者实现接口的区别2.5、一个结构体实现多…...

【无标题】湖北理元理律师事务所:债务优化中的生活保障与法律平衡之道
文/法律实务观察组 在债务重组领域,专业机构的核心价值不仅在于减轻债务数字,更在于帮助债务人在履行义务的同时维持基本生活尊严。湖北理元理律师事务所的服务实践表明,合法债务优化需同步实现三重平衡: 法律刚性(债…...
前端高频面试题2:浏览器/计算机网络
本专栏相关链接 前端高频面试题1:HTML/CSS 前端高频面试题2:浏览器/计算机网络 前端高频面试题3:JavaScript 1.什么是强缓存、协商缓存? 强缓存: 当浏览器请求资源时,首先检查本地缓存是否命中。如果命…...
2.2.2 ASPICE的需求分析
ASPICE的需求分析是汽车软件开发过程中至关重要的一环,它涉及到对需求进行详细分析、验证和确认,以确保软件产品能够满足客户和用户的需求。在ASPICE中,需求分析的关键步骤包括: 需求细化:将从需求收集阶段获得的高层需…...