flutter3_osx:基于flutter3.22+getx手机桌面os管理系统

4,425 阅读4分钟

历经半个月潜心研发,又一个原创跨平台新项目flutter3-os手机os系统正式完结了。

未标题-2.png

flutter3_osx基于最新跨端技术flutter3.22.1开发手机端仿ios管理系统项目。

未标题-1.png

实现了自研手机桌面栅格布局引擎、桌面多屏滑动管理、自定义壁纸/小部件/Dock菜单、可自由拖拽式悬浮球菜单。

p2.gif

运用技术

  • 编辑器:VScode
  • 技术框架:Flutter3.22.1+Dart3.4.1
  • 路由/状态管理:get^4.6.6
  • 本地存储:get_storage^2.1.1
  • svg图片插件:flutter_svg^2.0.10+1
  • 图表组件:fl_chart^0.68.0
  • 国际化时间:intl^0.19.0

pc1.gif

如上图:在windows端也能完美运行。

p3.gif

前段时间也有分享一款flutter3仿macOS桌面管理系统,感兴趣的也可以去看看。 juejin.cn/post/735686…

项目结构

采用最新版flutter3.22搭建项目,目录结构非常清晰明了。

360截图20240604234342427.png

旨在通过开发这个项目,探索flutter手机端OA管理系统新模式。

p1-1.gif

入口文件main.js

import 'dart:io';
import 'package:flutter/material.dart';
import 'package:get/get.dart';
import 'package:get_storage/get_storage.dart';
import 'package:intl/date_symbol_data_local.dart';

import 'utils/index.dart';

// 引入桌面栅格模板
import 'layouts/desk.dart';

// 引入路由管理
import 'router/index.dart';

void main() async {
  // 初始化get_storage本地存储
  await GetStorage.init();
  // 初始化国际化语言
  initializeDateFormatting();

  runApp(const MyApp());
}

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

  // This widget is the root of your application.
  @override
  Widget build(BuildContext context) {
    return GetMaterialApp(
      title: 'Flutter WeOS',
      debugShowCheckedModeBanner: false,
      theme: ThemeData(
        colorScheme: ColorScheme.fromSeed(seedColor: Colors.blue),
        useMaterial3: true,
        // 修复windows端字体粗细不一致
        fontFamily: Platform.isWindows ? 'Microsoft YaHei' : null,
      ),
      home: const DeskLayout(),
      // 初始化路由
      initialRoute: Utils.isLogin() ? '/' : '/launch',
      // 路由页面
      getPages: routes,
    );
  }
}

p3.gif

目前这个项目已经同步发布到我的原创作品集,感兴趣的可以去瞧瞧~ gf.bilibili.com/item/detail…

p4.gif

flutter3实现数字密码解锁

采用全新的上滑数字密码解锁模式。使用 AnimatedSwitcher 和 FadeTransition 配合实现切换动画效果。

001360截图20240604224017952.png

002360截图20240604224117326.png

ae5dd9078db4f73e2c2bc9a8409eceef_1289798-20240606074856084-1427905882.png

@override
Widget build(BuildContext context) {
  return Layout(
    extendBodyBehindAppBar: true,
    body: Container(
      padding: const EdgeInsets.all(20.0),
      child: AnimatedSwitcher(
        duration: const Duration(milliseconds: 250),
        // 动画控制
        transitionBuilder: (child, animation) {
          return FadeTransition(
            opacity: animation,
            child: ScaleTransition(
              // scale: animation,
              scale: animation.drive(Tween(begin: 0.9, end: 1.0).chain(CurveTween(curve: Curves.easeOut))),
              child: child,
            ),
          );
        },
        // 当内容有变化的时候就会触发动画
        child: splashScreen ? GestureDetector(
          // 修复Column和Row组件,点击空白处无响应问题
          behavior: HitTestBehavior.translucent,
          child: Column(
            children: [
              ...
            ],
          ),
          onPanStart: (details) {
            setState(() {
              swipeY = details.globalPosition.dy;
            });
          },
          onPanUpdate: (details) {
            double posY = swipeY - details.globalPosition.dy;
            if(posY > 100) {
              setState(() {
                splashScreen = false;
              });
            }
          },
        )
        :
        Center(
          child: Column(
            mainAxisAlignment: MainAxisAlignment.center,
            children: [
              ...
            ],
          ),
        ),
      ),
    ),
  );
}

数字密码长度可随意定义,默认是6个。

5ed3d3fcfb179c8b43021e1f75704120_1289798-20240606075805306-666523875.png

Column(
  children: [
    const Text('数字密码解锁', style: TextStyle(color: Colors.white, fontSize: 14.0),),
    const SizedBox(height: 10.0,),
    Wrap(
      spacing: 15.0,
      children: List.generate(passwordArr.length, (index) {
        return AnimatedContainer(
          duration: const Duration(milliseconds: 300),
          height: 10.0,
          width: 10.0,
          decoration: BoxDecoration(
            color: int.parse(passwordArr[index]) <= pwdValue.length ? Colors.white : Colors.white.withOpacity(0.01),
            border: Border.all(color: Colors.white),
            borderRadius: BorderRadius.circular(50.0),
          ),
        );
      })
    ),
  ],
),

1ad7c8b32450caff35959a2a59639a87_1289798-20240606080044662-455011205.png

Container(
  width: 250.0,
  margin: const EdgeInsets.only(top: 50.0),
  child: Wrap(
    spacing: 15.0,
    runSpacing: 15.0,
    alignment: WrapAlignment.center,
    children: List.generate(keyNumbers.length, (index) {
      return Material(
        type: MaterialType.transparency,
        child: Ink(
          height: 60.0,
          width: 60.0,
          decoration: BoxDecoration(
            color: Colors.white24,
            border: Border.all(color: Colors.white24, width: .5),
            borderRadius: BorderRadius.circular(50.0),
          ),
          child: InkWell(
            borderRadius: BorderRadius.circular(50.0),
            overlayColor: WidgetStateProperty.all(Colors.white38),
            child: DefaultTextStyle(
              style: const TextStyle(color: Colors.white, fontFamily: 'arial'),
              child: Column(
                children: [
                  const SizedBox(height: 10.0,),
                  Text(keyNumbers[index]['num'], style: const TextStyle(fontSize: 20.0, fontWeight: FontWeight.bold),),
                  Text(keyNumbers[index]['letter'], style: const TextStyle(fontSize: 10.0),),
                ],
              ),
            ),
            onTap: () {
              handleClickNum(keyNumbers[index]['num']);
            },
          ),
        ),
      );
    })
  ),
),

004360截图20240604224217381.png

005360截图20240604224258902.png

006360截图20240604225403575.png

007360截图20240604224420038.png

008360截图20240604224435542.png

009360截图20240604224744903.png

011360截图20240604224838287.png

013360截图20240604224912846.png

016360截图20240604225009087.png

015360截图20240604224956286.png

018360截图20240604225112431.png

023360截图20240605000059085.png

031360截图20240605001459720.png

024360截图20240605000124149.png

flutter3手机桌面os栅格引擎

2e8cd255047b3b8b68e2956f3415fa7c_1289798-20240606081914932-1676024752.png

d06a2855b7275408a0591fd5a778fbed_1289798-20240606082027855-1104647357.png

/*
 * ================== 桌面os菜单配置项 ==================
 * [label]  图标标题
 * [imgico] 图标(本地或网络图片) 当type: 'icon'则为uni-icons图标名,当type: 'widget'则为自定义小部件标识名
 * [type]   图标类型(icon | widget) icon为uni-icons图标、widget为自定义小部件
 * [path]   跳转路由页面
 * [link]   跳转外部链接
 * [hideLabel]  是否隐藏图标标题
 * [background] 自定义图标背景色
 * [size] 栅格磁贴布局(16种) 1x1 1x2 1x3 1x4、2x1 2x2 2x3 2x4、3x1 3x2 3x3 3x4、4x1 4x2 4x3 4x4
 * [onClick]  点击图标回调函数
 */

ce73bd3226d9e402051214b6e0c07380_1289798-20240606082311540-7370266.png

支持配置children二级弹窗菜单。通过Hero组件实现二级菜单弹窗动画效果。

b737744270095c5242bb94da77224980_1289798-20240606082424160-2048214472.png

93d698cc1cd2da0c406c0478fb1d9a69_1289798-20240606082618748-919425520.png

@override
Widget build(BuildContext context) {
  return Column(
    children: [
      Expanded(
        // 水平轮播
        child: PageView(
          scrollBehavior: PageScrollBehavior().copyWith(scrollbars: false),
          scrollDirection: Axis.horizontal,
          controller: pageController,
          onPageChanged: (index) {
            setState(() {
              deskIndex = index;
            });
          },
          children: List.generate(deskMenus.length, (mindex) {
            final mitem = deskMenus[mindex];
            final mlist = mitem['list'];
            return ListView(
              children: [
                Container(
                  alignment: Alignment.topCenter,
                  padding: const EdgeInsets.symmetric(horizontal: 10.0, vertical: 20.0),
                  /// 栅格磁贴布局引擎
                  child: Wrap(
                    spacing: 25.0,
                    runSpacing: 45.0,
                    children: List.generate(mlist.length, (index) {
                      final item = mlist[index];
                      return Stack(
                        alignment: Alignment.bottomCenter,
                        children: [
                          // 图标
                          Hero(
                            tag: item['label'],
                            child: GestureDetector(
                              child: Container(
                                ...
                              ),
                              onTap: () {
                                handleClickDeskMenu(item);
                              },
                            ),
                          ),
                          // 标签
                          Visibility(
                            visible: item!['hideLabel'] == null,
                            child: Positioned(
                              ...
                            ),
                          ),
                        ],
                      );
                    }),
                  ),
                ),
              ],
            );
          }),
        ),
      ),
      // 指示点
      Container(
        padding: const EdgeInsets.all(10.0),
        child: Wrap(
          ...
        ),
      ),
    ],
  );
}
// 手机桌面os栅格菜单配置 Q:282310962
List deskMenus = [
  ...
  {
    'uid': '3u85fb90-12c4-11e1-840d-7b25c5ee775a',
    'list': [
      {'label': 'Flutter3.22', 'imgico': 'assets/images/flutter.png', 'link': 'https://flutter.dev/'},
      {'label': 'Dart中文官方文档', 'imgico': 'assets/images/dart.png', 'link': 'https://dart.cn/'},
      ...
      {'label': '日历', 'imgico': const Calendar1x1(), 'type': 'widget', 'path': '/calendar', 'background': const Color(0xffffffff),},
      {'label': '首页', 'imgico': const Icon(Icons.home_outlined), 'type': 'icon', 'path': '/home'},
      {'label': '工作台', 'imgico': const Icon(Icons.poll_outlined), 'type': 'icon', 'path': '/workplace'},
      {
        'label': '组件',
        'children': [
          {'label': '组件', 'imgico': 'assets/images/svg/component.svg', 'path': '/component'},
          ...
        ]
      },
      {
        'label': '管理中心',
        'children': [
          {'label': '个人主页', 'imgico': 'assets/images/svg/my.svg', 'path': '/ucenter'},
          ...
        ]
      },
      {
        'label': '编程开发',
        'children': [
          {'label': 'Github', 'imgico': 'assets/images/svg/github.svg', 'background': const Color(0xff607d8b),},
          {'label': 'Flutter', 'imgico': 'assets/images/flutter.png', 'background': const Color(0xFFDAF2FA),},
          {'label': 'ChatGPT', 'imgico': 'assets/images/svg/chatgpt.svg', 'background': const Color(0xFF15A17F),},
          ...
        ]
      },
      {
        'label': '关于', 'imgico': const Icon(Icons.info), 'type': 'icon',
        'onClick': () => {
          ...
        }
      },
      {
        'label': '公众号', 'imgico': const Icon(Icons.qr_code), 'type': 'icon',
        'onClick': () => {
          ...
        }
      },
    ]
  }
  ...
];

pc2.gif

计算栅格菜单尺寸。

Map calcGridSize(size) {
    List<String> sizeArr = size.split('x');
    // 图标尺寸
    double iconSize = 60.0;
    // 水平间距
    double gapX = 25.0;
    // 垂直间距
    double gapY = 45.0;

    int colNum = int.parse(sizeArr[0]);
    int rowNum = int.parse(sizeArr[1]);
    
    return {
      'width': iconSize * colNum + gapX * (colNum - 1),
      'height': iconSize * rowNum + gapY * (rowNum - 1),
    };
}

flutter-os项目涉及到的知识点还是非常多的,限于篇幅,就先分享到这里吧。

小伙伴们可以在该项目基础上拓展一些更有创意的功能,希望以上分享对大家有所帮助!

juejin.cn/post/731918…

juejin.cn/post/734954…

juejin.cn/post/736312…

juejin.cn/post/731918…

a8a5dc63jw1falkc05snfg206q046gli.gif