大家好,欢迎来到IT知识分享网。
继上一篇基于Flutter3.x桌面端仿微信exe实例一经发表,被赞爆了。
今天再来分享一些实现的技术知识,希望以下分享对大家有些许的帮助~~
技术框架
- 开发工具: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
已经实现了聊天消息、通讯录、收藏、朋友圈、短视频、我的等页面模块。
项目结构目录
// 创建flutter项目模板 flutter create win_proj // 运行到桌面 flutter run -d windows
注意:开发之前需要自行配置好flutter sdk和dart sdk环境。
https://flutter.dev/ https://www.dartcn.com/
整体采用 bitsdojo_window 插件进行窗口管理。支持自定义窗口尺寸及系统操作按钮(最大化/最小化/关闭)。
https://pub-web.flutter-io.cn/packages/bitsdojo_window
flutter桌面端通过 system_tray 插件,生成系统托盘图标。
https://pub-web.flutter-io.cn/packages/system_tray
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}); @override 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自定义右上角操作按钮组
@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; @override void initState() { super.initState(); WidgetsBinding.instance.addObserver(this); } @override void dispose() { WidgetsBinding.instance.removeObserver(this); super.dispose(); } // 监听窗口尺寸变化 @override void didChangeMetrics() { super.didChangeMetrics(); WidgetsBinding.instance.addPostFrameCallback((_) { setState(() { isMaximized = appWindow.isMaximized; }); }); } // ... }
flutter布局模板
项目整体参照微信客户端,分为如下几个大模块。
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; // 是否显示侧边栏 @override State<Layout> createState() => _LayoutState(); }
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, ), ), ], ), ), ], ), );
@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(0xFF), size: 24.0), selectedIconTheme: const IconThemeData(color: Color(0xFF07C160), size: 24.0,), unselectedLabelTextStyle: const TextStyle(color: Color(0xFF),), 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(0xFF), size: 24.0), onPressed:(){showAboutDialog(context);}), PopupMenuButton( icon: const Icon(Icons.menu, color: Color(0xFF), size: 24.0,), offset: const Offset(54.0, 0.0), tooltip: '', color: const Color(0xFF), 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短视频模板
// flutter3短视频模板 Container( 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), ), ), ), ], ), ),
flutter_winchat项目涵盖了很多flutter知识点,限于篇幅不能一一详细分享了。
希望以上的分享内容,对小伙伴们有些帮助。
免责声明:本站所有文章内容,图片,视频等均是来源于用户投稿和互联网及文摘转载整编而成,不代表本站观点,不承担相关法律责任。其著作权各归其原作者或其出版社所有。如发现本站有涉嫌抄袭侵权/违法违规的内容,侵犯到您的权益,请在线联系站长,一经查实,本站将立刻删除。 本文来自网络,若有侵权,请联系删除,如若转载,请注明出处:https://yundeesoft.com/163832.html