flutter3-dart-chat:基於flutter3+material-ui仿微信App聊天應用

發表於2024-02-11

距離2024新春佳節還有3天時間,趁著佳節來臨之際,使用全新跨多平臺技術flutter3+dart3開發了一款仿微信App聊天介面例項專案Flutter3Chat

7cd775e115c23caf19a1fe8a12c1ba6b_1289798-20240205160906111-825831202.png

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開發專案,可安裝擴充套件外掛。

563a42ece7402ea12731c372fffd7363_1289798-20240205142055892-198925888.png

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倒數計時/圓角/漸變按鈕

26b6a80d1cfd020761eef241fba5623c_1289798-20240205145858178-428080361.png

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);
}

42dc5a00df96def54d452c65f8225327_1289798-20240205144808162-1407042051.png

  • 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漸變色導航條

image.png

如何在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實現微信下拉選單/長按選單

ac63566f9e847b885ebe2ad879b12783_1289798-20240205153458760-555463628.png

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),),
      ],
    ),
  );
}

image.png

// 長按座標點
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: () {},
                  )
                ],
              ),
            ),
          )
        ],
      );
    },
  );
}

image.png

flutter3微信朋友圈九宮格

9d50aa8f37b5d8b41431a16fcd23283c_1289798-20240205154947818-1459673697.png

3d5ba0c44e9c4569766987132cb0076a_1289798-20240205155031859-387646627.png

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聊天實現

34fc6e42c7a5064d947a80e090dd2d0d_1289798-20240205160545174-1324081609.png

文字框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

相關文章