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

16,165 阅读8分钟

我正在参加跨端技术专题征文活动,详情查看:juejin.cn/post/710123…


一、前言

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

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


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

flutter create .

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


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

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

---->[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 上不支持。


目前 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/<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

如下代码也放在 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 中可以选择对应的对应的桌面设备来运行:


1. 运行表现

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

  • windows 的表现:

  • macOS 的表现:

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


2. 设置窗口大小

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

---->[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 的表现:

  • windows 的表现:


四、布局的适配

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

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

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


1. 导航栏适配

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

桌面端移动端
image-20220609071047786.png

如下 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 在桌面端已经完成了从 01 的质变,接下来只要累积量变,完善社区生态,未来可期。目前 Flutter 对于桌面端,非常适合一些工具软件的开发,或者依赖于网络、数据库的展示类型的软件。

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

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