共计 16106 个字符,预计需要花费 41 分钟才能阅读完成。
春节期间,吃饱喝足,寻思着年前有应用 flutter3 开发过一款 App 聊天利用,索性应用 flutter3 开发一款桌面端仿微信 exe 聊天我的项目 Flutter_Winchat。
在通过了半个多月的开发,flutter3 桌面聊天我的项目正式开发结束!
在开发的过程中,的确遇到了一些问题,不过好在最初都解决了。
想着这个我的项目还算不错,就和小伙伴们做一些分享。
应用技术
- 编辑器: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
整个我的项目应用到了下面的一些技术,窗口治理采纳的 bitsdojo_window
插件,不过 window_manager
这个窗口治理插件也不错,不过绝对重量级一些。
https://pub-web.flutter-io.cn/packages/bitsdojo_window
https://pub-web.flutter-io.cn/packages/window_manager
我的项目构造
通过 flutter create app_proj
创立一个新我的项目。
应用 flutter run -d windows
来运行到 window 桌面。
通过 system_tray
插件治理 flutter 桌面端任务栏托盘图标。
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();} | |
}); | |
} |
flutter3 路由 / 状态治理
采纳 getx
作为路由和状态治理。将 MaterialApp
替换为GetMaterialApp
组件。
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, | |
); | |
} | |
} |
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<String, Widget> 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<GetPage> patchRoute = routeMap.entries.map((e) => GetPage( | |
name: e.key, // 路由名称 | |
page: () => e.value, // 路由页面 | |
transition: Transition.noTransition, // 跳转路由动画 | |
middlewares: [AuthMiddleware()], // 路由中间件 | |
)).toList(); | |
final List<GetPage> routes = [GetPage(name: '/login', page: () => const Login()), | |
GetPage(name: '/register', page: () => const Register()), | |
...patchRoute, | |
]; |
通过 getx
内置的 middlewares
中间件进行路由跳转拦挡。
// 路由拦挡 | |
class AuthMiddleware extends GetMiddleware { | |
@override | |
RouteSettings? redirect(String? route) {return Utils.isLogin() ? null : const RouteSettings(name: '/login'); | |
} | |
} |
flutter 自定义最小化 / 最大化 / 敞开
采纳 bitsdojo_window
插件进行窗口治理。该插件反对去掉零碎导航条,自定义窗口大小、右上角操作按钮、拖拽窗口等性能。
@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 公共布局模板
整体我的项目借鉴了微信桌面端 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; // 是否显示侧边栏 | |
@override | |
State<Layout> createState() => _LayoutState(); | |
} |
左侧操作栏,无事件操作区域 MoveWindow
还反对拖动窗口性能。
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
组件实现侧边导航性能。该组件反对自定义头部和尾部组件。
@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 仿微信朋友圈
@override | |
Widget build(BuildContext context) { | |
return Layout( | |
showSidebar: false, | |
workbench: CustomScrollView( | |
slivers: [ | |
SliverAppBar(backgroundColor: const Color(0xFF224E7F), | |
foregroundColor: Colors.white, | |
pinned: true, | |
elevation: 0.0, | |
expandedHeight: 200.0, | |
leading: IconButton(icon: const Icon(Icons.arrow_back,), onPressed: () {Navigator.pop(context);}), | |
flexibleSpace: FlexibleSpaceBar( | |
title: Row( | |
children: <Widget>[ClipOval(child: Image.asset('assets/images/avatar/uimg1.jpg',height: 36.0,width: 36.0,fit: BoxFit.fill)), | |
const SizedBox(width: 10.0), | |
const Text('Andy', style: TextStyle(fontSize: 14.0)), | |
], | |
), | |
titlePadding: const EdgeInsets.fromLTRB(55, 10, 10, 10), | |
background: InkWell(child: Image.asset('assets/images/cover.jpg', fit: BoxFit.cover), | |
onTap: () {changePhotoAlbum(context);}, | |
), | |
), | |
actions: <Widget>[IconButton(icon: const Icon(Icons.favorite_border, size: 18,), onPressed: () {}), | |
IconButton(icon: const Icon(Icons.share, size: 18,), onPressed: () {}), | |
IconButton(icon: const Icon(Icons.add_a_photo, size: 18,), onPressed: () {Get.toNamed('/publish');}), | |
const SizedBox(width: 10.0,), | |
], | |
), | |
SliverToBoxAdapter( | |
child: UnconstrainedBox( | |
child: Container(width: MediaQuery.of(context).size.height * 3 / 4, | |
decoration: const BoxDecoration(color: Colors.white,), | |
child: Column(children: uzoneList.map((item) { | |
return Container(padding: const EdgeInsets.all(15.0), | |
decoration: const BoxDecoration(border: Border(bottom: BorderSide(color: Color(0xFFEEEEEE), width: .5)), | |
), | |
child: Row( | |
crossAxisAlignment: CrossAxisAlignment.start, | |
children: <Widget>[Image.asset(item['avatar'],height: 35.0,width: 35.0,fit: BoxFit.cover), | |
const SizedBox(width: 10.0), | |
Expanded( | |
child: Column( | |
crossAxisAlignment: CrossAxisAlignment.start, | |
children: <Widget>[Text(item['author'], style: TextStyle(color: Colors.indigo[400])), | |
const SizedBox(height: 2.0), | |
Text(item['content'], style: const TextStyle(color: Colors.black87, fontSize: 15.0)), | |
const SizedBox(height: 10.0), | |
GroupZone(images: item['images']), | |
const SizedBox(height: 10.0), | |
Row( | |
children: <Widget>[Expanded(child: Text(item['time'], style: const TextStyle(color: Colors.grey, fontSize: 12.0)),), | |
FStyle.iconfont(0xe653, color: Colors.black54, size: 16.0,), | |
], | |
) | |
], | |
), | |
), | |
], | |
), | |
); | |
}).toList(),), | |
), | |
), | |
), | |
], | |
), | |
); | |
} |
flutter3 短视频模板
短视频模板反对点击播放 / 暂停,底部 mini 播放进度条。
// flutter3 视频播放模块 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), | |
), | |
), | |
), | |
], | |
), | |
), |
flutter3 聊天模板
聊天模块表情 / 抉择弹窗采纳了 dialog 展现形式。
// 表情弹窗 | |
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 状态
showDialog 实质上是另一个路由页面,它的性质跟你以后主页面是一样的。在 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); | |
} |
整个我的项目涵盖的 flutter 知识点还蛮多的,限于篇幅,不都一一具体介绍了。
心愿以上的分享能给大家些许的帮忙哈~~
https://segmentfault.com/a/1190000044624387
https://segmentfault.com/a/1190000044519351