目前越来越多的人用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); @override State<_ExampleMainWindow> createState() => _ExampleMainWindowState();}class _ExampleMainWindowState extends State<_ExampleMainWindow> { @override void initState() { // TODO: implement initState super.initState(); } @override Widget build(BuildContext context) { return const MaterialApp( home: App(), ); }}class App extends StatefulWidget{ const App({Key? key}) : super(key: key); @override State<StatefulWidget> createState() { return AppState(); }}class AppState extends State<App>{ List<Screen> screenList=[]; @override void initState() { // TODO: implement initState super.initState(); initDevice(); } void initDevice()async{ screenList=await getScreenList(); screenList.forEach((element) { print(element.frame); }); } @override Widget 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; @override Widget 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; @override State<EventWidget> createState() => _EventWidgetState();}class MessageItem { const MessageItem({this.content, required this.from, required this.method}); final int from; final dynamic content; final String method; @override String toString() { return '$method($from): $content'; } @override int get hashCode => Object.hash(from, content, method); @override bool 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(); @override void initState() { super.initState(); DesktopMultiWindow.setMethodHandler(_handleMethodCallback); } @override dispose() { 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, ), ); }); } @override Widget 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; @override Widget 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给到窗口,这样创建出来的窗口就是直接在副屏的位置,同时是全屏的状态。
来源地址:https://blog.csdn.net/oZhuiMeng123/article/details/128997529