历经半个月潜心研发,又一个原创跨平台新项目flutter3-os手机os系统正式完结了。
flutter3_osx基于最新跨端技术flutter3.22.1开发手机端仿ios管理系统项目。
实现了自研手机桌面栅格布局引擎、桌面多屏滑动管理、自定义壁纸/小部件/Dock菜单、可自由拖拽式悬浮球菜单。
运用技术
- 编辑器: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
如上图:在windows端也能完美运行。
前段时间也有分享一款flutter3仿macOS桌面管理系统,感兴趣的也可以去看看。 juejin.cn/post/735686…
项目结构
采用最新版flutter3.22搭建项目,目录结构非常清晰明了。
旨在通过开发这个项目,探索flutter手机端OA管理系统新模式。
入口文件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,
);
}
}
目前这个项目已经同步发布到我的原创作品集,感兴趣的可以去瞧瞧~ gf.bilibili.com/item/detail…
flutter3实现数字密码解锁
采用全新的上滑数字密码解锁模式。使用 AnimatedSwitcher 和 FadeTransition 配合实现切换动画效果。
@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个。
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),
),
);
})
),
],
),
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']);
},
),
),
);
})
),
),
flutter3手机桌面os栅格引擎
/*
* ================== 桌面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] 点击图标回调函数
*/
支持配置children二级弹窗菜单。通过Hero组件实现二级菜单弹窗动画效果。
@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': () => {
...
}
},
]
}
...
];
计算栅格菜单尺寸。
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项目涉及到的知识点还是非常多的,限于篇幅,就先分享到这里吧。
小伙伴们可以在该项目基础上拓展一些更有创意的功能,希望以上分享对大家有所帮助!