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开发跨平台项目,性能有了大幅度提升,官方支持编译到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:constOutlineInputBorder(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: constLinearGradient(
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倒计时 voidhandleVcode() {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:constBoxDecoration(
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
中引入字体文件。
classFStyle {//自定义iconfont图标 static iconfont(int codePoint, {double size = 16.0, Color?color}) {returnIcon(
IconData(codePoint, fontFamily:'iconfont', matchTextDirection: true),
size: size,
color: color,
);
}//自定义Badge红点 static badge(intcount, {
Color color=Colors.redAccent,bool isdot = false,double height = 18.0,double width = 18.0}) {
final num= count > 99 ? '99+': count;returnContainer(
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(intcodePoint, String title, value) {returnPopupMenuItem(
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: constClassicHeader(//showMessage: false, ),//加载更多提示 footer: ClassicFooter(),//下拉刷新逻辑 onRefresh: () async {//...下拉逻辑 await Future.delayed(const Duration(seconds: 2));
},//上拉加载逻辑 onLoad: () async {//... },
child: ListView.builder(
itemCount: chatList.length,
itemBuilder: (context, index) {returnInk(//... );
},
),
)
如上图:弹窗功能均是自定义AlertDialog实现效果。通过
无限制容器UnconstrainedBox配合SizedBox组件实现自定义窗口大小。
//关于弹窗 voidaboutAlertDialog(BuildContext context) {
showDialog(
context: context,
builder: (context) {returnUnconstrainedBox(
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),),
],
),
),
),
),
);
}
);
}//二维码名片弹窗 voidqrcodeAlertDialog(BuildContext context) {
showDialog(
context: context,
builder: (context) {returnUnconstrainedBox(
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,),),
],
),
),
),
),
);
}
);
}//退出登录弹窗 voidlogoutAlertDialog(BuildContext context) {
showDialog(
context: context,
builder: (context) {returnAlertDialog(
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, intkey) {returnGestureDetector(
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';classImageViewer extends StatefulWidget {constImageViewer({
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;
@overridevoidinitState() {
super.initState();
currentIndex=widget.index;
}
@override
Widget build(BuildContext context) {var imgCount = widget.images?.length;returnScaffold(
body: Stack(
children: [
Positioned(
top:0,
left:0,
bottom:0,
right:0,
child: GestureDetector(
child: imgCount== 1 ?PhotoView(
imageProvider: AssetImage(widget.images![0]),
backgroundDecoration:constBoxDecoration(
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) {returnPhotoViewGalleryPageOptions(
imageProvider: AssetImage(widget.images![index]),
minScale: PhotoViewComputedScale.contained,
maxScale: PhotoViewComputedScale.covered* 2,
heroAttributes: PhotoViewHeroAttributes(tag: widget.images![index]),
);
},
scrollPhysics:constBouncingScrollPhysics(),
backgroundDecoration:constBoxDecoration(
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:constInputDecoration(
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:constIcon(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:constIcon(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:constRow(
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:constBoxDecoration(
color: Colors.transparent,
),
child:constColumn(
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:constBoxDecoration(
color: Colors.transparent,
),
child:constColumn(
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:constIcon(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:constTextStyle(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:constAlign(
child: Icon(Icons.graphic_eq_rounded, color: Colors.black54,),
),
),
),
],
),
),
)
flutter3绘制箭头
聊天模块消息及各种箭头展示,通过flutter提供的画板功能绘制箭头。
//绘制气泡箭头 classArrowShape extends CustomPainter {
ArrowShape({
requiredthis.arrowColor,this.arrowSize = 7,
});
final Color arrowColor;//箭头颜色 final double arrowSize; //箭头大小 @overridevoidpaint(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);
}
@overrideboolshouldRepaint(CustomPainter oldDelegate) {return false;
}
}
Okay,以上就是Flutter3+Dart3开发全平台聊天App实例的一些知识分享,希望对大家有所帮助哈~~