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);

  @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给到窗口,这样创建出来的窗口就是直接在副屏的位置,同时是全屏的状态。

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

倚栏静望

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值