基于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});Widget 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自定义系统最小化/最大化/关闭

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


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;void initState() {super.initState();WidgetsBinding.instance.addObserver(this);}void dispose() {WidgetsBinding.instance.removeObserver(this);super.dispose();}// 监听窗口尺寸变化void 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; // 是否显示侧边栏State<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('短视频'))
];

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

相关文章

W外链微信推广短连接怎么做?

制作微信推广链接的难点分析 一、内容创作难度 制作微信推广链接时,首先需要创作有吸引力的内容。这不仅要求内容本身有趣、有价值,还要能够激起人们的分享欲望。对于许多企业和个人来说,尤其是那些缺乏创意和写作能力的人来说,这是制作微信推广链接的一大难点。 二、精准定位难度 微信用户群体庞大,不同用户的需求和兴趣各异。因此,制作推广链接时需要精准定位目标受众,以便更有效地吸引他们点击并分享链接

uniapp设置微信小程序的交互反馈

链接:uni.showToast(OBJECT) | uni-app官网 (dcloud.net.cn) 设置操作成功的弹窗: title是我们弹窗提示的文字 showToast是我们在加载的时候进入就会弹出的提示。 2.设置失败的提示窗口和标签 icon:'error'是设置我们失败的logo 设置的文字上限是7个文字,如果需要设置的提示文字过长就需要设置icon并给

js window.addEventListener 是什么?

window.addEventListener 是 JavaScript 中的一个方法,用于向指定对象(在这个情况下是 window 对象,代表浏览器窗口)添加事件监听器,以便在该对象上发生特定事件时执行相应的函数(称为事件处理函数或事件监听器)。 这个方法接受三个参数: 事件类型(type):一个字符串,表示要监听的事件类型。例如,"click" 表示鼠标点击事件,"load" 表示页面加

Qt中window frame的影响

window frame 在创建图形化界面的时候,会创建窗口主体,上面会多出一条,周围多次一圈细边,这就叫window frame窗口框架,这是操作系统自带的。 这个对geometry的一些属性有一定影响,主要体现在Qt坐标系体系: 窗口当中包含一个按钮,这个按钮的坐标系是以父元素为参考,那么这个参考是widget本体作为参考,还是window frame作为参考,这两种参考体系都存在

Python知识点:如何使用Python开发桌面应用(Tkinter、PyQt)

Python 提供了多个库来开发桌面应用程序,其中最常见的两个是 Tkinter 和 PyQt。这两者各有优点,选择取决于你的需求。以下我会介绍如何使用 Tkinter 和 PyQt 开发简单的桌面应用程序。 1. 使用 Tkinter 开发桌面应用 Tkinter 是 Python 的标准库,它非常轻量级且跨平台。它适合开发简单的桌面应用,入门较容易。 安装 Tkinter Tkinte

Caused by: android.view.WindowManager$BadTokenException: Unable to add window -- token android.os.B

一个bug日志 FATAL EXCEPTION: main03-25 14:24:07.724: E/AndroidRuntime(4135): java.lang.RuntimeException: Unable to start activity ComponentInfo{com.syyx.jingubang.ky/com.anguotech.android.activity.Init

基于微信小程序与嵌入式系统的智能小车开发(详细流程)

一、项目概述 本项目旨在开发一款智能小车,结合微信小程序与嵌入式系统,提供实时图像处理与控制功能。用户可以通过微信小程序远程操控小车,并实时接收摄像头采集的图像。该项目解决了传统遥控小车在图像反馈和控制延迟方面的问题,提升了小车的智能化水平,适用于教育、科研和娱乐等多个领域。 二、系统架构 1. 系统架构设计 本项目的系统架构主要分为以下几个部分: 微信小程序:负责用户界面、控制指令的

最初的window

不知你是否也是一个常年在MFC下编程的程序员,有的时候是否忘记了在MFC之前是如何写画窗口的了呢,或者你从来都只是机械的在MFC下面写代码,已经麻木了。其实有一个很简单的方法,或许能够帮你更清楚的了解WINDOW是怎么产生的。 随便用什么版本的VS,在创建win32工程的时候,直接创建WINDOW类型的就OK了。然后,来研究下产生的源代码吧。 // Global Variables:H

微信小程序uniappvue3版本-控制tabbar某一个的显示与隐藏

1. 首先在pages.json中配置tabbar信息 2. 在代码根目录下添加 tabBar 代码文件 直接把微信小程序文档里面的四个文件复制到自己项目中就可以了   3. 根据自己的需求更改index.js文件 首先我这里需要判断什么时候隐藏某一个元素,需要引入接口 然后在切换tabbar时,改变tabbar当前点击的元素 import getList from '../

微信小程序(一)数据流与数据绑定

一、单向数据流和双向数据流 1、单项数据流:指的是我们先把模板写好,然后把模板和数据(数据可能来自后台)整合到一起形成HTML代码,然后把这段HTML代码插入到文档流里面 优点:数据跟踪方便,流向单一,追寻问题比较方便【主要体现:微信小程序】。 缺点:就是写起来不太方便,如果修改UI界面数据需要维护对应的model对象 2、双向数据流:值和UI是双向绑定的,大家都知道,只要UI里面的值发生