距離2024新春佳節還有3天時間,趁著佳節來臨之際,使用全新跨多平臺技術flutter3+dart3開發了一款仿微信App聊天介面例項專案Flutter3Chat。
flutter3-wchat 實現傳送圖文emoj表情訊息+gif動圖、長按仿微信語音操作皮膚、圖片預覽、紅包及朋友圈等功能。
技術棧
- 開發工具:VScode
- 框架技術:Flutter3.16.5+Dart3.2.3
- UI元件庫:Material-Design3
- 彈窗元件:showDialog/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
目前使用flutter開發的專案支援編譯到全平臺android/ios/macos/linux/windows/web,可以說是前景非常廣大的。
專案目錄結構
透過flutter create app_proj
命令即可快速建立一個多平臺專案。
不過在開發之前,需要先配置好flutter開發環境。大家去官網根據步驟一步一步配置即可。
https://flutter.dev/
https://flutter.cn/
https://pub.flutter-io.cn/
https://www.dartcn.com/
如果使用vscode開發專案,可安裝擴充套件外掛。
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());
},
);
}
}
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);
}
- Container和TextField元件配合實現圓角文字框
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漸變色導航條
如何在flutter中導航條實現漸變背景呢,由於AppBar提供的background屬性只能設定顏色,不能設定漸變。只能透過flexibleSpace可伸縮靈活屬性實現功能。
AppBar(
title: Text('Flutter3-Chat'),
flexibleSpace: Container(
decoration: const BoxDecoration(
gradient: LinearGradient(
begin: Alignment.topLeft,
end: Alignment.bottomRight,
colors: [
Color(0xFF0091EA), Color(0xFF07C160)
],
)
),
)
),
flutter實現微信下拉選單/長按選單
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),),
],
),
);
}
// 長按座標點
double posDX = 0.0;
double posDY = 0.0;
// 長按選單
void showContextMenu(BuildContext context) {
bool isLeft = posDX > MediaQuery.of(context).size.width / 2 ? false : true;
bool isTop = posDY > MediaQuery.of(context).size.height / 2 ? false : true;
showDialog(
context: context,
barrierColor: Colors.transparent, // 遮罩透明
builder: (context) {
return Stack(
children: [
Positioned(
top: isTop ? posDY : posDY - 135,
left: isLeft ? posDX : posDX - 135,
width: 135,
child: Material(
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(2.0)),
color: Colors.white,
elevation: 3.0,
clipBehavior: Clip.hardEdge,
child: Column(
children: [
ListTile(
title: const Text('標為未讀', style: TextStyle(color: Colors.black87,),),
dense: true,
onTap: () {},
),
ListTile(
title: const Text('置頂該聊天', style: TextStyle(color: Colors.black87,),),
dense: true,
onTap: () {},
),
ListTile(
title: const Text('不顯示該聊天', style: TextStyle(color: Colors.black87,),),
dense: true,
onTap: () {},
),
ListTile(
title: const Text('刪除', style: TextStyle(color: Colors.black87,),),
dense: true,
onTap: () {},
)
],
),
),
)
],
);
},
);
}
flutter3微信朋友圈九宮格
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,
)));
}
},
);
}
flutter3聊天實現
文字框TextField設定maxLines: null即可實現多行文字輸入。
// 輸入框
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;
});
},
),
),
好了,綜上就是flutter3+dart3開發跨端聊天App例項的一些分享。
https://segmentfault.com/a/1190000044519351
https://segmentfault.com/a/1190000044418592