flutter3-winchat桌面端聊天實例|Flutter3+Dart3+Getx仿微信Exe程式

来源:https://www.cnblogs.com/xiaoyan2017/p/18048244
-Advertisement-
Play Games

首發原創flutter3+bitsdojo_window+getx客戶端仿微信exe聊天Flutter-WinChat。 flutter3-dart3-winchat 基於flutter3+dart3+getx+bitsdojo_window+file_picker+media_kit等技術開發桌面 ...


首發原創flutter3+bitsdojo_window+getx客戶端仿微信exe聊天Flutter-WinChat

flutter3-dart3-winchat 基於flutter3+dart3+getx+bitsdojo_window+file_picker+media_kit等技術開發桌面端仿微信聊天exe實戰項目。實現了聊天消息、通訊錄、收藏、朋友圈、短視頻、我的等頁面模塊。

實現技術

  • 編輯器:vscode
  • 技術框架:flutter3.16.5+dart3.2.3
  • 視窗管理:bitsdojo_window: ^0.1.6
  • 托盤圖標:system_tray: ^2.0.3
  • 路由/狀態管理:get: ^4.6.6
  • 本地存儲:get_storage: ^2.1.1
  • 圖片預覽插件:photo_view: ^0.14.0
  • 網址預覽:url_launcher: ^6.2.4
  • 視頻組件:media_kit: ^1.1.10+1
  • 文件選擇器:file_picker: ^6.1.1

目前網上關於flutter3.x開發的桌面端項目並不多,希望有更多的開發者能加入flutter在window/macos客戶端的探索開發。

項目結構

如上圖:flutter構建的項目結構層級。

需要註意的是在開發之前需要自行配置好flutter sdkdart sdk環境。

https://flutter.dev/

https://www.dartcn.com/

通過 flutter run -d windows 命令,運行到windows上。

主入口main.dart

import 'dart:io';
import 'package:flutter/material.dart';
import 'package:bitsdojo_window/bitsdojo_window.dart';
import 'package:get/get.dart';
import 'package:get_storage/get_storage.dart';
import 'package:media_kit/media_kit.dart';
import 'package:system_tray/system_tray.dart';

import 'utils/index.dart';

// 引入公共樣式
import 'styles/index.dart';

// 引入公共佈局模板
import 'layouts/index.dart';

// 引入路由配置
import 'router/index.dart';

void main() async {
  // 初始化get_storage存儲類
  await GetStorage.init();

  // 初始化media_kit視頻套件
  WidgetsFlutterBinding.ensureInitialized();
  MediaKit.ensureInitialized();

  initSystemTray();

  runApp(const MyApp());

  // 初始化bitsdojo_window視窗
  doWhenWindowReady(() {
    appWindow.size = const Size(850, 620);
    appWindow.minSize = const Size(700, 500);
    appWindow.alignment = Alignment.center;
    appWindow.title = 'Flutter3-WinChat';
    appWindow.show();
  });
}

class MyApp extends StatelessWidget {
  const MyApp({super.key});

  @override
  Widget build(BuildContext context) {
    return GetMaterialApp(
      title: 'FLUTTER3 WINCHAT',
      debugShowCheckedModeBanner: false,
      theme: ThemeData(
        primaryColor: FStyle.primaryColor,
        useMaterial3: true,
        // 修正windows端字體粗細不一致
        fontFamily: Platform.isWindows ? 'Microsoft YaHei' : null,
      ),
      home: const Layout(),
      // 初始路由
      initialRoute: Utils.isLogin() ? '/index' :'/login',
      // 路由頁面
      getPages: routes,
      onInit: () {},
      onReady: () {},
    );
  }
}

// 創建系統托盤圖標
Future<void> initSystemTray() async {
  String trayIco = 'assets/images/tray.ico';
  SystemTray systemTray = SystemTray();

  // 初始化系統托盤
  await systemTray.initSystemTray(
    title: 'system-tray',
    iconPath: trayIco,
  );

  // 右鍵菜單
  final Menu menu = Menu();
  await menu.buildFrom([
    MenuItemLabel(label: 'show', onClicked: (menuItem) => appWindow.show()),
    MenuItemLabel(label: 'hide', onClicked: (menuItem) => appWindow.hide()),
    MenuItemLabel(label: 'close', onClicked: (menuItem) => appWindow.close()),
  ]);
  await systemTray.setContextMenu(menu);

  // 右鍵事件
  systemTray.registerSystemTrayEventHandler((eventName) {
    debugPrint('eventName: $eventName');
    if (eventName == kSystemTrayEventClick) {
      Platform.isWindows ? appWindow.show() : systemTray.popUpContextMenu();
    } else if (eventName == kSystemTrayEventRightClick) {
      Platform.isWindows ? systemTray.popUpContextMenu() : appWindow.show();
    }
  });
}

整個項目採用 bitsdojo_window 插件進行視窗管理。支持無邊框視窗,視窗尺寸大小,自定義系統操作按鈕(最大化/最小化/關閉)。

https://pub-web.flutter-io.cn/packages/bitsdojo_window

flutter桌面端通過 system_tray 插件,生成系統托盤圖標。

https://pub-web.flutter-io.cn/packages/system_tray

Flutter路由管理

整個項目採用Getx作為路由和狀態管理。將MaterialApp替換為GetMaterialApp組件。

class MyApp extends StatelessWidget {
  const MyApp({super.key});

  @override
  Widget build(BuildContext context) {
    return GetMaterialApp(
      title: 'FLUTTER3 WINCHAT',
      debugShowCheckedModeBanner: false,
      theme: ThemeData(
        primaryColor: FStyle.primaryColor,
        useMaterial3: true,
      ),
      home: const Layout(),
      // 初始路由
      initialRoute: Utils.isLogin() ? '/index' :'/login',
      // 路由頁面
      getPages: routes,
    );
  }
}

新建router/index.dart路由管理文件。

import 'package:flutter/material.dart';
import 'package:get/get.dart';

// 引入工具類
import '../utils/index.dart';

/* 引入路由頁面 */
import '../views/auth/login.dart';
import '../views/auth/register.dart';
// 首頁
import '../views/index/index.dart';
// 通訊錄
import '../views/contact/index.dart';
import '../views/contact/addfriends.dart';
import '../views/contact/newfriends.dart';
import '../views/contact/uinfo.dart';
// 收藏
import '../views/favor/index.dart';
// 我的
import '../views/my/index.dart';
import '../views/my/setting.dart';
import '../views/my/recharge.dart';
import '../views/my/wallet.dart';
// 朋友圈
import '../views/fzone/index.dart';
import '../views/fzone/publish.dart';
// 短視頻
import '../views/fvideo/index.dart';
// 聊天
import '../views/chat/group-chat/chat.dart';

// 路由地址集合
final Map<String, Widget> routeMap = {
  '/index': const Index(),
  '/contact': const Contact(),
  '/addfriends': const AddFriends(),
  '/newfriends': const NewFriends(),
  '/uinfo': const Uinfo(),
  '/favor': const Favor(),
  '/my': const My(),
  '/setting': const Setting(),
  '/recharge': const Recharge(),
  '/wallet': const Wallet(),
  '/fzone': const Fzone(),
  '/publish': const PublishFzone(),
  '/fvideo': const Fvideo(),
  '/chat': const Chat(),
};

final List<GetPage> patchRoute = routeMap.entries.map((e) => GetPage(
  name: e.key, // 路由名稱
  page: () => e.value, // 路由頁面
  transition: Transition.noTransition, // 跳轉路由動畫
  middlewares: [AuthMiddleware()], // 路由中間件
)).toList();

final List<GetPage> routes = [
  GetPage(name: '/login', page: () => const Login()),
  GetPage(name: '/register', page: () => const Register()),
  ...patchRoute,
];

Getx提供了middlewares中間件進行路由攔截

// 路由攔截
class AuthMiddleware extends GetMiddleware {
  @override
  RouteSettings? redirect(String? route) {
    return Utils.isLogin() ? null : const RouteSettings(name: '/login');
  }
}

Flutter3桌面端自定義最大化/最小化/關閉

flutter開發桌面端項目,為了達到桌面視窗高定製化效果,採用了bitsdojo_window插件。該插件支持去掉系統導航條,自定義視窗大小、右上角操作按鈕、拖拽視窗等功能。

@override
Widget build(BuildContext context){
  return Row(
    children: [
      Container(
        child: widget.leading,
      ),
      Visibility(
        visible: widget.minimizable,
        child: MouseRegion(
          cursor: SystemMouseCursors.click,
          child: SizedBox(
            width: 32.0,
            height: 36.0,
            child: MinimizeWindowButton(colors: buttonColors, onPressed: handleMinimize,),
          )
        ),
      ),
      Visibility(
        visible: widget.maximizable,
        child: MouseRegion(
          cursor: SystemMouseCursors.click,
          child: SizedBox(
            width: 32.0,
            height: 36.0,
            child: isMaximized ? 
            RestoreWindowButton(colors: buttonColors, onPressed: handleMaxRestore,)
            : 
            MaximizeWindowButton(colors: buttonColors, onPressed: handleMaxRestore,),
          ),
        ),
      ),
      Visibility(
        visible: widget.closable,
        child: MouseRegion(
          cursor: SystemMouseCursors.click,
          child: SizedBox(
            width: 32.0,
            height: 36.0,
            child: CloseWindowButton(colors: closeButtonColors, onPressed: handleExit,),
          ),
        ),
      ),
      Container(
        child: widget.trailing,
      ),
    ],
  );
}

自定義最大化/最小化/關閉功能。

// 最小化
void handleMinimize() {
  appWindow.minimize();
}
// 設置最大化/恢復
void handleMaxRestore() {
  appWindow.maximizeOrRestore();
}
// 關閉
void handleExit() {
  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(3.0)),
        elevation: 3.0,
        actionsPadding: const EdgeInsets.all(15.0),
        actions: [
          TextButton(
            onPressed: () {
              Get.back();
              appWindow.close();
            },
            child: const Text('退出', style: TextStyle(color: Colors.red),)
          ),
          TextButton(
            onPressed: () {
              Get.back();
              appWindow.hide();
            },
            child: const Text('最小化至托盤', style: TextStyle(color: Colors.deepPurple),)
          ),
        ],
      );
    }
  );
}

flutter內置了滑鼠手勢組件MouseRegion。根據需求可以自定義設置不同的滑鼠樣式。

問:bitsdojo_window設置最大化/恢復不能實時監測視窗尺寸變化?

答:大家可以通過flutter內置的WidgetsBindingObserver來監測視窗變化。

class _WinbtnState extends State<Winbtn> with WidgetsBindingObserver {
  // 是否最大化
  bool isMaximized = false;

  @override
  void initState() {
    super.initState();
    WidgetsBinding.instance.addObserver(this);
  }

  @override
  void dispose() {
    WidgetsBinding.instance.removeObserver(this);
    super.dispose();
  }

  // 監聽視窗尺寸變化
  @override
  void didChangeMetrics() {
    super.didChangeMetrics();
    WidgetsBinding.instance.addPostFrameCallback((_) {
      setState(() {
        isMaximized = appWindow.isMaximized;
      });
    });
  }

  // ...
}

Flutter3公共佈局模板

整體項目佈局參照了微信桌面端界面。分為左側操作欄+側邊欄+右側內容區三大模塊。

class Layout extends StatefulWidget {
  const Layout({
    super.key,
    this.activitybar = const Activitybar(),
    this.sidebar,
    this.workbench,
    this.showSidebar = true,
  });

  final Widget? activitybar; // 左側操作欄
  final Widget? sidebar; // 側邊欄
  final Widget? workbench; // 右側工作面板
  final bool showSidebar; // 是否顯示側邊欄

  @override
  State<Layout> createState() => _LayoutState();
}

左側操作欄無點擊事件區域支持拖拽視窗。

return Scaffold(
  backgroundColor: Colors.grey[100],
  body: Flex(
    direction: Axis.horizontal,
    children: [
      // 左側操作欄
      MoveWindow(
        child: widget.activitybar,
        onDoubleTap: () => {},
      ),
      // 側邊欄
      Visibility(
        visible: widget.showSidebar,
        child: SizedBox(
          width: 270.0,
          child: Container(
            decoration: const BoxDecoration(
              gradient: LinearGradient(
                begin: Alignment.topLeft,
                end: Alignment.bottomRight,
                colors: [
                  Color(0xFFEEEBE7), Color(0xFFEEEEEE)
                ]
              ),
            ),
            child: widget.sidebar,
          ),
        ),
      ),
      // 主體容器
      Expanded(
        child: Column(
          children: [
            WindowTitleBarBox(
              child: Row(
                children: [
                  Expanded(
                    child: MoveWindow(),
                  ),
                  // 右上角操作按鈕組
                  Winbtn(
                    leading: Row(
                      children: [
                        IconButton(onPressed: () {}, icon: const Icon(Icons.auto_fix_high), iconSize: 14.0,),
                        IconButton(
                          onPressed: () {
                            setState(() {
                              winTopMost = !winTopMost;
                            });
                          },
                          tooltip: winTopMost ? '取消置頂' : '置頂',
                          icon: const Icon(Icons.push_pin_outlined),
                          iconSize: 14.0,
                          highlightColor: Colors.transparent, // 點擊水波紋顏色
                          isSelected: winTopMost ? true : false, // 是否選中
                          style: ButtonStyle(
                            visualDensity: VisualDensity.compact,
                            backgroundColor: MaterialStateProperty.all(winTopMost ? Colors.grey[300] : Colors.transparent),
                            shape: MaterialStatePropertyAll(
                              RoundedRectangleBorder(borderRadius: BorderRadius.circular(0.0))
                            ),
                          ),
                        ),
                      ],
                    ),
                  ),
                ],
              ),
            ),
            // 右側工作面板
            Expanded(
              child: Container(
                child: widget.workbench,
              ),
            ),
          ],
        ),
      ),
    ],
  ),
);

左側Tab切換操作欄,使用 NavigationRail 組件實現功能。該組件支持自定義頭部和尾部組件。

@override
Widget build(BuildContext context) {
  return Container(
    width: 54.0,
    decoration: const BoxDecoration(
      color: Color(0xFF2E2E2E),
    ),
    child: NavigationRail(
      backgroundColor: Colors.transparent,
      labelType: NavigationRailLabelType.none, // all 顯示圖標+標簽 selected 只顯示激活圖標+標簽 none 不顯示標簽
      indicatorColor: Colors.transparent, // 去掉選中橢圓背景
      indicatorShape: RoundedRectangleBorder(
        borderRadius: BorderRadius.circular(0.0),
      ),
      unselectedIconTheme: const IconThemeData(color: Color(0xFF979797), size: 24.0),
      selectedIconTheme: const IconThemeData(color: Color(0xFF07C160), size: 24.0,),
      unselectedLabelTextStyle: const TextStyle(color: Color(0xFF979797),),
      selectedLabelTextStyle: const TextStyle(color: Color(0xFF07C160),),
      // 頭部(圖像)
      leading: GestureDetector(
        onPanStart: (details) => {},
        child: Container(
          margin: const EdgeInsets.only(top: 30.0, bottom: 10.0),
          child: InkWell(
            child: Image.asset('assets/images/avatar/uimg1.jpg', height: 36.0, width: 36.0,),
            onTapDown: (TapDownDetails details) {
              cardDX = details.globalPosition.dx;
              cardDY = details.globalPosition.dy;
            },
            onTap: () {
              showCardDialog(context);
            },
          ),
        ),
      ),
      // 尾部(鏈接)
      trailing: Expanded(
        child: Container(
          margin: const EdgeInsets.only(bottom: 10.0),
          child: GestureDetector(
            onPanStart: (details) => {},
            child: Column(
              mainAxisAlignment: MainAxisAlignment.end,
              children: [
                IconButton(icon: Icon(Icons.info_outline, color: Color(0xFF979797), size: 24.0), onPressed:(){showAboutDialog(context);}),
                PopupMenuButton(
                  icon: const Icon(Icons.menu, color: Color(0xFF979797), size: 24.0,),
                  offset: const Offset(54.0, 0.0),
                  tooltip: '',
                  color: const Color(0xFF353535),
                  surfaceTintColor: Colors.transparent,
                  shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(0.0)),
                  padding: EdgeInsets.zero,
                  itemBuilder: (BuildContext context) {
                    return <PopupMenuItem>[
                      popupMenuItem('我的私密空間', 0),
                      popupMenuItem('鎖定', 1),
                      popupMenuItem('意見反饋', 2),
                      popupMenuItem('設置', 3),
                    ];
                  },
                  onSelected: (value) {
                    switch(value) {
                      case 0:
                        Get.toNamed('/my');
                        break;
                      case 3:
                        Get.toNamed('/setting');
                        break;
                    }
                  },
                ),
              ],
            ),
          ),
        ),
      ),
      selectedIndex: tabCur,
      destinations: [
        ...tabNavs
      ],
      onDestinationSelected: (index) {
        setState(() {
          tabCur = index;
          if(tabRoute[index] != null && tabRoute[index]?['path'] != null) {
            Get.toNamed(tabRoute[index]['path']);
          }
        });
      },
    ),
  );
}

Flutter3朋友圈功能

@override
Widget build(BuildContext context) {
  return Layout(
    showSidebar: false,
    workbench: CustomScrollView(
      slivers: [
        SliverAppBar(
          backgroundColor: const Color(0xFF224E7F),
          foregroundColor: Colors.white,
          pinned: true,
          elevation: 0.0,
          expandedHeight: 200.0,
          leading: IconButton(icon: const Icon(Icons.arrow_back,), onPressed: () {Navigator.pop(context);}),
          flexibleSpace: FlexibleSpaceBar(
            title: Row(
              children: <Widget>[
                ClipOval(child: Image.asset('assets/images/avatar/uimg1.jpg',height: 36.0,width: 36.0,fit: BoxFit.fill)),
                const SizedBox(width: 10.0),
                const Text('Andy', style: TextStyle(fontSize: 14.0)),
              ],
            ),
            titlePadding: const EdgeInsets.fromLTRB(55, 10, 10, 10),
            background: InkWell(
              child: Image.asset('assets/images/cover.jpg', fit: BoxFit.cover),
              onTap: () {changePhotoAlbum(context);},
            ),
          ),
          actions: <Widget>[
            IconButton(icon: const Icon(Icons.favorite_border, size: 18,), onPressed: () {}),
            IconButton(icon: const Icon(Icons.share, size: 18,), onPressed: () {}),
            IconButton(icon: const Icon(Icons.add_a_photo, size: 18,), onPressed: () {Get.toNamed('/publish');}),
            const SizedBox(width: 10.0,),
          ],
        ),
        SliverToBoxAdapter(
          child: UnconstrainedBox(
            child: Container(
              width: MediaQuery.of(context).size.height * 3 / 4,
              decoration: const BoxDecoration(
                color: Colors.white,
              ),
              child: Column(
                children: uzoneList.map((item) {
                  return Container(
                    padding: const EdgeInsets.all(15.0),
                    decoration: const BoxDecoration(
                      border: Border(bottom: BorderSide(color: Color(0xFFEEEEEE), width: .5)),
                    ),
                    child: Row(
                      crossAxisAlignment: CrossAxisAlignment.start,
                      children: <Widget>[
                        Image.asset(item['avatar'],height: 35.0,width: 35.0,fit: BoxFit.cover),
                        const SizedBox(width: 10.0),
                        Expanded(
                          child: Column(
                            crossAxisAlignment: CrossAxisAlignment.start,
                            children: <Widget>[
                              Text(item['author'], style: TextStyle(color: Colors.indigo[400])),
                              const SizedBox(height: 2.0),
                              Text(item['content'], style: const TextStyle(color: Colors.black87, fontSize: 15.0)),
                              const SizedBox(height: 10.0),
                              GroupZone(images: item['images']),
                              const SizedBox(height: 10.0),
                              Row(
                                children: <Widget>[
                                  Expanded(child: Text(item['time'], style: const TextStyle(color: Colors.grey, fontSize: 12.0)),),
                                  FStyle.iconfont(0xe653, color: Colors.black54, size: 16.0,),
                                ],
                              )
                            ],
                          ),
                        ),
                      ],
                    ),
                  );
                }).toList(),
              ),
            ),
          ),
        ),
      ],
    ),
  );
}

圖片排列類似微信朋友圈九宮格,支持點擊大圖預覽。

Flutter3短視頻模塊

使用media_kit插件整合進了短視頻功能,支持點擊播放/暫停,上下滑動功能。

底部mini時間進度條是自定義組件實現功能效果。

// flutter3短視頻模板  Q:282310962

Container(
  width: MediaQuery.of(context).size.height * 9 / 16,
  decoration: const BoxDecoration(
    color: Colors.black,
  ),
  child: Stack(
    children: [
      // Swiper垂直滾動區域
      PageView(
        // 自定義滾動行為(支持桌面端滑動、去掉滾動條槽)
        scrollBehavior: SwiperScrollBehavior().copyWith(scrollbars: false),
        scrollDirection: Axis.vertical,
        controller: pageController,
        onPageChanged: (index) {
          // 暫停(垂直滑動)
          controller.player.pause();
        },
        children: [
          Stack(
            children: [
              // 視頻區域
              Positioned(
                top: 0,
                left: 0,
                right: 0,
                bottom: 0,
                child: GestureDetector(
                  child: Stack(
                    children: [
                      // 短視頻插件
                      Video(
                        controller: controller,
                        fit: BoxFit.cover,
                        // 無控制條
                        controls: NoVideoControls,
                      ),
                      // 播放/暫停按鈕
                      Center(
                        child: IconButton(
                          onPressed: () {
                            controller.player.playOrPause();
                          },
                          icon: StreamBuilder(
                            stream: controller.player.stream.playing,
                            builder: (context, playing) {
                              return Visibility(
                                visible: playing.data == false,
                                child: Icon(
                                  playing.data == true ? Icons.pause : Icons.play_arrow_rounded,
                                  color: Colors.white70,
                                  size: 50,
                                ),
                              );
                            },
                          ),
                        ),
                      ),
                    ],
                  ),
                  onTap: () {
                    controller.player.playOrPause();
                  },
                ),
              ),
              // 右側操作欄
              Positioned(
                bottom: 70.0,
                right: 10.0,
                child: Column(
                  children: [
                    // ...
                  ],
                ),
              ),
              // 底部信息區域
              Positioned(
                bottom: 30.0,
                left: 15.0,
                right: 80.0,
                child: Column(
                  crossAxisAlignment: CrossAxisAlignment.start,
                  children: [
                    	   

您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • 本操作在虛擬機上 安裝Redis 1)更新系統 sudo apt update sudo apt upgrade 2)安裝Redis sudo apt install redis-server 3)測試Redis是否工作 redis-cli --version systemctl status re ...
  • 指標是什麼? 業務發展過程中,企業內外部都會產生很多的業務數據,對這些數據進行採集、計算、落庫、分析後,形成的統計結果稱為指標。簡單來說,指標是業務被拆解、量化後形成的數量特征,企業利用數據指標對業務進行精準的號脈,實現對業務的科學管理和有效優化。 在我們對多家企業展開深入調研的過程中,發現數據指標 ...
  • 2024年2月27日,在“2024年世界移動通信大會”(Mobile World Congress 2024,簡稱MWC 2024)上,以“雲原生×AI,躍遷新機遇”為主題的創原會圓桌成功舉辦。會上,全球企業技術精英面對面交流,圍繞雲原生×AI技術變革,分享企業在架構、算力、存儲、數智、應用開發、媒 ...
  • 在大數據處理領域,Apache SeaTunnel 已成為一款備受青睞的開源數據集成平臺,它不僅可以基於Apache Spark和Flink,而且還有社區單獨開發專屬數據集成的Zeta引擎,提供了強大的數據處理能力。隨著SeaTunnel Web的推出,用戶界面(UI)操作變得更加友好,項目部署和管 ...
  • 當用戶需要的計算或者存儲資源冗餘超出業務需求時,可在管理控制台對已有集群進行縮容操作,以便充分利用GaussDB(DWS) 提供的計算資源和存儲資源。 ...
  • Android 修改系統息屏時間. 本篇文章主要記錄下android 如何修改手機息屏時間. 目前手機屏幕超時的時間範圍一般是: 15秒 30秒 1分鐘 2分鐘 5分鐘 10分鐘 30分鐘 那如何設置超過30分鐘呢? 代碼很簡單,如下: private void changeScreenOffTim ...
  • 兩個常用的組件:Material和Scaffold修飾App和H5一樣很固定。 1.Container 2.Text 3.picture import 'package:flutter/material.dart'; void main() { runApp(MaterialApp( home: S ...
  • 本文基於Glide 4.11.0 Glide載入過程有一個解碼過程,比如將url載入為inputStream後,要將inputStream解碼為Bitmap。 從Glide源碼解析一我們大致知道了Glide載入的過程,所以我們可以直接從這裡看起,在這個過程中我們以從文件中載入bitmap為例: De ...
一周排行
    -Advertisement-
    Play Games
  • 移動開發(一):使用.NET MAUI開發第一個安卓APP 對於工作多年的C#程式員來說,近來想嘗試開發一款安卓APP,考慮了很久最終選擇使用.NET MAUI這個微軟官方的框架來嘗試體驗開發安卓APP,畢竟是使用Visual Studio開發工具,使用起來也比較的順手,結合微軟官方的教程進行了安卓 ...
  • 前言 QuestPDF 是一個開源 .NET 庫,用於生成 PDF 文檔。使用了C# Fluent API方式可簡化開發、減少錯誤並提高工作效率。利用它可以輕鬆生成 PDF 報告、發票、導出文件等。 項目介紹 QuestPDF 是一個革命性的開源 .NET 庫,它徹底改變了我們生成 PDF 文檔的方 ...
  • 項目地址 項目後端地址: https://github.com/ZyPLJ/ZYTteeHole 項目前端頁面地址: ZyPLJ/TreeHoleVue (github.com) https://github.com/ZyPLJ/TreeHoleVue 目前項目測試訪問地址: http://tree ...
  • 話不多說,直接開乾 一.下載 1.官方鏈接下載: https://www.microsoft.com/zh-cn/sql-server/sql-server-downloads 2.在下載目錄中找到下麵這個小的安裝包 SQL2022-SSEI-Dev.exe,運行開始下載SQL server; 二. ...
  • 前言 隨著物聯網(IoT)技術的迅猛發展,MQTT(消息隊列遙測傳輸)協議憑藉其輕量級和高效性,已成為眾多物聯網應用的首選通信標準。 MQTTnet 作為一個高性能的 .NET 開源庫,為 .NET 平臺上的 MQTT 客戶端與伺服器開發提供了強大的支持。 本文將全面介紹 MQTTnet 的核心功能 ...
  • Serilog支持多種接收器用於日誌存儲,增強器用於添加屬性,LogContext管理動態屬性,支持多種輸出格式包括純文本、JSON及ExpressionTemplate。還提供了自定義格式化選項,適用於不同需求。 ...
  • 目錄簡介獲取 HTML 文檔解析 HTML 文檔測試參考文章 簡介 動態內容網站使用 JavaScript 腳本動態檢索和渲染數據,爬取信息時需要模擬瀏覽器行為,否則獲取到的源碼基本是空的。 本文使用的爬取步驟如下: 使用 Selenium 獲取渲染後的 HTML 文檔 使用 HtmlAgility ...
  • 1.前言 什麼是熱更新 游戲或者軟體更新時,無需重新下載客戶端進行安裝,而是在應用程式啟動的情況下,在內部進行資源或者代碼更新 Unity目前常用熱更新解決方案 HybridCLR,Xlua,ILRuntime等 Unity目前常用資源管理解決方案 AssetBundles,Addressable, ...
  • 本文章主要是在C# ASP.NET Core Web API框架實現向手機發送驗證碼簡訊功能。這裡我選擇是一個互億無線簡訊驗證碼平臺,其實像阿裡雲,騰訊雲上面也可以。 首先我們先去 互億無線 https://www.ihuyi.com/api/sms.html 去註冊一個賬號 註冊完成賬號後,它會送 ...
  • 通過以下方式可以高效,並保證數據同步的可靠性 1.API設計 使用RESTful設計,確保API端點明確,並使用適當的HTTP方法(如POST用於創建,PUT用於更新)。 設計清晰的請求和響應模型,以確保客戶端能夠理解預期格式。 2.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...