一、前言
FlutterUnit
虽然支持六端,但分为了三个分支:移动端和桌面端以及 web
端。这是由于历史遗留问题,起初 Flutter 稳定版 SDK
是不支持桌面开发,需要 master
版本的 SDK
,在那时 FlutterUnit
就已经开始支持桌面版。为了让移动端在 稳定版 SDK
上开发符合大多数人的场景,所以选择新建分支让桌面端用 master
版本的 SDK
尝鲜体验。
一直以来 FlutterUnit
偏重于移动端,桌面端和 web
端基本处于能跑就像的状态。不过最近 Flutter
桌面端也在逐渐发展,windows
和 macOS
官方也已经宣布稳定支持。很多三方插件也支持了桌面版,越来越多的朋友开始向 Flutter
桌面端尝试,感觉也是时候将 桌面端
和 移动端
的代码进行合并。顺便记录一下其中需要注意的要点。
想要让一个只有 Android/iOS
的 Flutter
项目支持 windows
,只需要在项目根目录执行:
flutter create .
这样即可生成其他平台的源码文件,这里暂时不集成 web
,可以删掉。
二、SQLite 数据库的全平台支持
sqflite
目前已经支持了 Android
、 iOS
, 和 MacOS
平台;对 Windows
和 Linux
的支持,可以使用 sqflite_common_ffi
---->[pubspec.yaml]----
dependencies:
#...
sqflite: ^2.0.2+1 # 数据库
sqflite_common_ffi: ^2.1.1 # 数据库
1. 关于数据库的路径
sqflite
中有一个 getDatabasesPath
的方法,用于获取数据库文件夹路径:
Android: data/data//databases
iOS/MacOS: 应用 Documents 文件夹
该方法只支持 Android/iOS/MacOS
,在 windows/Linux
上不支持。
目前 path_provider
已经支持了五个平台,
所以我们可以不使用 sqflite#getDatabasesPath
方法,直接用 path_provider
确定路径即可。如下是 path_provider
相关路径支持的情况,这里选用 Application Documents
文件夹:
Directory appDocDir = await getApplicationDocumentsDirectory();
String appDocPath = appDocDir.path;
print(appDocPath);
|--- macos: /Users/mac/Library/Containers//Data/Documents
|--- windows: C:\Users\Administrator\Documents
|--- Android: /data/data//app_flutter
下面根据不同平台的路径,简单封装一个 getDbDirPath
静态方法来辅助获取数据库路径。大家可以根据自己的喜欢来设置文件夹:
class DbOpenHelper{
static Future getDbDirPath() async{
Directory appDocDir = await getApplicationDocumentsDirectory();
String dirName = 'databases';
String dirPath = path.join(appDocDir.path, dirName);
if(Platform.isAndroid){
dirPath = path.join(appDocDir.parent.path, dirName);
}
if(Platform.isWindows||Platform.isLinux){
dirPath = path.join(appDocDir.path, 'FlutterUnit','databases');
}
Directory result = Directory(dirPath);
if(!result.existsSync()){
result.createSync(recursive: true);
}
return dirPath;
}
}
3. 打开数据库
由于 windows
和 linux
使用的是 sqflite_common_ffi
所以开启数据库的方式不同。对于 windows
而言,需要在项目根目录添加一个 sqlite3.dll
文件。打包后也需要将这个 dll
放在根目录下,才能支持 sqlite
。
如下代码也放在 DbOpenHelper
中,在程序开始是调用 setupDatabase
方法,为 windows
设置 sqlite3.dll
的加载文件夹:
---->[DbOpenHelper#setupDatabase]----
static void setupDatabase(){
if(Platform.isWindows){
String location = Directory.current.path;
_windowsInit(join(location, 'sqlite3.dll'));
}
}
static void _windowsInit(String path) {
open.overrideFor(OperatingSystem.windows, () {
try {
return DynamicLibrary.open(path);
} catch (e) {
stderr.writeln('Failed to load sqlite3.dll at $path');
rethrow;
}
});
sqlite3.openInMemory().dispose();
}
在初始化数据库是,对 windows
和 linux
使用 databaseFactory.openDatabase
进行开启数据库。其中 options
参数可指定数据库版本、以及开启、更新、创建的回调。
---->[LocalDb#initDb]----
if (Platform.isWindows||Platform.isLinux) {
DatabaseFactory databaseFactory = databaseFactoryFfi;
_database = await databaseFactory.openDatabase(
dbPath,
options: OpenDatabaseOptions(
// version: DbUpdater.VERSION,
// onCreate: _onCreate,
// onUpgrade: _onUpgrade,
// onOpen: _onOpen
),
);
}else{
_database = await openDatabase(dbPath);
}
到这里,数据库就准备完毕,现在手机端分支的代码,就可以在桌面端运行了。
三、运行项目与窗口优化
在 AndroidStudio
中可以选择对应的对应的桌面设备来运行:
1. 运行表现
由于目前我只有 windows
和 macOS
的设备,所以下面看一下目前在这两端运行的表现。
-
windows
的表现:
-
macOS
的表现:
其实这也不出所料,毕竟这里还是移动端的布局,只不过强行拉成了横向布局。所以接下来的任务是如何对桌面端的布局结构进行优化。因为之前再 desk
分支已经写过了一套桌面端布局,先简单适配一下。
2. 设置窗口大小
不同桌面默认的大小不同,可以使用 desktop_window
插件来控制桌面端窗口尺寸。
---->[pubspec.yaml]----
dependencies:
#...
desktop_window: ^0.4.0 #桌面尺寸
这里目前先用 800*600
的固定宽度,不支持窗口缩放。把最小尺寸、最大尺寸和窗口尺寸设置一致即可。后面有时间再对窗口尺寸变化的布局进行适配。
class WindowSizeHelper{
static Future setFixSize({Size size = const Size(800,600)}) async{
bool isDesk = Platform.isMacOS||Platform.isWindows||Platform.isLinux;
if(isDesk){
await DesktopWindow.setWindowSize(size);
await DesktopWindow.setMinWindowSize(size);
await DesktopWindow.setMaxWindowSize(size);
}
}
}
这样 macOS
和 windows
在尺寸方面就一致了:
-
macOS
的表现:
-
windows
的表现:
四、布局的适配
对于多态的布局适配来说,没有必要强求一个组件能在所有平台的能适配。对于有些界面差距非常大的,可以给出桌面和移动端两套 UI
。就像要求一件衣服要同时适配 蚂蚁
和 燕子
一样,两个外形表现差别很大,不如各自一件衣服。另外这样也更容易分工,现实中可以让桌面端的 UI
实现交给不同的人实现,毕竟要支持桌面端,就注定有人要多干活。
对于一些差别不太大的界面,可以在构件时进行适配。你也可以自己打造一个 平台通用组件库
,其中的组件可以根据平台,或父级约束尺寸来主动调节自身的布局行为,对常用的适配界面进行封装,以便复用。
让一个项目同时支持多端的好处在于 业务逻辑
可以共用,这时候使用状态管理,分离视图和业务层次的优势就可以体现出来了。虽然 Flutter
可以支持多平台,实现了 统一
,但我并不认为这表示一个人要做所有的工作。视图层
和 业务逻辑
完全可以交由不同的人或小组进行开发,毕竟合理分工很重要。一个人把所有的东西都写了,然后工资还是那些,平白无故多干活,也是不现实的。
1. 导航栏适配
先看一下导航栏如何适配,达到如下的效果。桌面端由于宽度大,一般都有左侧的导航。这两个布局的差异比较大,可以用两个不同的组件来维护:
桌面端 | 移动端 |
---|---|
|
|
如下 UnitNavigation
中,可以通过 LayoutBuilder
来根据约束的宽度值来构建不同的组件。比如大于 500
时,使用 UnitDeskNavigation
组件,否则使用 UnitPhoneNavigation
组件。Flutter
在界面上的的优势在于组件化,任何 UI
的构成部分都可以看做一个独立的 块
,随用随放,像拼图一样,拼出你期望的界面。
class UnitNavigation extends StatelessWidget {
const UnitNavigation({Key? key}) : super(key: key);
@override
Widget build(BuildContext context) {
return LayoutBuilder(builder: (_,c){
if(c.maxWidth>500){
return const UnitDeskNavigation();
}
return const UnitPhoneNavigation();
});
}
}
通过 LayoutBuilder
的好处在于:你可以精确地对尺寸变化进行感知,构建符合需求的界面。这相比于直接检测平台要灵活地多,比如桌面端的宽度被用户拉的很小,这时如果只是根据平台来返回不同的界面,不能达到界面的适应性变化。
2. 中间内容的适配
主页面可以使用 SliverGrid
构建滑动的网格,一行排 2
个,效果如下:
桌面端 | 移动端 |
---|---|
|
|
其中要注意的一点是:在 CustomScrollView
滑动体中,不能使用 LayoutBuilder
,取而代之的是 SliverLayoutBuilder
组件。可以通过约束中的 crossAxisExtent
获取滑动交叉轴,也就是这里的宽度。
return SliverLayoutBuilder(builder: (_,c){
if(c.crossAxisExtent>500){
return DeskWidgetContent(
items: items,
width: c.crossAxisExtent,
);
}
return PhoneWidgetContent(
items: items,
);
});
另外,收藏集录本身就是 SliverGrid
,所以只要根据支持,指定不同的 SliverGridDelegate
即可:
桌面端 | 移动端 |
---|---|
|
|
|
3. 绘制集录的优化
不同的地域有着其不同的 风俗
,不同的平台也是如此,有些界面布局就是适合在宽度较窄的屏幕上。像绘制集录的界面是移动端特有的样式,桌面端再怎么强行适配也有种 削足适履
的感觉。有些场景没必要追求 UI
显示的一致性。
移动端 | 桌面端 |
---|---|
|
|
Flutter
对于多平台的支持,为了对于设计师、还是开发者、还是产品本身都是一个挑战。毕竟通过写 dart
代码,编译成各平台的软件,本身就是一种 奇迹
。Flutter
在桌面端已经完成了从 0
到 1
的质变,接下来只要累积量变,完善社区生态,未来可期。目前 Flutter
对于桌面端,非常适合一些工具软件的开发,或者依赖于网络、数据库的展示类型的软件。
比如下面是我基于 AndroidStudio
界面使用 Flutter
打造的正则匹配应用。Flutter 对于界面的塑形能力是非常强大的,这也是我钟爱 Flutter
的原因。
FlutterUnit
核心的界面就适配到这里,后面的小细节以后慢慢改。现在主分支已经支持五个平台了。flutter_unit_desk
分支也完成了它的使命,退出历史舞台,那本文就到这里,如果对你有所帮助,欢迎点赞支持 ~
作者:张风捷特烈
链接:https://juejin.cn/post/7107033765997707294