flutter3+dart3聊天室|Flutter3跨平台仿微信App语音聊天/朋友圈
全新研发flutter3+dart3+photo_view跨多端仿微信App界面聊天Flutter3-Chat。
flutter3-chat基于最新跨全平台技术flutter3+dart3+material-design+shared_preferences+easy_refresh构建的仿微信APP界面聊天实例项目。实现发送图文表情消息/gif大图、长按仿微信语音操作面板、图片预览、红包及朋友圈等功能。
技术架构
- 编辑器:Vscode
- 框架技术:Flutter3.16.5+Dart3.2.3
- UI组件库:material-design3
- 弹窗组件:showDialog/SimpleDialog/showModalBottomSheet/AlertDialog
- 图片预览:photo_view^0.14.0
- 本地缓存:shared_preferences^2.2.2
- 下拉刷新:easy_refresh^3.3.4
- toast提示:toast^0.3.0
- 网址预览组件:url_launcher^6.2.4
Flutter3.x开发跨平台App项目,性能有了大幅度提升,官方支持编译到android/ios/macos/windows/linux/web等多平台,未来可期!
项目构建目录
通过 flutter create app_project 命令即可快速创建一个跨平台初始化项目。
通过命令创建项目后,项目结构就如上图所示。
需要注意的是,flutter项目是基于dart语音开发,需要首先配置Dart SDK和Flutter SDK开发环境,大家可以去官网查看详细配置文档。
另外使用VScode编辑器开发项目,可自行安装Flutter / Dart扩展插件。
由于flutter3支持编译到windows,大家可以开发初期在windows上面调试,后期release apk到手机上。
通过如下命令即可运行到windows平台
flutter run -d windows
运行后默认窗口大小为1280x720,可以修改windows/runner/main.cpp文件里面的窗口尺寸。
同样,可以通过 flutter run -d chrome 命令运行到web上预览。
假如在没有真机的情况下,我们可以选择模拟器调试。目前市面上有很多类型模拟器,他们使用adb连接时都会有不同的默认端口,下面列出了一些常用的模拟器及端口号。通过adb connect连接上指定模拟器之后,执行flutter run命令即可运行项目到模拟器上面。
flutter3实现圆角文本框及渐变按钮
- Container(
- height: 40.0,
- margin: const EdgeInsets.symmetric(vertical: 5.0, horizontal: 30.0),
- decoration: BoxDecoration(
- color: Colors.white,
- border: Border.all(color: const Color(0xffdddddd)),
- borderRadius: BorderRadius.circular(15.0),
- ),
- child: Row(
- children: [
- Expanded(
- child: TextField(
- keyboardType: TextInputType.phone,
- controller: fieldController,
- decoration: InputDecoration(
- hintText: '输入手机号',
- suffixIcon: Visibility(
- visible: authObj['tel'].isNotEmpty,
- child: InkWell(
- hoverColor: Colors.transparent,
- highlightColor: Colors.transparent,
- splashColor: Colors.transparent,
- onTap: handleClear,
- child: const Icon(Icons.clear, size: 16.0,),
- )
- ),
- contentPadding: const EdgeInsets.symmetric(vertical: 0, horizontal: 12.0),
- border: const OutlineInputBorder(borderSide: BorderSide.none),
- ),
- onChanged: (value) {
- setState(() {
- authObj['tel'] = value;
- });
- },
- ),
- )
- ],
- ),
- ),
按钮渐变则是通过Container组件的decotaion里面的gradient属性设置渐变效果。
- Container(
- margin: const EdgeInsets.symmetric(vertical: 10.0, horizontal: 30.0),
- decoration: BoxDecoration(
- borderRadius: BorderRadius.circular(15.0),
- // 自定义按钮渐变色
- gradient: const LinearGradient(
- begin: Alignment.topLeft,
- end: Alignment.bottomRight,
- colors: [
- Color(0xFF0091EA), Color(0xFF07C160)
- ],
- )
- ),
- child: SizedBox(
- width: double.infinity,
- height: 45.0,
- child: FilledButton(
- style: ButtonStyle(
- backgroundColor: MaterialStateProperty.all(Colors.transparent),
- shadowColor: MaterialStateProperty.all(Colors.transparent),
- shape: MaterialStatePropertyAll(
- RoundedRectangleBorder(borderRadius: BorderRadius.circular(15.0))
- )
- ),
- onPressed: handleSubmit,
- child: const Text('登录', style: TextStyle(fontSize: 18.0),),
- ),
- )
- ),
flutter实现60s倒计时发送验证码功能。
- Timer? timer;
- String vcodeText = '获取验证码';
- bool disabled = false;
- int time = 60;
- // 60s倒计时
- void handleVcode() {
- if(authObj['tel'] == '') {
- snackbar('手机号不能为空');
- }else if(!Utils.checkTel(authObj['tel'])) {
- snackbar('手机号格式不正确');
- }else {
- setState(() {
- disabled = true;
- });
- startTimer();
- }
- }
- startTimer() {
- timer = Timer.periodic(const Duration(seconds: 1), (timer) {
- setState(() {
- if(time > 0) {
- vcodeText = '获取验证码(${time--})';
- }else {
- vcodeText = '获取验证码';
- time = 60;
- disabled = false;
- timer.cancel();
- }
- });
- });
- snackbar('短信验证码已发送,请注意查收', color: Colors.green);
- }
Flutter3沉浸式渐变状态导航栏
要实现如上图渐变AppBar也非常简单,只需要配置AppBar提供的可伸缩灵活区域属性 flexibleSpace 配合gradient即可快速实现渐变导航栏。
- AppBar(
- title: Text('Flutter3-Chat'),
- flexibleSpace: Container(
- decoration: const BoxDecoration(
- gradient: LinearGradient(
- begin: Alignment.topLeft,
- end: Alignment.bottomRight,
- colors: [
- Color(0xFF0091EA), Color(0xFF07C160)
- ],
- )
- ),
- )
- ),
Flutter3字体图标/自定义badge
flutter内置了丰富的字体图标,通过图标组件 Icon(Icons.add) 引入即可使用。
https://api.flutter-io.cn/flutter/material/Icons-class.html
另外还支持通过自定义IconData方式自定义图标,如使用阿里iconfont图表库图标。
Icon(IconData(0xe666, fontFamily: 'iconfont'), size: 18.0)
把下载的字体文件放到assets目录,
在pubspec.yaml中引入字体文件。
- class FStyle {
- // 自定义iconfont图标
- static iconfont(int codePoint, {double size = 16.0, Color? color}) {
- return Icon(
- IconData(codePoint, fontFamily: 'iconfont', matchTextDirection: true),
- size: size,
- color: color,
- );
- }
- // 自定义Badge红点
- static badge(int count, {
- Color color = Colors.redAccent,
- bool isdot = false,
- double height = 18.0,
- double width = 18.0
- }) {
- final num = count > 99 ? '99+' : count;
- return Container(
- alignment: Alignment.center,
- height: isdot ? height / 2 : height,
- width: isdot ? width / 2 : width,
- decoration: BoxDecoration(color: color, borderRadius: BorderRadius.circular(100.00)),
- child: isdot ? null : Text('$num', style: const TextStyle(color: Colors.white, fontSize: 12.0)),
- );
- }
- }
- FStyle.badge(23)
- FStyle.badge(2, color: Colors.pink, height: 10.0, width: 10.0)
- FStyle.badge(0, isdot: true)
Flutter仿微信PopupMenu下拉菜单/下拉刷新
通过flutter提供的PopupMenuButton组件实现下拉菜单功能。
- PopupMenuButton(
- icon: FStyle.iconfont(0xe62d, size: 17.0),
- offset: const Offset(0, 50.0),
- tooltip: '',
- color: const Color(0xFF353535),
- itemBuilder: (BuildContext context) {
- return <PopupMenuItem>[
- popupMenuItem(0xe666, '发起群聊', 0),
- popupMenuItem(0xe75c, '添加朋友', 1),
- popupMenuItem(0xe603, '扫一扫', 2),
- popupMenuItem(0xe6ab, '收付款', 3),
- ];
- },
- onSelected: (value) {
- switch(value) {
- case 0:
- print('发起群聊');
- break;
- case 1:
- Navigator.pushNamed(context, '/addfriends');
- break;
- case 2:
- print('扫一扫');
- break;
- case 3:
- print('收付款');
- break;
- }
- },
- )
- // 下拉菜单项
- static popupMenuItem(int codePoint, String title, value) {
- return PopupMenuItem(
- value: value,
- child: Row(
- mainAxisAlignment: MainAxisAlignment.start,
- children: [
- const SizedBox(width: 10.0,),
- FStyle.iconfont(codePoint, size: 21.0, color: Colors.white),
- const SizedBox(width: 10.0,),
- Text(title, style: const TextStyle(fontSize: 16.0, color: Colors.white),),
- ],
- ),
- );
- }
如上图:下拉刷新、上拉加载更多是通过 easy_refresh 组件实现功能。
- EasyRefresh(
- // 下拉加载提示
- header: const ClassicHeader(
- // showMessage: false,
- ),
- // 加载更多提示
- footer: ClassicFooter(),
- // 下拉刷新逻辑
- onRefresh: () async {
- // ...下拉逻辑
- await Future.delayed(const Duration(seconds: 2));
- },
- // 上拉加载逻辑
- onLoad: () async {
- // ...
- },
- child: ListView.builder(
- itemCount: chatList.length,
- itemBuilder: (context, index) {
- return Ink(
- // ...
- );
- },
- ),
- )
如上图:弹窗功能均是自定义AlertDialog实现效果。通过无限制容器UnconstrainedBox配合SizedBox组件实现自定义窗口大小。
- // 关于弹窗
- void aboutAlertDialog(BuildContext context) {
- showDialog(
- context: context,
- builder: (context) {
- return UnconstrainedBox(
- constrainedAxis: Axis.vertical,
- child: SizedBox(
- width: 320.0,
- child: AlertDialog(
- contentPadding: const EdgeInsets.symmetric(horizontal: 10.0, vertical: 20.0),
- backgroundColor: Colors.white,
- surfaceTintColor: Colors.white,
- shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(12.0)),
- content: Padding(
- padding: const EdgeInsets.symmetric(horizontal: 10.0),
- child: Column(
- mainAxisSize: MainAxisSize.min,
- children: [
- Image.asset('assets/images/logo.png', width: 90.0, height: 90.0, fit: BoxFit.cover,),
- const SizedBox(height: 10.0),
- const Text('Flutter3-WChat', style: TextStyle(color: Color(0xFF0091EA), fontSize: 22.0),),
- const SizedBox(height: 5.0),
- const Text('基于flutter3+dart3开发跨平台仿微信App聊天实例。', style: TextStyle(color: Colors.black45),),
- const SizedBox(height: 20.0),
- Text('2024/01 Andy Q: 282310962', style: TextStyle(color: Colors.grey[400], fontSize: 12.0),),
- ],
- ),
- ),
- ),
- ),
- );
- }
- );
- }
- // 二维码名片弹窗
- void qrcodeAlertDialog(BuildContext context) {
- showDialog(
- context: context,
- builder: (context) {
- return UnconstrainedBox(
- constrainedAxis: Axis.vertical,
- child: SizedBox(
- width: 320.0,
- child: AlertDialog(
- contentPadding: const EdgeInsets.symmetric(horizontal: 10.0, vertical: 20.0),
- backgroundColor: const Color(0xFF07C160),
- surfaceTintColor: const Color(0xFF07C160),
- shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(3.0)),
- content: Padding(
- padding: const EdgeInsets.symmetric(horizontal: 10.0),
- child: Column(
- mainAxisSize: MainAxisSize.min,
- children: [
- Image.asset('assets/images/qrcode.png', width: 250.0, fit: BoxFit.cover,),
- const SizedBox(height: 15.0),
- const Text('扫一扫,加我公众号', style: TextStyle(color: Colors.white60, fontSize: 14.0,),),
- ],
- ),
- ),
- ),
- ),
- );
- }
- );
- }
- // 退出登录弹窗
- void logoutAlertDialog(BuildContext context) {
- 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(12.0)),
- elevation: 2.0,
- actionsPadding: const EdgeInsets.all(15.0),
- actions: [
- TextButton(
- onPressed: () {Navigator.of(context).pop();},
- child: const Text('取消', style: TextStyle(color: Colors.black54),)
- ),
- TextButton(
- onPressed: handleLogout,
- child: const Text('退出登录', style: TextStyle(color: Colors.red),)
- ),
- ],
- );
- }
- );
- }
flutter实现微信朋友圈九宫格
- GroupZone(images: item['images']),
- GroupZone(
- images: uploadList,
- album: true,
- onChoose: () async {
- Toast.show('选择手机相册图片', duration: 2, gravity: 1);
- },
- ),
- // 创建可点击预览图片
- createImage(BuildContext context, String img, int key) {
- return GestureDetector(
- child: Hero(
- tag: img, // 放大缩小动画效果标识
- child: img == '+' ?
- Container(color: Colors.transparent, child: const Icon(Icons.add, size: 30.0, color: Colors.black45),)
- :
- Image.asset(
- img,
- width: width,
- fit: BoxFit.contain,
- ),
- ),
- onTap: () {
- // 选择图片
- if(img == '+') {
- onChoose!();
- }else {
- Navigator.of(context).push(FadeRoute(route: ImageViewer(
- images: album ? imgList!.sublist(0, imgList!.length - 1) : imgList,
- index: key,
- )));
- }
- },
- );
- }
使用photo_view插件实现预览大图功能,支持预览单张及多张大图。
- import 'package:flutter/material.dart';
- import 'package:photo_view/photo_view.dart';
- import 'package:photo_view/photo_view_gallery.dart';
- class ImageViewer extends StatefulWidget {
- const ImageViewer({
- super.key,
- this.images,
- this.index = 0,
- });
- final List? images; // 预览图列表
- final int index; // 当前预览图索引
- @override
- State<ImageViewer> createState() => _ImageViewerState();
- }
- class _ImageViewerState extends State<ImageViewer> {
- int currentIndex = 0;
- @override
- void initState() {
- super.initState();
- currentIndex = widget.index;
- }
- @override
- Widget build(BuildContext context) {
- var imgCount = widget.images?.length;
- return Scaffold(
- body: Stack(
- children: [
- Positioned(
- top: 0,
- left: 0,
- bottom: 0,
- right: 0,
- child: GestureDetector(
- child: imgCount == 1 ? PhotoView(
- imageProvider: AssetImage(widget.images![0]),
- backgroundDecoration: const BoxDecoration(
- color: Colors.black,
- ),
- minScale: PhotoViewComputedScale.contained,
- maxScale: PhotoViewComputedScale.covered * 2,
- heroAttributes: PhotoViewHeroAttributes(tag: widget.images![0]),
- enableRotation: true,
- )
- :
- PhotoViewGallery.builder(
- itemCount: widget.images?.length,
- builder: (context, index) {
- return PhotoViewGalleryPageOptions(
- imageProvider: AssetImage(widget.images![index]),
- minScale: PhotoViewComputedScale.contained,
- maxScale: PhotoViewComputedScale.covered * 2,
- heroAttributes: PhotoViewHeroAttributes(tag: widget.images![index]),
- );
- },
- scrollPhysics: const BouncingScrollPhysics(),
- backgroundDecoration: const BoxDecoration(
- color: Colors.black,
- ),
- pageController: PageController(initialPage: widget.index),
- enableRotation: true,
- onPageChanged: (index) {
- setState(() {
- currentIndex = index;
- });
- },
- ),
- onTap: () {
- Navigator.of(context).pop();
- },
- ),
- ),
- // 图片索引index
- Positioned(
- top: MediaQuery.of(context).padding.top + 15,
- width: MediaQuery.of(context).size.width,
- child: Center(
- child: Visibility(
- visible: imgCount! > 1 ? true : false,
- child: Text('${currentIndex+1} / ${widget.images?.length}', style: const TextStyle(color: Colors.white)),
- )
- ),
- ),
- ],
- ),
- );
- }
- }
flutter3聊天模块
文本框TextField设置maxLines: null即可实现多行文本输入,支持图文emoj混排,网址连接识别等功能。
- // 输入框
- Offstage(
- offstage: voiceBtnEnable,
- child: TextField(
- decoration: const InputDecoration(
- isDense: true,
- hoverColor: Colors.transparent,
- contentPadding: EdgeInsets.all(8.0),
- border: OutlineInputBorder(borderSide: BorderSide.none),
- ),
- style: const TextStyle(fontSize: 16.0,),
- maxLines: null,
- controller: editorController,
- focusNode: editorFocusNode,
- cursorColor: const Color(0xFF07C160),
- onChanged: (value) {},
- ),
- ),
支持仿微信语音按住说话,左滑取消发送、右滑转换语音功能。
- // 语音
- Offstage(
- offstage: !voiceBtnEnable,
- child: GestureDetector(
- child: Container(
- decoration: BoxDecoration(
- color: Colors.white,
- borderRadius: BorderRadius.circular(5),
- ),
- alignment: Alignment.center,
- height: 40.0,
- width: double.infinity,
- child: Text(voiceTypeMap[voiceType], style: const TextStyle(fontSize: 15.0),),
- ),
- onPanStart: (details) {
- setState(() {
- voiceType = 1;
- voicePanelEnable = true;
- });
- },
- onPanUpdate: (details) {
- Offset pos = details.globalPosition;
- double swipeY = MediaQuery.of(context).size.height - 120;
- double swipeX = MediaQuery.of(context).size.width / 2 + 50;
- setState(() {
- if(pos.dy >= swipeY) {
- voiceType = 1; // 松开发送
- }else if (pos.dy < swipeY && pos.dx < swipeX) {
- voiceType = 2; // 左滑松开取消
- }else if (pos.dy < swipeY && pos.dx >= swipeX) {
- voiceType = 3; // 右滑语音转文字
- }
- });
- },
- onPanEnd: (details) {
- // print('停止录音');
- setState(() {
- switch(voiceType) {
- case 1:
- Toast.show('发送录音文件', duration: 1, gravity: 1);
- voicePanelEnable = false;
- break;
- case 2:
- Toast.show('取消发送', duration: 1, gravity: 1);
- voicePanelEnable = false;
- break;
- case 3:
- Toast.show('语音转文字', duration: 1, gravity: 1);
- voicePanelEnable = true;
- voiceToTransfer = true;
- break;
- }
- voiceType = 0;
- });
- },
- ),
- ),
按住录音显示面板
- // 录音主体(按住说话/松开取消/语音转文本)
- Visibility(
- visible: voicePanelEnable,
- child: Material(
- color: const Color(0xDD1B1B1B),
- child: Stack(
- children: [
- // 取消发送+语音转文字
- Positioned(
- bottom: 120,
- left: 30,
- right: 30,
- child: Visibility(
- visible: !voiceToTransfer,
- child: Column(
- children: [
- // 语音动画层
- Stack(
- children: [
- Container(
- height: 70.0,
- margin: const EdgeInsets.symmetric(horizontal: 50.0),
- decoration: BoxDecoration(
- color: Colors.white,
- borderRadius: BorderRadius.circular(15.0),
- ),
- child: Row(
- mainAxisAlignment: MainAxisAlignment.center,
- children: [
- Image.asset('assets/images/voice_record.gif', height: 30.0,)
- ],
- ),
- ),
- Positioned(
- right: (MediaQuery.of(context).size.width - 60) / 2,
- bottom: 1,
- child: RotatedBox(
- quarterTurns: 0,
- child: CustomPaint(painter: ArrowShape(arrowColor: Colors.white, arrowSize: 10.0)),
- )
- ),
- ],
- ),
- const SizedBox(height: 50.0,),
- // 操作项
- Row(
- mainAxisAlignment: MainAxisAlignment.spaceBetween,
- children: [
- // 取消发送
- Container(
- height: 60.0,
- width: 60.0,
- decoration: BoxDecoration(
- borderRadius: BorderRadius.circular(50.0),
- color: voiceType == 2 ? Colors.red : Colors.black38,
- ),
- child: const Icon(Icons.close, color: Colors.white54,),
- ),
- // 语音转文字
- Container(
- height: 60.0,
- width: 60.0,
- decoration: BoxDecoration(
- borderRadius: BorderRadius.circular(50.0),
- color: voiceType == 3 ? Colors.green : Colors.black38,
- ),
- child: const Icon(Icons.translate, color: Colors.white54,),
- ),
- ],
- ),
- ],
- ),
- ),
- ),
- // 语音转文字(识别结果状态)
- Positioned(
- bottom: 120,
- left: 30,
- right: 30,
- child: Visibility(
- visible: voiceToTransfer,
- child: Column(
- children: [
- // 提示结果
- Stack(
- children: [
- Container(
- height: 100.0,
- decoration: BoxDecoration(
- color: Colors.red,
- borderRadius: BorderRadius.circular(15.0),
- ),
- child: const Row(
- mainAxisAlignment: MainAxisAlignment.center,
- children: [
- Icon(Icons.info, color: Colors.white,),
- Text('未识别到文字。', style: TextStyle(color: Colors.white),),
- ],
- ),
- ),
- Positioned(
- right: 35.0,
- bottom: 1,
- child: RotatedBox(
- quarterTurns: 0,
- child: CustomPaint(painter: ArrowShape(arrowColor: Colors.red, arrowSize: 10.0)),
- )
- ),
- ],
- ),
- const SizedBox(height: 50.0,),
- // 操作项
- Row(
- mainAxisAlignment: MainAxisAlignment.spaceBetween,
- children: [
- GestureDetector(
- child: Container(
- height: 60.0,
- width: 60.0,
- decoration: const BoxDecoration(
- color: Colors.transparent,
- ),
- child: const Column(
- mainAxisAlignment: MainAxisAlignment.center,
- children: [
- Icon(Icons.undo, color: Colors.white54,),
- Text('取消', style: TextStyle(color: Colors.white70),)
- ],
- ),
- ),
- onTap: () {
- setState(() {
- voicePanelEnable = false;
- voiceToTransfer = false;
- });
- },
- ),
- GestureDetector(
- child: Container(
- height: 60.0,
- width: 100.0,
- decoration: const BoxDecoration(
- color: Colors.transparent,
- ),
- child: const Column(
- mainAxisAlignment: MainAxisAlignment.center,
- children: [
- Icon(Icons.graphic_eq_rounded, color: Colors.white54,),
- Text('发送原语音', style: TextStyle(color: Colors.white70),)
- ],
- ),
- ),
- onTap: () {},
- ),
- GestureDetector(
- child: Container(
- height: 60.0,
- width: 60.0,
- decoration: BoxDecoration(
- borderRadius: BorderRadius.circular(50.0),
- color: Colors.white12,
- ),
- child: const Icon(Icons.check, color: Colors.white12,),
- ),
- onTap: () {},
- ),
- ],
- ),
- ],
- ),
- ),
- ),
- // 提示文字(操作状态)
- Positioned(
- bottom: 120,
- left: 0,
- width: MediaQuery.of(context).size.width,
- child: Visibility(
- visible: !voiceToTransfer,
- child: Align(
- child: Text(voiceTypeMap[voiceType], style: const TextStyle(color: Colors.white70),),
- ),
- ),
- ),
- // 背景
- Align(
- alignment: Alignment.bottomCenter,
- child: Visibility(
- visible: !voiceToTransfer,
- child: Image.asset('assets/images/voice_record_bg.webp', width: double.infinity, height: 100.0, fit: BoxFit.fill),
- ),
- ),
- // 背景图标
- Positioned(
- bottom: 25,
- left: 0,
- width: MediaQuery.of(context).size.width,
- child: Visibility(
- visible: !voiceToTransfer,
- child: const Align(
- child: Icon(Icons.graphic_eq_rounded, color: Colors.black54,),
- ),
- ),
- ),
- ],
- ),
- ),
- )
flutter3绘制箭头
聊天模块消息及各种箭头展示,通过flutter提供的画板功能绘制箭头。
- // 绘制气泡箭头
- class ArrowShape extends CustomPainter {
- ArrowShape({
- required this.arrowColor,
- this.arrowSize = 7,
- });
- final Color arrowColor; // 箭头颜色
- final double arrowSize; // 箭头大小
- @override
- void paint(Canvas canvas, Size size) {
- var paint = Paint()..color = arrowColor;
- var path = Path();
- path.lineTo(-arrowSize, 0);
- path.lineTo(0, arrowSize);
- path.lineTo(arrowSize, 0);
- canvas.drawPath(path, paint);
- }
- @override
- bool shouldRepaint(CustomPainter oldDelegate) {
- return false;
- }
- }
Okay,以上就是Flutter3+Dart3开发全平台聊天App实例的一些知识分享,希望对大家有所帮助哈~~
最后附上两个最新实战项目
uni-app+vue3+pinia2仿抖音直播商城:https://www.cnblogs.com/xiaoyan2017/p/17938517
electron27+react18 hooks仿macos桌面系统:https://www.cnblogs.com/xiaoyan2017/p/17850653.html
flutter3+dart3聊天室|Flutter3跨平台仿微信App语音聊天/朋友圈的更多相关文章
- Taro聊天室|react+taro仿微信聊天App界面|taro聊天实例
一.项目简述 taro-chatroom是基于Taro多端实例聊天项目,运用Taro+react+react-redux+taroPop+react-native等技术开发的仿微信App界面聊天室,实 ...
- Svelte3聊天室|svelte+svelteKit仿微信聊天实例|svelte.js开发App
基于svelte3.x+svelteKit构建仿微信App聊天应用svelte-chatroom. svelte-chatroom 基于svelte.js+svelteKit+mescroll.js+ ...
- Svelte3.x网页聊天实例|svelte.js仿微信PC版聊天svelte-webchat
基于Svelte3+SvelteKit+Sass仿微信Mac界面聊天实战项目SvelteWebChat. 基于svelte3+svelteKit+sass+mescroll.js+svelte-lay ...
- vue聊天室|h5+vue仿微信聊天界面|vue仿微信
一.项目简介 基于Vue2.0+Vuex+vue-router+webpack2.0+es6+vuePhotoPreview+wcPop等技术架构开发的仿微信界面聊天室——vueChatRoom,实现 ...
- uni-app聊天室|vue+uniapp仿微信聊天实例|uniapp仿微信App界面
一.介绍 运用UniApp+Vue+Vuex+swiper+uniPop等技术开发的仿微信原生App聊天室|仿微信聊天界面实例项目uniapp-chatroom,实现了发送图文消息.表情(gif图), ...
- react聊天室|react+redux仿微信聊天IM实例|react仿微信界面
一.项目概况 基于react+react-dom+react-router-dom+redux+react-redux+webpack2.0+react-photoswipe+swiper等技术混合开 ...
- electron聊天室|vue+electron-vue仿微信客户端|electron桌面聊天
一.项目概况 基于Electron+vue+electron-vue+vuex+Nodejs+vueVideoPlayer+electron-builder等技术仿制微信电脑端界面聊天室实例,实现消息 ...
- Vue3.0聊天室|vue3+vant3仿微信聊天实例|vue3.x仿微信app界面
一.项目简介 基于Vue3.0+Vant3.x+Vuex4.x+Vue-router4+V3Popup等技术开发实现的仿微信手机App聊天实例项目Vue3-Chatroom.实现了发送图文表情消息/g ...
- Vue3.0+Electron聊天室|electron跨平台仿QQ客户端|vue3.x聊天应用
基于vue3+electron11跨端仿制QQ桌面应用实战Vue3ElectronQchat. 使用vue3+electron+vuex4+ant-design-vue+v3scroll+v3laye ...
- Tauri-Vue3桌面端聊天室|tauri+vite3仿微信|tauri聊天程序EXE
基于tauri+vue3.js+vite3跨桌面端仿微信聊天实例TauriVue3Chat. tauri-chat 运用最新tauri+vue3+vite3+element-plus+v3layer等 ...
随机推荐
- CSS Sticky Footer 几种实现方式
项目里,有个需求,登录页,信息,需要使用到sticky footer布局,刚好,巩固下这个技术: 核心代码: 播客: https://www.jb51.net/css/676798.html 视频:h ...
- nextTick使用
- python之logging日志
一.logging介绍: 使用 logging.debug(text)来打印信息,info等的使用方法与debug一致,都只有一个位置参数 默认日志界别为:会输出warning以上的信息,代码示例: ...
- wxpython窗体之间传递参数
如何界面存在frame1与frame2,通过frame1打开页面frame2,并将frame2的值传递给frame1 可以使用回调函数传值参考具体代码如下: # -*- coding: utf-8 - ...
- 【scikit-learn基础】--『回归模型评估』之误差分析
模型评估在统计学和机器学习中具有至关重要,它帮助我们主要目标是量化模型预测新数据的能力. 在这个数据充斥的时代,没有评估的模型就如同盲人摸象,可能带来误导和误判.模型评估不仅是一种方法,更是一种保障, ...
- 14-TTL与非门的输入特性和输出特性
TTL与非门的电压传输特性 传输特性 输入电压连续发生变化,输出电压发生什么变化?需要研究输出电压与输入电压之间的关系 输入小的时候,输出大的信号:输入大时候输出小信号 中间有截止和导通,需要过渡过程 ...
- 如何查找SpringBoot应用中的请求路径(不使用idea)
背景 昨天有个同事向我咨询某个接口的物理表是哪个,由于公司业务较多.这块业务的确不是我负责的,也没有使用idea不能全局搜索(eclipse搜不到jar内的字符串),也就回复了不清楚. 除了自己写代码 ...
- [转帖]AF_UNIX和AF_INET
https://www.cnblogs.com/shangerzhong/p/9153737.html family参数代表地址家族,比较常用的为AF_INET或AF_UNIX.AF_UNIX用于同一 ...
- [转帖]TiDB 环境与系统配置检查
https://docs-archive.pingcap.com/zh/tidb/v6.0/check-before-deployment 本文介绍部署 TiDB 前的环境检查操作,以下各项操作按优先 ...
- [转帖]InnoDB表聚集索引层高什么时候发生变化
导读 本文略长,主要解决以下几个疑问 1.聚集索引里都存储了什么宝贝 2.什么时候索引层高会发生变化 3.预留的1/16空闲空间做什么用的 4.记录被删除后的空间能回收重复利用吗 1.背景信息 1.1 ...