Flutter 屏幕旋转适配

0 阅读3分钟
mindmap
  root((Flutter 屏幕旋转适配))
    原理
      旋转手机 = 窗口尺寸变了
      Flutter 检测到 → 自动重新布局
    怎么监听
      OrientationBuilder 根据横竖屏切布局
      MediaQuery.sizeOf 根据宽度切布局 更推荐
    怎么锁定屏幕
      SystemChrome.setPreferredOrientations
      锁定竖屏 portraitUp
      锁定横屏 landscapeLeft Right
      恢复自动 DeviceOrientation.values
    安卓 vs iOS
      Android 一行代码搞定
      iOS 要多配置 3 层
        Info.plist 声明支持方向
        AppDelegate 写回调动态控制
        ViewController 也会参与判断
    视频全屏实战
      进全屏 → 切横屏
      退出全屏 → 切回竖屏
      iOS 需要额外在 AppDelegate 里配合

在移动开发中,屏幕旋转是非常常见的场景。

例如:

  • 视频播放器全屏播放
  • 平板横屏适配
  • 表单页面横竖屏切换
  • 折叠屏展开与收起

Flutter 已提供了完整的横竖屏支持。

本文将从实际开发角度介绍:

  • 如何监听屏幕旋转
  • 如何适配横竖屏布局
  • 如何锁定横屏或竖屏
  • Android 与 iOS 的区别,尤其是 iOS 特别要注意的地方

什么是屏幕旋转

当用户旋转手机时:

设备方向变化
      ↓
系统更新窗口尺寸
      ↓
Flutter 重新布局
      ↓
Widget 重新构建

因此:

Flutter 屏幕旋转本质上是窗口尺寸变化后的重新布局过程。


使用 OrientationBuilder 监听方向变化

Flutter 官方推荐使用:

OrientationBuilder(builder: (context, orientation) {
    return orientation == Orientation.portrait ? const PortraitPage()
        : const LandscapePage();
  },
);

效果:

  • 竖屏显示 PortraitPage
  • 横屏显示 LandscapePage

实现横竖屏不同布局

例如商品列表页面:

竖屏:

2列 Grid

横屏:

3列 Grid

代码:

OrientationBuilder(builder: (context, orientation) {
  return GridView.count(rossAxisCount: orientation == Orientation.portrait ? 2 : 3);
  },
);

使用 MediaQuery 获取方向

除了 OrientationBuilder。

还可以直接获取方向:

final orientation = MediaQuery.orientationOf(context);

判断:

if (orientation == Orientation.landscape) {
  print('横屏');
}

使用 MediaQuery 获取屏幕尺寸

实际开发中更推荐:

final size = MediaQuery.sizeOf(context);

例如:

final width = MediaQuery.sizeOf(context).width;

根据宽度决定布局:

if (width > 600) {
  return TabletPage();
}

return MobilePage();

相比 Orientation。

这种方式更适合:

  • 平板
  • 折叠屏
  • 多窗口

场景。


OrientationBuilder 和 MediaQuery 的区别

方式作用
OrientationBuilder根据方向切换布局
MediaQuery.orientationOf获取当前方向
MediaQuery.sizeOf获取屏幕尺寸(推荐)

Flutter 官方建议:

优先根据 Size 设计布局
而不是只依赖 Orientation

因为大屏设备和折叠屏场景下:

方向不变

尺寸也可能变化

锁定竖屏

很多 App 会禁止横屏。

实现方式:

void main() async {
  WidgetsFlutterBinding.ensureInitialized();

  await SystemChrome.setPreferredOrientations([
    DeviceOrientation.portraitUp,
  ]);

  runApp(const MyApp());
}

锁定横屏

例如:

  • 视频播放器
  • 游戏
  • 车机应用

代码:

await SystemChrome.setPreferredOrientations([
  DeviceOrientation.landscapeLeft,
  DeviceOrientation.landscapeRight,
]);

恢复自动旋转

await SystemChrome.setPreferredOrientations(
  DeviceOrientation.values,
);

恢复系统默认行为。


Android 与 iOS 的区别

为什么

SystemChrome.setPreferredOrientations(...)

Android 可以正常工作。

但 iOS 经常不生效

原因是Android 和 iOS 的方向管理机制完全不同


Android

Android 中:

SystemChrome.setPreferredOrientations(...)

Flutter 最终会调用 Android 原生:

Activity.setRequestedOrientation()

请求系统切换方向。App一般能够正确旋转方向

因此大多数 Flutter 项目不需要编写 Android 原生代码 只使用 Flutter API 即可。


iOS

iOS 不一样。 很多人以为:

SystemChrome.setPreferredOrientations(...)

就能直接控制方向。

实际上 Flutter 只是向系统提出请求

最终是否旋转由 UIKit 决定

Apple 官方文档也说明:

系统会综合判断:

  • App 支持哪些方向
  • 当前 ViewController 支持哪些方向
  • 当前 Window 支持哪些方向

来判断当前是否允许旋转。


iOS 配置 Info.plist

如果需要用户旋转手机屏幕 首页保持竖屏, 需要在 Info.plist 配置 Portrait

<key>UISupportedInterfaceOrientations</key>
<array>
    <string>UIInterfaceOrientationPortrait</string>
</array>

表示整个 App 默认只支持竖屏

那视频播放器为什么还能横屏

在 AppDelegate 实现如下回调: 例如:

override func application(
    _ application: UIApplication,
    supportedInterfaceOrientationsFor window: UIWindow?
) -> UIInterfaceOrientationMask {
    return OrientationUtils.shared.orientationLock
}

作用: 动态控制当前页面允许的方向

例如:

首页
↓
Portrait

视频页
↓
Landscape

退出视频页, 返回其他页面
↓
Portrait

Apple 官方说明:

如果实现了这个方法。 系统会优先询问使用这里返回的方向,而不仅仅使用 Info.plist。


Flutter + iOS 常见方案

Info.plist

默认:

Portrait

AppDelegate

动态控制:

var orientationLock:
UIInterfaceOrientationMask = .portrait

进入视频页:

orientationLock = .landscape

退出视频页:

orientationLock = .portrait

然后 Flutter 调用:

SystemChrome.setPreferredOrientations(...)

完成横竖屏切换。

Android iOS 横竖屏切换总结

Android:

Flutter 请求方向

系统通常直接执行

iOS:

Flutter 请求方向
      ↓
Info.plist 判断
      ↓
AppDelegate 判断(如果实现,覆盖Info.plist中条件)
      ↓
ViewController 判断(和AppDelegate/Info.plist取交集)
      ↓
UIKit 最终决定(交集结果)

因此:

  • Android 通常只需要 Flutter 代码;
  • iOS 除了 Flutter 代码外,还需要正确配置 Info.plist,还要通过 AppDelegate 动态控制横竖屏。

视频播放器横屏实战

进入全屏:

await SystemChrome
    .setPreferredOrientations([
  DeviceOrientation.landscapeLeft,
  DeviceOrientation.landscapeRight,
]);

退出全屏:

await SystemChrome
    .setPreferredOrientations([
  DeviceOrientation.portraitUp,
]);

这是最常见的使用场景。