flutter3-wechat:基于Flutter3.x+Dart3+Matetial3聊天App应用

趁着快过年之际,分享一款最新研发的基于Flutter3+Dart3技术跨端仿微信App聊天实例项目。

flutter3-chat 基本实现了发送图文表情消息/gif大图、长按仿微信语音操作面板、图片预览、红包及朋友圈等功能。

使用技术

  • 编辑器:VScode
  • 框架技术:Flutter3.16.5+Dart3.2.3
  • UI组件库:Material-Design3
  • 图片预览: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 create appname即可快速创建一个flutter项目模板。

开发之前,需要大家自己先配置好flutter开发环境。

flutter.dev/
flutter.cn/
pub.flutter-io.cn/
www.dartcn.com/

如果使用vscode开发项目,可安装扩展插件。

flutter3入口main.dart

ts 复制代码
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倒计时
ts 复制代码
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);
}
  • 圆角文本框/渐变按钮
ts 复制代码
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;
            });
          },
        ),
      )
    ],
  ),
),
ts 复制代码
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渐变导航状态栏

Appbar提供的background只能设置简单的颜色,不能设置渐变背景,可通过伸缩灵活区域属性 flexibleSpace 配合gradient即可快速实现渐变导航栏。

ts 复制代码
AppBar(
  title: Text('Flutter3-Chat'),
  flexibleSpace: Container(
    decoration: const BoxDecoration(
      gradient: LinearGradient(
        begin: Alignment.topLeft,
        end: Alignment.bottomRight,
        colors: [
          Color(0xFF0091EA), Color(0xFF07C160)
        ],
      )
    ),
  )
),
ts 复制代码
// 长按坐标点
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聊天功能

ts 复制代码
// 输入框
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) {},
  ),
),
ts 复制代码
// 语音
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;
      });
    },
  ),
),

End,以上就是flutter3开发仿微信app聊天项目的一些分享,希望能喜欢!

juejin.cn/post/731918...

相关推荐
孤鸿玉17 小时前
Fluter InteractiveViewer 与ScrollView滑动冲突问题解决
flutter
叽哥1 天前
Flutter Riverpod上手指南
android·flutter·ios
BG2 天前
Flutter 简仿Excel表格组件介绍
flutter
zhangmeng2 天前
FlutterBoost在iOS26真机运行崩溃问题
flutter·app·swift
恋猫de小郭2 天前
对于普通程序员来说 AI 是什么?AI 究竟用的是什么?
前端·flutter·ai编程
卡尔特斯2 天前
Flutter A GlobalKey was used multipletimes inside one widget'schild list.The ...
flutter
w_y_fan2 天前
Flutter 滚动组件总结
前端·flutter
醉过才知酒浓2 天前
Flutter Getx 的页面传参
flutter
火柴就是我3 天前
flutter 之真手势冲突处理
android·flutter
Speed1233 天前
`mockito` 的核心“打桩”规则
flutter·dart