基于Flutter3+bitsdojo_window+Getx桌面端仿微信EXE

2024-03-02 11:12

本文主要是介绍基于Flutter3+bitsdojo_window+Getx桌面端仿微信EXE,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

经过接近一个月的爆肝开发,flutter3.x桌面端仿微信聊天项目完结啦!

基于flutter3+dart3+bitsdojo_window+get+media_kit等技术架构全新研发原创的Flutter3客户端聊天实战项目。实现了消息/emoj混合、图片/视频预览、红包、通讯录、收藏/朋友圈及小视频等功能。

在这里插入图片描述

技术栈

  • 编辑器:vscode
  • 窗口管理器:bitsdojo_window: ^0.1.6
  • 系统托盘:system_tray: ^2.0.3
  • 路由/状态管理:get: ^4.6.6
  • 存储服务:get_storage: ^2.1.1
  • 图片预览:photo_view: ^0.14.0
  • 网址预览:url_launcher: ^6.2.4
  • 视频组件:media_kit: ^1.1.10+1
  • 文件选择器:file_picker: ^6.1.1

在这里插入图片描述
在这里插入图片描述
flutter3_winchat 采用 bitsdojo_window 进行窗口管理。支持无边框窗口、自定义窗口尺寸及右上角系统操作栏。

在这里插入图片描述

项目结构目录

通过 flutter create app_project 命令创建一个新项目模板。

在这里插入图片描述
通过 flutter run -d window 命令,运行项目到windows系统。

在这里插入图片描述
在这里插入图片描述
FlutterWinchat项目采用 bitsdojo_window 插件来管理窗口。支持无边框窗口,自定义窗口尺寸,自定义右上角系统操作按钮(最大化/最小化/关闭)。

https://pub-web.flutter-io.cn/packages/bitsdojo_window

通过 system_tray 插件生成任务栏系统托盘图标。

在这里插入图片描述

https://pub-web.flutter-io.cn/packages/system_tray

在这里插入图片描述

flutter入口main.dart

import 'dart:io';
import 'package:flutter/material.dart';
import 'package:bitsdojo_window/bitsdojo_window.dart';
import 'package:get/get.dart';
import 'package:get_storage/get_storage.dart';
import 'package:media_kit/media_kit.dart';
import 'package:system_tray/system_tray.dart';import 'utils/index.dart';// 引入公共样式
import 'styles/index.dart';// 引入公共布局模板
import 'layouts/index.dart';// 引入路由配置
import 'router/index.dart';void main() async {// 初始化get_storage存储类await GetStorage.init();// 初始化media_kit视频套件WidgetsFlutterBinding.ensureInitialized();MediaKit.ensureInitialized();initSystemTray();runApp(const MyApp());// 初始化bitsdojo_window窗口doWhenWindowReady(() {appWindow.size = const Size(850, 620);appWindow.minSize = const Size(700, 500);appWindow.alignment = Alignment.center;appWindow.title = 'Flutter3-WinChat';appWindow.show();});
}class MyApp extends StatelessWidget {const MyApp({super.key});@overrideWidget build(BuildContext context) {return GetMaterialApp(title: 'FLUTTER3 WINCHAT',debugShowCheckedModeBanner: false,theme: ThemeData(primaryColor: FStyle.primaryColor,useMaterial3: true,// 修正windows端字体粗细不一致fontFamily: Platform.isWindows ? 'Microsoft YaHei' : null,),home: const Layout(),// 初始路由initialRoute: Utils.isLogin() ? '/index' :'/login',// 路由页面getPages: routes,onInit: () {},onReady: () {},);}
}// 创建系统托盘图标
Future<void> initSystemTray() async {String trayIco = 'assets/images/tray.ico';SystemTray systemTray = SystemTray();// 初始化系统托盘await systemTray.initSystemTray(title: 'system-tray',iconPath: trayIco,);// 右键菜单final Menu menu = Menu();await menu.buildFrom([MenuItemLabel(label: 'show', onClicked: (menuItem) => appWindow.show()),MenuItemLabel(label: 'hide', onClicked: (menuItem) => appWindow.hide()),MenuItemLabel(label: 'close', onClicked: (menuItem) => appWindow.close()),]);await systemTray.setContextMenu(menu);// 右键事件systemTray.registerSystemTrayEventHandler((eventName) {debugPrint('eventName: $eventName');if (eventName == kSystemTrayEventClick) {Platform.isWindows ? appWindow.show() : systemTray.popUpContextMenu();} else if (eventName == kSystemTrayEventRightClick) {Platform.isWindows ? systemTray.popUpContextMenu() : appWindow.show();}});
}

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

flutter自定义系统最小化/最大化/关闭

在这里插入图片描述
在这里插入图片描述

@override
Widget build(BuildContext context){return Row(children: [Container(child: widget.leading,),Visibility(visible: widget.minimizable,child: MouseRegion(cursor: SystemMouseCursors.click,child: SizedBox(width: 32.0,height: 36.0,child: MinimizeWindowButton(colors: buttonColors, onPressed: handleMinimize,),)),),Visibility(visible: widget.maximizable,child: MouseRegion(cursor: SystemMouseCursors.click,child: SizedBox(width: 32.0,height: 36.0,child: isMaximized ? RestoreWindowButton(colors: buttonColors, onPressed: handleMaxRestore,): MaximizeWindowButton(colors: buttonColors, onPressed: handleMaxRestore,),),),),Visibility(visible: widget.closable,child: MouseRegion(cursor: SystemMouseCursors.click,child: SizedBox(width: 32.0,height: 36.0,child: CloseWindowButton(colors: closeButtonColors, onPressed: handleExit,),),),),Container(child: widget.trailing,),],);
}
// 最小化
void handleMinimize() {appWindow.minimize();
}
// 设置最大化/恢复
void handleMaxRestore() {appWindow.maximizeOrRestore();
}
// 关闭
void handleExit() {showDialog(context: context,builder: (context) {return AlertDialog(content: const Text('是否最小化至托盘,不退出程序?', style: TextStyle(fontSize: 16.0),),backgroundColor: Colors.white,surfaceTintColor: Colors.white,shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(3.0)),elevation: 3.0,actionsPadding: const EdgeInsets.all(15.0),actions: [TextButton(onPressed: () {Get.back();appWindow.close();},child: const Text('退出', style: TextStyle(color: Colors.red),)),TextButton(onPressed: () {Get.back();appWindow.hide();},child: const Text('最小化至托盘', style: TextStyle(color: Colors.deepPurple),)),],);});
}

flutter通过内置的 WidgetsBindingObserver 类来监测窗口变化。

class _WinbtnState extends State<Winbtn> with WidgetsBindingObserver {// 是否最大化bool isMaximized = false;@overridevoid initState() {super.initState();WidgetsBinding.instance.addObserver(this);}@overridevoid dispose() {WidgetsBinding.instance.removeObserver(this);super.dispose();}// 监听窗口尺寸变化@overridevoid didChangeMetrics() {super.didChangeMetrics();WidgetsBinding.instance.addPostFrameCallback((_) {setState(() {isMaximized = appWindow.isMaximized;});});}// ...
}

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

在这里插入图片描述
在这里插入图片描述

flutter3-winchat公共布局模板

在这里插入图片描述
项目整体参考了微信桌面端UI界面。

在这里插入图片描述

class Layout extends StatefulWidget {const Layout({super.key,this.activitybar = const Activitybar(),this.sidebar,this.workbench,this.showSidebar = true,});final Widget? activitybar; // 左侧操作栏final Widget? sidebar; // 侧边栏final Widget? workbench; // 右侧工作面板final bool showSidebar; // 是否显示侧边栏@overrideState<Layout> createState() => _LayoutState();
}

布局模板分为左中右大模块。采用Flex组件进行布局样式。

return Scaffold(backgroundColor: Colors.grey[100],body: Flex(direction: Axis.horizontal,children: [// 左侧操作栏MoveWindow(child: widget.activitybar,onDoubleTap: () => {},),// 侧边栏Visibility(visible: widget.showSidebar,child: SizedBox(width: 270.0,child: Container(decoration: const BoxDecoration(gradient: LinearGradient(begin: Alignment.topLeft,end: Alignment.bottomRight,colors: [Color(0xFFEEEBE7), Color(0xFFEEEEEE)]),),child: widget.sidebar,),),),// 主体容器Expanded(child: Column(children: [WindowTitleBarBox(child: Row(children: [Expanded(child: MoveWindow(),),// 右上角操作按钮组Winbtn(leading: Row(children: [IconButton(onPressed: () {}, icon: const Icon(Icons.auto_fix_high), iconSize: 14.0,),IconButton(onPressed: () {setState(() {winTopMost = !winTopMost;});},tooltip: winTopMost ? '取消置顶' : '置顶',icon: const Icon(Icons.push_pin_outlined),iconSize: 14.0,highlightColor: Colors.transparent, // 点击水波纹颜色isSelected: winTopMost ? true : false, // 是否选中style: ButtonStyle(visualDensity: VisualDensity.compact,backgroundColor: MaterialStateProperty.all(winTopMost ? Colors.grey[300] : Colors.transparent),shape: MaterialStatePropertyAll(RoundedRectangleBorder(borderRadius: BorderRadius.circular(0.0))),),),],),),],),),// 右侧工作面板Expanded(child: Container(child: widget.workbench,),),],),),],),
);

在这里插入图片描述
使用 NavigationRail 组件实现Tab切换功能。该组件支持自定义头部和尾部组件。

// 索引
int tabCur = 0;
Map tabRoute = {0: {'path': '/index', 'pathMatch': ['/index', '/chat']}, // 聊天1: {'path': '/contact', 'pathMatch': ['/contact', '/addfriends', '/newfriends', '/uinfo']}, // 通讯录2: {'path': '/favor', 'pathMatch': ['/favor']}, // 收藏3: {'path': '/fzone', 'pathMatch': ['/fzone', '/publish']}, // 朋友圈4: {'path': '/fvideo', 'pathMatch': ['/fvideo']}, // 短视频
};
// tabs选项
List tabNavs = [NavigationRailDestination(icon: Stack(alignment: const Alignment(3.0, -1.5),children: [const Icon(Icons.wechat),FStyle.badge(11)],),label: const Text('聊天'),),const NavigationRailDestination(icon: Icon(Icons.group_outlined),label: Text('通讯录'),),const NavigationRailDestination(icon: Icon(Icons.folder_zip_outlined),label: Text('收藏'),),NavigationRailDestination(icon: Stack(alignment: const Alignment(3.0, -1.5),children: [const Icon(Icons.camera),FStyle.badge(3)],),label: const Text('朋友圈'),),NavigationRailDestination(icon: Stack(alignment: const Alignment(1.5, -1.0),children: [const Icon(Icons.play_circle),FStyle.badge(0, isdot: true)],),label: const Text('短视频'))
];
@override
Widget build(BuildContext context) {return Container(width: 54.0,decoration: const BoxDecoration(color: Color(0xFF2E2E2E),),child: NavigationRail(backgroundColor: Colors.transparent,labelType: NavigationRailLabelType.none, // all 显示图标+标签 selected 只显示激活图标+标签 none 不显示标签indicatorColor: Colors.transparent, // 去掉选中椭圆背景indicatorShape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(0.0),),unselectedIconTheme: const IconThemeData(color: Color(0xFF979797), size: 24.0),selectedIconTheme: const IconThemeData(color: Color(0xFF07C160), size: 24.0,),unselectedLabelTextStyle: const TextStyle(color: Color(0xFF979797),),selectedLabelTextStyle: const TextStyle(color: Color(0xFF07C160),),// 头部(图像)leading: GestureDetector(onPanStart: (details) => {},child: Container(margin: const EdgeInsets.only(top: 30.0, bottom: 10.0),child: InkWell(child: Image.asset('assets/images/avatar/uimg1.jpg', height: 36.0, width: 36.0,),onTapDown: (TapDownDetails details) {cardDX = details.globalPosition.dx;cardDY = details.globalPosition.dy;},onTap: () {showCardDialog(context);},),),),// 尾部(链接)trailing: Expanded(child: Container(margin: const EdgeInsets.only(bottom: 10.0),child: GestureDetector(onPanStart: (details) => {},child: Column(mainAxisAlignment: MainAxisAlignment.end,children: [IconButton(icon: Icon(Icons.info_outline, color: Color(0xFF979797), size: 24.0), onPressed:(){showAboutDialog(context);}),PopupMenuButton(icon: const Icon(Icons.menu, color: Color(0xFF979797), size: 24.0,),offset: const Offset(54.0, 0.0),tooltip: '',color: const Color(0xFF353535),surfaceTintColor: Colors.transparent,shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(0.0)),padding: EdgeInsets.zero,itemBuilder: (BuildContext context) {return <PopupMenuItem>[popupMenuItem('我的私密空间', 0),popupMenuItem('锁定', 1),popupMenuItem('意见反馈', 2),popupMenuItem('设置', 3),];},onSelected: (value) {switch(value) {case 0:Get.toNamed('/my');break;case 3:Get.toNamed('/setting');break;}},),],),),),),selectedIndex: tabCur,destinations: [...tabNavs],onDestinationSelected: (index) {setState(() {tabCur = index;if(tabRoute[index] != null && tabRoute[index]?['path'] != null) {Get.toNamed(tabRoute[index]['path']);}});},),);
}

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

flutter3小视频功能

flutter-winchat项目采用 media_kit 进行视频播放操作。

在这里插入图片描述

// flutter3短视频模板  Q:282310962Container(width: MediaQuery.of(context).size.height * 9 / 16,decoration: const BoxDecoration(color: Colors.black,),child: Stack(children: [// Swiper垂直滚动区域PageView(// 自定义滚动行为(支持桌面端滑动、去掉滚动条槽)scrollBehavior: SwiperScrollBehavior().copyWith(scrollbars: false),scrollDirection: Axis.vertical,controller: pageController,onPageChanged: (index) {// 暂停(垂直滑动)controller.player.pause();},children: [Stack(children: [// 视频区域Positioned(top: 0,left: 0,right: 0,bottom: 0,child: GestureDetector(child: Stack(children: [// 短视频插件Video(controller: controller,fit: BoxFit.cover,// 无控制条controls: NoVideoControls,),// 播放/暂停按钮Center(child: IconButton(onPressed: () {controller.player.playOrPause();},icon: StreamBuilder(stream: controller.player.stream.playing,builder: (context, playing) {return Visibility(visible: playing.data == false,child: Icon(playing.data == true ? Icons.pause : Icons.play_arrow_rounded,color: Colors.white70,size: 50,),);},),),),],),onTap: () {controller.player.playOrPause();},),),// 右侧操作栏Positioned(bottom: 70.0,right: 10.0,child: Column(children: [// ...],),),// 底部信息区域Positioned(bottom: 30.0,left: 15.0,right: 80.0,child: Column(crossAxisAlignment: CrossAxisAlignment.start,children: [// ...],),),// 播放mini进度条Positioned(bottom: 15.0,left: 15.0,right: 15.0,child: Container(// ...),),],),Container(color: Colors.black,child: const Center(child: Text('1', style: TextStyle(color: Colors.white, fontSize: 60),),)),Container(color: Colors.black,child: const Center(child: Text('2', style: TextStyle(color: Colors.white, fontSize: 60),),)),Container(color: Colors.black,child: const Center(child: Text('3', style: TextStyle(color: Colors.white, fontSize: 60),),)),],),// 固定tab菜单Align(alignment: Alignment.topCenter,child: DefaultTabController(length: 3,child: TabBar(tabs: const [Tab(text: '推荐'),Tab(text: '关注'),Tab(text: '同城'),],tabAlignment: TabAlignment.center,overlayColor: MaterialStateProperty.all(Colors.transparent),unselectedLabelColor: Colors.white70,labelColor: const Color(0xff0091ea),indicatorColor: const Color(0xff0091ea),indicatorSize: TabBarIndicatorSize.label,dividerHeight: 0,indicatorPadding: const EdgeInsets.all(5),),),),],),
),

flutter3聊天功能

在这里插入图片描述
表情弹窗使用了showDialog实现功能。

// 表情弹窗
void showEmojDialog() {updateAnchorOffset(anchorEmojKey);showDialog(context: context,barrierColor: Colors.transparent, // 遮罩透明builder: (context) {// 解决flutter通过 setState 方法无法更新当前的dialog状态// dialog是一个路由页面,本质跟你当前主页面是一样的。在Flutter中它是一个新的路由。所以,你使用当前页面的 setState 方法当然是没法更新dialog中内容。// 如何更新dialog中的内容呢?答案是使用StatefulBuilder。return StatefulBuilder(builder: (BuildContext context, StateSetter setState) {setEmojState = setState;return Stack(children: [Positioned(top: anchorDy - (anchorDy - 100) - 15,left: anchorDx - 180,width: 360.0,height: anchorDy - 100,child: Material(shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(12.0)),color: Colors.white,elevation: 1.0,clipBehavior: Clip.hardEdge,child: Column(children: renderEmojWidget(),),),)],);},);},);
}

注意:通过 setState 方法无法更新当前的dialog状态!

dialog本质上是另一个路由页面,它的性质跟你当前主页面是一样的。在Flutter中它是一个新的路由。所以,你使用当前页面的 setState 方法当然是没法更新dialog中内容。如何更新dialog中的内容呢?答案是使用StatefulBuilder

late StateSetter setEmojState;

// 表情Tab切换
void handleEmojTab(index) {var emols = emoJson;for(var i = 0, len = emols.length; i < len; i++) {emols[i]['selected'] = false;}emols[index]['selected'] = true;setEmojState(() {emoJson = emols;});emojController.jumpTo(0);
}

综上就是flutter3+dart3开发桌面端聊天项目的一些知识分享~ 希望对大家有所帮助!

最后附上两个最新项目实例

  • 基于Electron27+React18桌面端macos管理系统
    https://blog.csdn.net/yanxinyun1990/article/details/134567716

  • vue3+uniapp跨端h5+小程序+App直播商城
    https://blog.csdn.net/yanxinyun1990/article/details/135329724

在这里插入图片描述

这篇关于基于Flutter3+bitsdojo_window+Getx桌面端仿微信EXE的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!


原文地址:
本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.chinasem.cn/article/765924

相关文章

将Java程序打包成EXE文件的实现方式

《将Java程序打包成EXE文件的实现方式》:本文主要介绍将Java程序打包成EXE文件的实现方式,具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地方,望不吝赐教... 目录如何将Java程序编程打包成EXE文件1.准备Java程序2.生成JAR包3.选择并安装打包工具4.配置Launch4

Python结合PyWebView库打造跨平台桌面应用

《Python结合PyWebView库打造跨平台桌面应用》随着Web技术的发展,将HTML/CSS/JavaScript与Python结合构建桌面应用成为可能,本文将系统讲解如何使用PyWebView... 目录一、技术原理与优势分析1.1 架构原理1.2 核心优势二、开发环境搭建2.1 安装依赖2.2 验

SpringBoot实现微信小程序支付功能

《SpringBoot实现微信小程序支付功能》小程序支付功能已成为众多应用的核心需求之一,本文主要介绍了SpringBoot实现微信小程序支付功能,文中通过示例代码介绍的非常详细,对大家的学习或者工作... 目录一、引言二、准备工作(一)微信支付商户平台配置(二)Spring Boot项目搭建(三)配置文件

微信公众号脚本-获取热搜自动新建草稿并发布文章

《微信公众号脚本-获取热搜自动新建草稿并发布文章》本来想写一个自动化发布微信公众号的小绿书的脚本,但是微信公众号官网没有小绿书的接口,那就写一个获取热搜微信普通文章的脚本吧,:本文主要介绍微信公众... 目录介绍思路前期准备环境要求获取接口token获取热搜获取热搜数据下载热搜图片给图片加上标题文字上传图片

如何使用Python实现一个简单的window任务管理器

《如何使用Python实现一个简单的window任务管理器》这篇文章主要为大家详细介绍了如何使用Python实现一个简单的window任务管理器,文中的示例代码讲解详细,感兴趣的小伙伴可以跟随小编一起... 任务管理器效果图完整代码import tkinter as tkfrom tkinter i

如何用java对接微信小程序下单后的发货接口

《如何用java对接微信小程序下单后的发货接口》:本文主要介绍在微信小程序后台实现发货通知的步骤,包括获取Access_token、使用RestTemplate调用发货接口、处理AccessTok... 目录配置参数 调用代码获取Access_token调用发货的接口类注意点总结配置参数 首先需要获取Ac

Window Server创建2台服务器的故障转移群集的图文教程

《WindowServer创建2台服务器的故障转移群集的图文教程》本文主要介绍了在WindowsServer系统上创建一个包含两台成员服务器的故障转移群集,文中通过图文示例介绍的非常详细,对大家的... 目录一、 准备条件二、在ServerB安装故障转移群集三、在ServerC安装故障转移群集,操作与Ser

Window Server2016加入AD域的方法步骤

《WindowServer2016加入AD域的方法步骤》:本文主要介绍WindowServer2016加入AD域的方法步骤,包括配置DNS、检测ping通、更改计算机域、输入账号密码、重启服务... 目录一、 准备条件二、配置ServerB加入ServerA的AD域(test.ly)三、查看加入AD域后的变

Window Server2016 AD域的创建的方法步骤

《WindowServer2016AD域的创建的方法步骤》本文主要介绍了WindowServer2016AD域的创建的方法步骤,文中通过图文介绍的非常详细,对大家的学习或者工作具有一定的参考学习价... 目录一、准备条件二、在ServerA服务器中常见AD域管理器:三、创建AD域,域地址为“test.ly”

javafx 如何将项目打包为 Windows 的可执行文件exe

《javafx如何将项目打包为Windows的可执行文件exe》文章介绍了三种将JavaFX项目打包为.exe文件的方法:方法1使用jpackage(适用于JDK14及以上版本),方法2使用La... 目录方法 1:使用 jpackage(适用于 JDK 14 及更高版本)方法 2:使用 Launch4j(