返回

Linux 测试 Flutter 纵向模式:3种方法设置竖屏窗口

Linux

在 Linux 上用纵向模式测试 Flutter 应用?轻松搞定!

遇到的问题

搞 Flutter 开发,特别是写那种主要给手机用的 App,咱们通常都希望它竖着显示,对吧?就是高度大于宽度的纵向模式 (Portrait Mode)。在手机或者模拟器上跑 flutter run -d <your_device_id>,一切正常,App乖乖地按竖屏样式来。

可问题来了,有时候想在 Linux 开发机上快速跑一下看看效果,比如用 flutter run -d linux 命令。结果呢?它启动后是个标准的桌面窗口,横着的,宽度大于高度那种(Landscape)。这下麻烦了,如果你的 UI 是严格按手机竖屏设计的,在这种横屏窗口里测试起来,布局、比例啥的都可能对不上,挺影响开发效率和判断的。

可能你也试过用代码来强制设置方向,类似下面这样:

import 'dart:io';
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';

void main() {
  WidgetsFlutterBinding.ensureInitialized(); // 确保绑定已初始化

  // 尝试从环境变量读取方向,默认为 landscape
  // String orientation = Platform.environment['ORIENTATION'] ?? 'landscape';
  // 注意:这种方式在 Linux 桌面通常无效,下面会解释

  // 强制设置竖屏
  SystemChrome.setPreferredOrientations([
    DeviceOrientation.portraitUp,
    DeviceOrientation.portraitDown,
  ]).then((_) {
      // 在方向设置完成后运行 App
      runApp(MyApp());
  });

  // 如果你想强制横屏(虽然不是我们这次的目标)
  // SystemChrome.setPreferredOrientations([
  //   DeviceOrientation.landscapeLeft,
  //   DeviceOrientation.landscapeRight,
  // ]).then((_) {
  //     runApp(MyApp());
  // });
}

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      home: Scaffold(
        appBar: AppBar(title: Text('纵向模式测试')),
        body: Center(
          child: Text('这个 App 应该竖着显示!'),
        ),
      ),
    );
  }
}

你可能满怀期待地在 main 函数开头加上 SystemChrome.setPreferredOrientations,指定 DeviceOrientation.portraitUp,然后跑 flutter run -d linux。然而,窗口依旧我行我素,还是横着的。

这是咋回事?难道 Linux 桌面就不能模拟手机的竖屏模式来跑 Flutter 应用吗?有没有啥办法让它启动时就变成竖屏样式,方便咱们测试呢?

为什么会这样?

简单说,这主要是因为手机操作系统和桌面操作系统的窗口管理机制不一样。

在 Android 或 iOS 上,系统对设备的物理方向(用户怎么拿着手机)和应用期望的显示方向有很强的控制力。当你调用 SystemChrome.setPreferredOrientations 时,Flutter Engine 会通过平台通道告诉操作系统:“嘿,我这个 App 希望只在竖屏(或横屏)模式下运行”。操作系统收到这个请求后,通常会强制执行,调整应用的显示区域和方向。

但在 Linux 桌面环境里,情况就不同了。Linux 上的窗口行为主要由窗口管理器(Window Manager,比如 GNOME Mutter, KWin, Xfwm 等)负责。用户通常可以自由调整窗口的大小和形状。Flutter 的 Linux 桌面应用,本质上是跑在一个由 GTK+ (默认) 或其他图形库创建的标准窗口里。这个窗口启动时的大小和形状,通常是由应用本身设定一个初始默认值,或者由窗口管理器根据用户上次关闭时的状态来决定。

SystemChrome.setPreferredOrientations 这个 API,主要是为移动平台设计的。在 Linux 桌面平台上,Flutter 的 C++ embedding(负责与操作系统交互的部分)并没有把这个“方向偏好”强制传递给窗口管理器,或者说,大多数窗口管理器根本就不认这种来自应用的“方向指令”。它们更关心的是用户通过鼠标拖拽或者键盘快捷键发出的窗口控制命令。

所以,尽管你在 Dart 代码里设置了 setPreferredOrientations,Linux 窗口管理器表示:“嗯,知道了,但我不听你的。” 窗口依然按照它的默认规则(通常是横向的、适合桌面的尺寸)来显示。

解决方案来了!

既然直接用 setPreferredOrientations 不行,咱们就得换个思路。下面提供几种在 Linux 上实现 Flutter 应用纵向显示的方法,你可以根据自己的需要选择。

1. 手动调整窗口大小

这是最直接,也是最“笨”的办法,但有时候足够用了。

  • 原理: 利用 Linux 窗口管理器本身提供的功能,手动改变窗口尺寸。
  • 操作步骤:
    1. 照常运行你的 Flutter 应用:flutter run -d linux
    2. 等应用窗口出现后,用鼠标光标移动到窗口的边缘或角落。
    3. 当光标变成调整大小的形状时,按住鼠标左键拖动,把窗口调整成你想要的纵向比例,比如宽度设置得比高度小很多(例如,宽 400 像素,高 800 像素)。
  • 优点:
    • 无需修改任何代码。
    • 简单直接,立刻见效。
  • 缺点:
    • 每次启动应用都需要手动调整一次,比较繁琐。
    • 不精确,尺寸凭感觉。

这个方法适合偶尔需要快速查看一下纵向效果的场景。

2. 修改 Linux Runner 的默认窗口大小

如果你希望每次用 flutter run -d linux 启动时,窗口就自动是纵向的,可以修改 Flutter 项目中 Linux 平台的原生代码。

  • 原理: Flutter Linux 应用的启动入口和窗口创建是在 linux/ 目录下的 C++ 代码里。咱们可以直接修改这部分代码,设置窗口的初始默认尺寸。

  • 操作步骤:

    1. 打开你的 Flutter 项目。

    2. 找到并打开文件 linux/my_application.cc。(注意:文件名可能因你的项目配置略有不同,但通常是这个或者类似的名字)。

    3. 在这个 C++ 文件里,找到创建主窗口的代码部分。通常会调用 gtk_window_new 来创建一个 GtkWindow 对象。

    4. 在创建窗口之后,设置窗口属性的代码附近,找到或者添加设置默认尺寸的调用。查找 gtk_window_set_default_size 函数。

    5. 修改或添加这行代码,设置你想要的纵向尺寸。比如,设置宽度为 400,高度为 800:

      #include "my_application.h"
      
      // ... 其他 include 和代码 ...
      
      struct _MyApplication {
        GtkApplication parent_instance;
        // 可能有其他成员
      };
      
      G_DEFINE_TYPE(MyApplication, my_application, GTK_TYPE_APPLICATION)
      
      // ... 其他函数 ...
      
      static void my_application_activate(GApplication* application) {
        MyApplication* self = MY_APPLICATION(application);
        GtkWindow* window =
            GTK_WINDOW(gtk_application_window_new(GTK_APPLICATION(application)));
      
        // 设置窗口标题等其他属性...
        // gtk_window_set_title(window, "你的应用标题");
      
        // *** 在这里设置默认窗口大小 ** *
        // 把下面的宽度和高度改成你想要的纵向尺寸
        gtk_window_set_default_size(window, 400, 800); // 例如:宽 400,高 800
      
        // 将 Flutter 视图添加到窗口
        GtkBox *hbox = GTK_BOX(gtk_box_new (GTK_ORIENTATION_HORIZONTAL, 0));
        gtk_widget_set_vexpand (GTK_WIDGET(hbox), TRUE);
      
        GtkStack *stack = GTK_STACK (gtk_stack_new ());
        gtk_stack_set_transition_type (stack, GTK_STACK_TRANSITION_TYPE_CROSSFADE);
        gtk_stack_set_transition_duration (stack, 250);
      
      
        FlView* view = fl_view_new(fl_engine_get_project(FL_ENGINE(self->engine)));
        gtk_widget_show(GTK_WIDGET(view));
        gtk_container_add(GTK_CONTAINER(stack), GTK_WIDGET(view));
        gtk_widget_grab_focus (GTK_WIDGET (view));
      
      
        gtk_box_pack_start(hbox, GTK_WIDGET (stack), TRUE, TRUE, 0);
      
      
        // ... 把 hbox 添加到窗口的代码 ...
      
      
        // 最后显示窗口
        gtk_widget_show_all(GTK_WIDGET(window));
      
      }
      
      // ... 其他函数,比如 my_application_startup, my_application_class_init 等 ...
      

      重要: 找到 gtk_window_set_default_size 这行。如果原来就有,直接修改参数;如果没有,在 gtk_application_window_new 创建窗口之后,gtk_widget_show_all 显示窗口之前,添加这行 gtk_window_set_default_size(window, 400, 800);

    6. 保存文件。

    7. 现在,重新运行 flutter run -d linux。你会发现应用窗口启动时就已经是你设定的 400x800 的纵向尺寸了!

  • 优点:

    • 一劳永逸,每次启动都是设定的纵向尺寸。
    • 应用启动时就是目标尺寸,没有闪烁或延迟调整。
  • 缺点:

    • 修改了原生平台代码,如果 Flutter SDK 或项目结构更新,可能需要重新调整。
    • 这个尺寸是硬编码的,如果需要多种尺寸测试,不够灵活。
  • 安全建议:

    • 修改原生代码前,最好使用版本控制(如 Git)提交当前状态,以便随时回滚。
    • 了解你修改的代码是针对 Linux 平台的,不会影响 Android 或 iOS 的构建。
  • 进阶使用技巧:

    • 条件编译: 如果你既想保留默认的桌面尺寸,又想在需要时切换到纵向,可以考虑使用 C++ 的条件编译指令(#ifdef, #define)配合编译参数来控制默认尺寸。例如,定义一个 USE_PORTRAIT_MODE 宏,在编译时决定是否启用纵向尺寸。但这会增加编译配置的复杂性。
    • 配置文件: 更灵活的方式是在 C++ 代码中读取一个配置文件(比如放在 linux/ 目录下的一个简单文本文件),根据文件内容来设置窗口尺寸。这样切换尺寸就不需要重新编译 C++ 代码了,只需要修改配置文件然后 flutter run

3. 使用 window_manager 包在 Dart 中控制窗口

如果你不想动原生 C++ 代码,或者希望在应用运行时动态调整窗口(虽然我们目标是启动时),可以使用第三方 Flutter 包来和窗口管理器交互。window_manager 是一个常用的包,可以让你在 Dart 代码里控制窗口的大小、位置、标题等。

  • 原理: window_manager 包封装了与桌面平台(Windows, macOS, Linux)窗口管理器交互的本地 API 调用,允许 Flutter 应用通过 Dart 代码来控制宿主窗口。

  • 操作步骤:

    1. 添加依赖: 在你的 pubspec.yaml 文件中添加 window_manager 依赖:

      dependencies:
        flutter:
          sdk: flutter
        # ... 其他依赖 ...
        window_manager: ^0.3.0 # 使用当前最新版本
      

      然后运行 flutter pub get

    2. 修改 main.dart: 在你的 main 函数中,初始化 window_manager 并设置窗口大小。注意,这需要在 runApp 之前完成,并且需要异步操作。

      import 'package:flutter/material.dart';
      import 'package:window_manager/window_manager.dart';
      import 'dart:io' show Platform; // 仍然可以用来检查平台
      
      void main() async { // main 函数需要是 async
        // 确保 Flutter 绑定已初始化
        WidgetsFlutterBinding.ensureInitialized();
      
        // 必须先调用 ensureInitialized
        await windowManager.ensureInitialized();
      
        // 仅在 Linux 平台上设置窗口大小 (或其他桌面平台)
        if (Platform.isLinux || Platform.isWindows || Platform.isMacOS) {
            WindowOptions windowOptions = WindowOptions(
              size: Size(400, 800), // 设置你想要的纵向尺寸
              center: true, // 让窗口居中显示(可选)
              // backgroundColor: Colors.transparent, // 其他选项
              // skipTaskbar: false,
              // titleBarStyle: TitleBarStyle.hidden,
            );
            windowManager.waitUntilReadyToShow(windowOptions, () async {
              await windowManager.show();
              await windowManager.focus();
              // 你还可以在这里设置最小/最大尺寸,来模拟固定比例
              // await windowManager.setMinimumSize(Size(400, 800));
              // await windowManager.setMaximumSize(Size(400, 800));
            });
        }
      
        runApp(MyApp());
      }
      
      class MyApp extends StatelessWidget {
        // ... (和之前一样)
      }
      
    3. 运行 flutter run -d linux

      你会看到应用启动后,窗口会(可能快速地)调整到你设定的 Size(400, 800)

  • 优点:

    • 在 Dart 代码层面控制,更符合 Flutter 开发习惯。
    • 跨桌面平台兼容性较好(虽然我们的目标是 Linux)。
    • 可以实现更复杂的窗口控制逻辑,比如在运行时响应某些事件来改变窗口大小。
    • 方便设置居中、最小/最大尺寸等。
  • 缺点:

    • 窗口调整发生在应用启动后,可能会有一个短暂的窗口从默认尺寸变为目标尺寸的过程(闪烁)。waitUntilReadyToShow 可以缓解但未必完全消除。
    • 引入了第三方包的依赖。
  • 安全建议:

    • 使用第三方包时,注意检查其维护状态和社区反馈。window_manager 是比较受认可的包。
    • 确保在 runApp 之前正确 await windowManager.ensureInitialized()windowManager.waitUntilReadyToShow
  • 进阶使用技巧:

    • 固定宽高比: 通过同时设置 setSize, setMinimumSizesetMaximumSize 为相同的值,可以创建一个固定大小、不可调整的窗口,更精确地模拟某些手机屏幕。
    • 动态调整: 你可以在应用的任何地方调用 windowManager 的方法来改变窗口状态,比如添加一个按钮来切换不同的模拟设备尺寸。
    • 监听窗口事件: window_manager 也允许你监听窗口事件,比如关闭、移动、大小调整等,可以实现更高级的交互。

选择哪种方法取决于你的具体需求和偏好。

  • 偶尔用用,不想改代码:手动调整
  • 希望每次启动都是固定纵向尺寸,不怕改点原生代码:修改 Linux Runner
  • 偏好在 Dart 中控制,或者需要更灵活的窗口操作:使用 window_manager

这几种方法应该能帮你解决在 Linux 上模拟手机纵向模式测试 Flutter 应用的问题了。