当前位置: 首页 > article >正文

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

在这里插入图片描述


http://www.kler.cn/a/229298.html

相关文章:

  • Deep4SNet: deep learning for fake speech classification
  • <OS 有关>Ubuntu 24 安装 openssh-server, tailscale+ssh 慢增加
  • Flink 应用
  • vue2制作长方形容器,正方形网格散点图,并且等比缩放拖动
  • FFmpeg开发笔记(七)欧拉系统编译安装FFmpeg
  • NVIDIA CUDA Linux 官方安装指南
  • 关于RabbitMQ面试题汇总
  • ChatGPT辅助编程,一次有益的尝试
  • 3dmatch-toolbox详细安装教程-Ubuntu14.04
  • Web APIs 2 事件
  • 解决“使用Edge浏览器每次鼠标点击会出现一个黑色边框”的问题
  • Spring Web Header 解析常见错误
  • opencv0014 索贝尔(sobel)算子
  • 如何在Termux中使用Hexo结合内网穿透工具实现远程访问本地博客站点
  • python实现rdbms和neo4j的转换
  • #Z0458. 树的中心2
  • 解决跨域问题8种方法,含网关、Nginx和SpringBoot~
  • 【数据结构与算法】之排序系列-20240205
  • 人工智能之大数定理和中心极限定理
  • Java中SQL注入的防范与解决方法
  • OpenCV 图像处理六(傅里叶变换、模板匹配与霍夫变换)
  • ubuntu22.04@laptop OpenCV Get Started: 000_hello_opencv
  • HomeAssistant系统添加HACS插件商店与远程控制家中智能家居
  • LeetCode、746. 使用最小花费爬楼梯【简单,动态规划 线性DP】
  • Webpack插件浅析
  • 用 Delphi 程序调用 Python 代码画曲线图 -- 数据来自 Delphi 程序