flutter3

您所在的位置:网站首页 flutter扫码插件开发 flutter3

flutter3

2024-07-14 18:23| 来源: 网络整理| 查看: 265

年前有给大家分享一款flutter3.x+dart3手机端聊天App实例。春节期间就又捣鼓了flutter3桌面端开发实践项目。

https://cloud.tencent.com/developer/article/2388052

flutter3_winchat 一款基于flutter3+getx+bitsdojo_window架构的客户端聊天实例项目。

该项目周期不过个半月,开发中遇到了很多的技术问题,不过都最后解决了。

通过这个项目旨在探究Flutter在桌面端技术实践,通过下面的一些技术分享让更多的开发者能参与推动flutter客户端的发展。

使用技术编辑器:vscode框架技术:flutter3.16+dart3.2窗口管理: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

代码语言:typescript复制// 创建flutter新项目模板 flutter create flutter_winchat // 运行flutter项目到window桌面 flutter run -d windows

窗口管理使用的是bitsdojo_window插件,不过window_manager这个窗口管理插件也不错,功能更加的丰富重量级一些。

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

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

项目结构

flutter构建的项目结构目录非常清晰,一目了然。

Flutter布局模板

项目整体采用左中右三列布局,UI结构和微信客户端类似。

代码语言:typescript复制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 createState() => _LayoutState(); }代码语言:typescript复制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, ), ), ], ), ), ], ), );

使用flutter内置的 NavigationRail 导航组件实现Tabs功能。该组件支持自定义头部和尾部组件。

代码语言:typescript复制@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('我的私密空间', 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路由管理配置

整个项目采用Getx作为路由及状态管理插件。

代码语言:typescript复制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, ), home: const Layout(), // 初始路由 initialRoute: Utils.isLogin() ? '/index' :'/login', // 路由页面 getPages: routes, ); } }

新建一个router路由管理目录。

代码语言:typescript复制import 'package:flutter/material.dart'; import 'package:get/get.dart'; // 引入工具类 import '../utils/index.dart'; /* 引入路由页面 */ import '../views/auth/login.dart'; import '../views/auth/register.dart'; // 首页 import '../views/index/index.dart'; // 通讯录 import '../views/contact/index.dart'; import '../views/contact/addfriends.dart'; import '../views/contact/newfriends.dart'; import '../views/contact/uinfo.dart'; // 收藏 import '../views/favor/index.dart'; // 我的 import '../views/my/index.dart'; import '../views/my/setting.dart'; import '../views/my/recharge.dart'; import '../views/my/wallet.dart'; // 朋友圈 import '../views/fzone/index.dart'; import '../views/fzone/publish.dart'; // 短视频 import '../views/fvideo/index.dart'; // 聊天 import '../views/chat/group-chat/chat.dart'; // 路由地址集合 final Map routeMap = { '/index': const Index(), '/contact': const Contact(), '/addfriends': const AddFriends(), '/newfriends': const NewFriends(), '/uinfo': const Uinfo(), '/favor': const Favor(), '/my': const My(), '/setting': const Setting(), '/recharge': const Recharge(), '/wallet': const Wallet(), '/fzone': const Fzone(), '/publish': const PublishFzone(), '/fvideo': const Fvideo(), '/chat': const Chat(), }; final List patchRoute = routeMap.entries.map((e) => GetPage( name: e.key, // 路由名称 page: () => e.value, // 路由页面 transition: Transition.noTransition, // 跳转路由动画 middlewares: [AuthMiddleware()], // 路由中间件 )).toList(); final List routes = [ GetPage(name: '/login', page: () => const Login()), GetPage(name: '/register', page: () => const Register()), ...patchRoute, ]; // 路由拦截 class AuthMiddleware extends GetMiddleware { @override RouteSettings? redirect(String? route) { return Utils.isLogin() ? null : const RouteSettings(name: '/login'); } }

Getx提供了middlewares中间件进行路由拦截。

代码语言:typescript复制// 路由拦截 class AuthMiddleware extends GetMiddleware { @override RouteSettings? redirect(String? route) { return Utils.isLogin() ? null : const RouteSettings(name: '/login'); } }Flutter3自定义窗口操作按钮

如下图:通过bitsdojo_window组件实现自定义窗口最小化/最大化/关闭按钮功能。

该插件引入会自动去掉系统导航条,支持自定义窗口尺寸,拖拽及最大化/最小化/关闭功能。

代码语言:typescript复制@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来监测窗口变化。

代码语言:typescript复制class _WinbtnState extends State 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小视频模块

项目中加入了视频播放模板。支持上下滑动,点击视频播放/暂停。

代码语言:typescript复制// flutter小视频播放模块 Q:282310962 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), ), ), ), ], ), ),

Ok,限于篇幅,基于flutter3.x开发桌面端聊天项目的知识分享就到这里。希望对大家有所帮助~~



【本文地址】


今日新闻


推荐新闻


CopyRight 2018-2019 办公设备维修网 版权所有 豫ICP备15022753号-3