flutter3-chat:基于flutter3.x+dart3聊天实例|flutter3仿微信App界面
flutter3_wchat全新跨平台flutter3.0仿微信app聊天应用。
基于多端跨平台技术
flutter3+dart3+materialUI+shared_preferences+easy_refresh
全新研发的一款仿微信app应用聊天实战项目。实现发送文字emoj消息+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开发的应用支持运行到 android/ios/macos/linux/windows/web 等多个平台。
项目结构
通过flutter create xxx
命令创建项目,目录结构如下图:
导航栏采用全屏沉浸式,支持透明背景图。
在项目开发初期,大家需要自行配置flutter/dart开发环境SDK。
https://flutter.dev/
https://flutter.cn/
https://pub.flutter-io.cn/
https://www.dartcn.com/
如果使用vscode开发项目,可自行按住flutter/dart扩展。
通过flutter doctor
来检查所需环境是否缺失。一切准备就绪接 下来就是愉快的开发了。
既然看到了这里,是不是感觉还不错~~😁
开发初期是在windows调试比较方便,也可以运行到web上。后期可以直接打包apk运行到手机上。
运行到桌面默认是1280大小,大家可以修改windows/runner/main.cpp文件里面窗口尺寸,适应手机端。
如果大家习惯在模拟器上面运行调试项目,下面列出了一些常见的模拟器及端口连接。
通过adb connect
连接上模拟器之后,flutter devices
查看当前连接设备,执行flutter run
命令即可运行项目到指定的模拟器。
入口main.dart
/// flutter入口文件
import 'dart:io';
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:toast/toast.dart';
// 引入公共样式
import 'styles/index.dart';
// 引入底部tabbar
import 'components/tabbar.dart';
// 引入路由管理
import 'router/index.dart';
// 错误模块
import '../views/error/index.dart';
void main() {
runApp(const MyApp());
}
DateTime? lastPopTime;
class MyApp extends StatelessWidget {
const MyApp({ super.key });
// 退出app提示
Future<bool> appOnPopInvoked(didPop) async {
if(lastPopTime == null || DateTime.now().difference(lastPopTime!) > const Duration(seconds: 2)) {
lastPopTime = DateTime.now();
Toast.show('再按一次退出应用');
return false;
}
SystemNavigator.pop();
return true;
}
@override
Widget build(BuildContext context){
ToastContext().init(context);
return MaterialApp(
title: 'Flutter Chat',
debugShowCheckedModeBanner: false,
theme: ThemeData(
primaryColor: FStyle.primaryColor,
useMaterial3: true,
// windows桌面端字体粗细不一样
fontFamily: Platform.isWindows ? 'Microsoft YaHei' : null,
),
// home: const FTabBar(),
home: PopScope(
// canPop: false,
onPopInvoked: appOnPopInvoked,
child: const FTabBar(),
),
// 初始路由
// initialRoute: '/',
// 自定义路由
onGenerateRoute: onGenerateRoute,
// 错误路由
onUnknownRoute: (settings) {
return MaterialPageRoute(builder: (context) => const Error());
},
);
}
}
Flutter3圆角/渐变背景色
如上图:flutter实现文本框圆角及按钮圆角渐变色。
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组件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),),
),
)
),
flutter3登录验证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
提供的background
属性不能设置渐变颜色,但是可以使用flexibleSpace
属性 配合Container组件gradient
实现导航栏渐变背景色。
AppBar(
title: Text('Flutter3-Chat'),
flexibleSpace: Container(
decoration: const BoxDecoration(
gradient: LinearGradient(
begin: Alignment.topLeft,
end: Alignment.bottomRight,
colors: [
Color(0xFF0091EA), Color(0xFF07C160)
],
)
),
)
),
flutter自定义badge红点/iconfont图标
FStyle.badge(23)
FStyle.badge(2, color: Colors.pink, height: 10.0, width: 10.0)
FStyle.badge(0, isdot: true)
自定义图标使用的阿里iconfont图表库。
在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)),
);
}
}
Flutter3仿微信快捷下拉菜单
通过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),),
],
),
);
}
flutter弹窗展示
项目中使用到了flutter各种弹窗应用场景。
// 关于弹窗
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),)
),
],
);
}
);
}
Flutter3实现微信群聊九宫格图像组
支持1-9张图片不同的排列组合。
const size = 44.0; // 默认一张图片尺寸
const padding = 2.0;
const margin = 2.0;
int row = 0; // 图片行数
int col = 0; // 图片列数
class GroupAvatar extends StatelessWidget {
const GroupAvatar({
super.key,
this.avatars,
});
final List<String>? avatars;
// 创建图片
createImage(String avatar, double width) {
return Image.asset(
avatar,
height: width,
width: width,
fit: BoxFit.fill,
);
}
// 顶部一张图片
avatarOne(List<Widget> stacks, Widget child, int count, i, imgWidth, left, top) {
// ...
}
// 顶部两张图片
avatarTwo(List<Widget> stacks, Widget child, int count, i, imgWidth, left, top) {
// ...
}
// 其它情况
avatarOther(List<Widget> stacks, Widget child, int count, i, imgWidth, left, top, colMax) {
// ...
}
@override
Widget build(BuildContext context){
var count = avatars?.length;
int colMax;
List<Widget> icons = [];
List<Widget> stacks = [];
double imgWidth;
if(count! == 1) {
return SizedBox(
width: size,
height: size,
child: createImage(avatars![0], size),
);
}
if(count >= 5) {
colMax = 3;
imgWidth = (size - (padding * colMax) - margin) / colMax;
}else {
colMax = 2;
imgWidth = (size - (padding * colMax) - margin) / colMax;
}
for(var i = 0; i < count; i++) {
icons.add(createImage(avatars![i], imgWidth));
}
row = 0;
col = 0;
var centerTop = 0.0;
if(count == 2 || count == 5 || count == 6) {
centerTop = (imgWidth + margin) / 2;
}
for(var i = 0; i < count; i++) {
var left = imgWidth * row + padding * (row + 1);
var top = imgWidth * col + padding * col + centerTop;
switch(count) {
case 3:
case 7:
avatarOne(stacks, icons[i], count, i, imgWidth, left, top);
break;
case 5:
case 8:
avatarTwo(stacks, icons[i], count, i, imgWidth, left, top);
break;
default:
avatarOther(stacks, icons[i], count, i, imgWidth, left, top, colMax);
break;
}
}
return Container(
width: size,
height: size,
color: const Color(0xFFEEEEEE),
padding: const EdgeInsets.only(top: padding),
child: Stack(
children: stacks,
),
);
}
}
flutter聊天功能模块
// 输入框
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) {},
),
),
文本框支持文字/emoj输入,多行文本输入。输入链接,消息自动转换为网址通过浏览器打开。
仿微信按住说话语音面板模块。
// 语音
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;
});
},
),
),
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;
}
}
OK,综上就是flutter3/dart3开发跨平台聊天应用的一些知识分享。
最后附上两个最近的实例项目
-
基于uni-app+vue3+pinia跨端仿抖音直播商城
https://blog.csdn.net/yanxinyun1990/article/details/135329724 -
react18+electron27桌面端macos管理系统
https://blog.csdn.net/yanxinyun1990/article/details/134567716