likes
comments
collection
share

FlutterUnit 桌面分支合并,一套代码 - 五端通行

作者站长头像
站长
· 阅读数 23

一、前言

FlutterUnit 虽然支持六端,但分为了三个分支:移动端和桌面端以及 web 端。这是由于历史遗留问题,起初 Flutter 稳定版 SDK 是不支持桌面开发,需要 master 版本的 SDK,在那时 FlutterUnit 就已经开始支持桌面版。为了让移动端在 稳定版 SDK 上开发符合大多数人的场景,所以选择新建分支让桌面端用 master 版本的 SDK 尝鲜体验。

FlutterUnit 桌面分支合并,一套代码 - 五端通行

一直以来 FlutterUnit 偏重于移动端,桌面端和 web 端基本处于能跑就像的状态。不过最近 Flutter 桌面端也在逐渐发展,windowsmacOS 官方也已经宣布稳定支持。很多三方插件也支持了桌面版,越来越多的朋友开始向 Flutter 桌面端尝试,感觉也是时候将 桌面端移动端 的代码进行合并。顺便记录一下其中需要注意的要点。


想要让一个只有 Android/iOSFlutter 项目支持 windows ,只需要在项目根目录执行:

flutter create .

这样即可生成其他平台的源码文件,这里暂时不集成 web ,可以删掉。

FlutterUnit 桌面分支合并,一套代码 - 五端通行


二、SQLite 数据库的全平台支持

sqflite 目前已经支持了 Android iOS, 和 MacOS 平台;对 WindowsLinux 的支持,可以使用 sqflite_common_ffi

FlutterUnit 桌面分支合并,一套代码 - 五端通行

---->[pubspec.yaml]----
dependencies:
  #...
	sqflite: ^2.0.2+1 # 数据库
  sqflite_common_ffi: ^2.1.1 # 数据库

1. 关于数据库的路径

sqflite 中有一个 getDatabasesPath 的方法,用于获取数据库文件夹路径:

Android:  data/data/<package_name>/databases
iOS/MacOS: 应用 Documents 文件夹

该方法只支持 Android/iOS/MacOS ,在 windows/Linux 上不支持。

FlutterUnit 桌面分支合并,一套代码 - 五端通行


目前 path_provider 已经支持了五个平台,

FlutterUnit 桌面分支合并,一套代码 - 五端通行

所以我们可以不使用 sqflite#getDatabasesPath 方法,直接用 path_provider 确定路径即可。如下是 path_provider 相关路径支持的情况,这里选用 Application Documents 文件夹:

FlutterUnit 桌面分支合并,一套代码 - 五端通行

Directory appDocDir = await getApplicationDocumentsDirectory();
String appDocPath = appDocDir.path;
print(appDocPath);

|--- macos: /Users/mac/Library/Containers/<package_name>/Data/Documents
|--- windows: C:\Users\Administrator\Documents
|--- Android: /data/data/<package_name>/app_flutter

下面根据不同平台的路径,简单封装一个 getDbDirPath 静态方法来辅助获取数据库路径。大家可以根据自己的喜欢来设置文件夹:

class DbOpenHelper{
  
  static Future<String> 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. 打开数据库

由于 windowslinux 使用的是 sqflite_common_ffi 所以开启数据库的方式不同。对于 windows 而言,需要在项目根目录添加一个 sqlite3.dll 文件。打包后也需要将这个 dll 放在根目录下,才能支持 sqlite

FlutterUnit 桌面分支合并,一套代码 - 五端通行

如下代码也放在 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();
}

在初始化数据库是,对 windowslinux 使用 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 中可以选择对应的对应的桌面设备来运行:

FlutterUnit 桌面分支合并,一套代码 - 五端通行


1. 运行表现

由于目前我只有 windowsmacOS 的设备,所以下面看一下目前在这两端运行的表现。

  • windows 的表现:

FlutterUnit 桌面分支合并,一套代码 - 五端通行

  • macOS 的表现:

FlutterUnit 桌面分支合并,一套代码 - 五端通行

其实这也不出所料,毕竟这里还是移动端的布局,只不过强行拉成了横向布局。所以接下来的任务是如何对桌面端的布局结构进行优化。因为之前再 desk 分支已经写过了一套桌面端布局,先简单适配一下。


2. 设置窗口大小

不同桌面默认的大小不同,可以使用 desktop_window 插件来控制桌面端窗口尺寸。

FlutterUnit 桌面分支合并,一套代码 - 五端通行

---->[pubspec.yaml]----
dependencies:
  #...
	desktop_window: ^0.4.0 #桌面尺寸

这里目前先用 800*600 的固定宽度,不支持窗口缩放。把最小尺寸、最大尺寸和窗口尺寸设置一致即可。后面有时间再对窗口尺寸变化的布局进行适配。

class WindowSizeHelper{

  static Future<void> 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);
    }
  }
}

这样 macOSwindows 在尺寸方面就一致了:

  • macOS 的表现:

FlutterUnit 桌面分支合并,一套代码 - 五端通行

  • windows 的表现:

FlutterUnit 桌面分支合并,一套代码 - 五端通行


四、布局的适配

对于多态的布局适配来说,没有必要强求一个组件能在所有平台的能适配。对于有些界面差距非常大的,可以给出桌面和移动端两套 UI 。就像要求一件衣服要同时适配 蚂蚁燕子 一样,两个外形表现差别很大,不如各自一件衣服。另外这样也更容易分工,现实中可以让桌面端的 UI 实现交给不同的人实现,毕竟要支持桌面端,就注定有人要多干活。

对于一些差别不太大的界面,可以在构件时进行适配。你也可以自己打造一个 平台通用组件库 ,其中的组件可以根据平台,或父级约束尺寸来主动调节自身的布局行为,对常用的适配界面进行封装,以便复用。

FlutterUnit 桌面分支合并,一套代码 - 五端通行

让一个项目同时支持多端的好处在于 业务逻辑 可以共用,这时候使用状态管理,分离视图和业务层次的优势就可以体现出来了。虽然 Flutter 可以支持多平台,实现了 统一 ,但我并不认为这表示一个人要做所有的工作。视图层业务逻辑 完全可以交由不同的人或小组进行开发,毕竟合理分工很重要。一个人把所有的东西都写了,然后工资还是那些,平白无故多干活,也是不现实的。


1. 导航栏适配

先看一下导航栏如何适配,达到如下的效果。桌面端由于宽度大,一般都有左侧的导航。这两个布局的差异比较大,可以用两个不同的组件来维护:

桌面端移动端
FlutterUnit 桌面分支合并,一套代码 - 五端通行FlutterUnit 桌面分支合并,一套代码 - 五端通行

如下 UnitNavigation 中,可以通过 LayoutBuilder 来根据约束的宽度值来构建不同的组件。比如大于 500 时,使用 UnitDeskNavigation 组件,否则使用 UnitPhoneNavigation 组件。Flutter 在界面上的的优势在于组件化,任何 UI 的构成部分都可以看做一个独立的 ,随用随放,像拼图一样,拼出你期望的界面。

FlutterUnit 桌面分支合并,一套代码 - 五端通行

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 即可:

桌面端移动端
FlutterUnit 桌面分支合并,一套代码 - 五端通行FlutterUnit 桌面分支合并,一套代码 - 五端通行

3. 绘制集录的优化

不同的地域有着其不同的 风俗 ,不同的平台也是如此,有些界面布局就是适合在宽度较窄的屏幕上。像绘制集录的界面是移动端特有的样式,桌面端再怎么强行适配也有种 削足适履 的感觉。有些场景没必要追求 UI 显示的一致性。

移动端桌面端
FlutterUnit 桌面分支合并,一套代码 - 五端通行FlutterUnit 桌面分支合并,一套代码 - 五端通行

FlutterUnit 桌面分支合并,一套代码 - 五端通行


Flutter 对于多平台的支持,为了对于设计师、还是开发者、还是产品本身都是一个挑战。毕竟通过写 dart 代码,编译成各平台的软件,本身就是一种 奇迹Flutter 在桌面端已经完成了从 01 的质变,接下来只要累积量变,完善社区生态,未来可期。目前 Flutter 对于桌面端,非常适合一些工具软件的开发,或者依赖于网络、数据库的展示类型的软件。

比如下面是我基于 AndroidStudio 界面使用 Flutter 打造的正则匹配应用。Flutter 对于界面的塑形能力是非常强大的,这也是我钟爱 Flutter 的原因。

FlutterUnit 桌面分支合并,一套代码 - 五端通行

FlutterUnit 核心的界面就适配到这里,后面的小细节以后慢慢改。现在主分支已经支持五个平台了。flutter_unit_desk 分支也完成了它的使命,退出历史舞台,那本文就到这里,如果对你有所帮助,欢迎点赞支持 ~