在移动开发中,屏幕旋转是非常常见的场景。
例如:
- 视频播放器全屏播放
- 平板横屏适配
- 表单页面横竖屏切换
- 折叠屏展开与收起
Flutter 已提供了完整的横竖屏支持。
本文将从实际开发角度介绍:
- 如何监听屏幕旋转
- 如何适配横竖屏布局
- 如何锁定横屏或竖屏
- Android 与 iOS 的区别,尤其是 iOS 特别要注意的地方
什么是屏幕旋转
当用户旋转手机时:
text
设备方向变化
↓
系统更新窗口尺寸
↓
Flutter 重新布局
↓
Widget 重新构建
因此:
Flutter 屏幕旋转本质上是窗口尺寸变化后的重新布局过程。
使用 OrientationBuilder 监听方向变化
Flutter 官方推荐使用:
dart
OrientationBuilder(builder: (context, orientation) {
return orientation == Orientation.portrait ? const PortraitPage()
: const LandscapePage();
},
);
效果:
- 竖屏显示 PortraitPage
- 横屏显示 LandscapePage
实现横竖屏不同布局
例如商品列表页面:
竖屏:
text
2列 Grid
横屏:
text
3列 Grid
代码:
dart
OrientationBuilder(builder: (context, orientation) {
return GridView.count(rossAxisCount: orientation == Orientation.portrait ? 2 : 3);
},
);
使用 MediaQuery 获取方向
除了 OrientationBuilder。
还可以直接获取方向:
dart
final orientation = MediaQuery.orientationOf(context);
判断:
dart
if (orientation == Orientation.landscape) {
print('横屏');
}
使用 MediaQuery 获取屏幕尺寸
实际开发中更推荐:
dart
final size = MediaQuery.sizeOf(context);
例如:
dart
final width = MediaQuery.sizeOf(context).width;
根据宽度决定布局:
dart
if (width > 600) {
return TabletPage();
}
return MobilePage();
相比 Orientation。
这种方式更适合:
- 平板
- 折叠屏
- 多窗口
场景。
OrientationBuilder 和 MediaQuery 的区别
| 方式 | 作用 |
|---|---|
| OrientationBuilder | 根据方向切换布局 |
| MediaQuery.orientationOf | 获取当前方向 |
| MediaQuery.sizeOf | 获取屏幕尺寸(推荐) |
Flutter 官方建议:
text
优先根据 Size 设计布局
而不是只依赖 Orientation
因为大屏设备和折叠屏场景下:
text
方向不变
尺寸也可能变化
锁定竖屏
很多 App 会禁止横屏。
实现方式:
dart
void main() async {
WidgetsFlutterBinding.ensureInitialized();
await SystemChrome.setPreferredOrientations([
DeviceOrientation.portraitUp,
]);
runApp(const MyApp());
}
锁定横屏
例如:
- 视频播放器
- 游戏
- 车机应用
代码:
dart
await SystemChrome.setPreferredOrientations([
DeviceOrientation.landscapeLeft,
DeviceOrientation.landscapeRight,
]);
恢复自动旋转
dart
await SystemChrome.setPreferredOrientations(
DeviceOrientation.values,
);
恢复系统默认行为。
Android 与 iOS 的区别
为什么
dart
SystemChrome.setPreferredOrientations(...)
Android 可以正常工作。
但 iOS 经常不生效
原因是Android 和 iOS 的方向管理机制完全不同
Android
Android 中:
dart
SystemChrome.setPreferredOrientations(...)
Flutter 最终会调用 Android 原生:
java
Activity.setRequestedOrientation()
请求系统切换方向。App一般能够正确旋转方向
因此大多数 Flutter 项目不需要编写 Android 原生代码 只使用 Flutter API 即可。
iOS
iOS 不一样。 很多人以为:
dart
SystemChrome.setPreferredOrientations(...)
就能直接控制方向。
实际上 Flutter 只是向系统提出请求
最终是否旋转由 UIKit 决定
Apple 官方文档也说明:
系统会综合判断:
- App 支持哪些方向
- 当前 ViewController 支持哪些方向
- 当前 Window 支持哪些方向
来判断当前是否允许旋转。
iOS 配置 Info.plist
如果需要用户旋转手机屏幕 首页保持竖屏, 需要在 Info.plist 配置 Portrait
xml
<key>UISupportedInterfaceOrientations</key>
<array>
<string>UIInterfaceOrientationPortrait</string>
</array>
表示整个 App 默认只支持竖屏
那视频播放器为什么还能横屏
在 AppDelegate 实现如下回调: 例如:
swift
override func application(
_ application: UIApplication,
supportedInterfaceOrientationsFor window: UIWindow?
) -> UIInterfaceOrientationMask {
return OrientationUtils.shared.orientationLock
}
作用: 动态控制当前页面允许的方向
例如:
text
首页
↓
Portrait
视频页
↓
Landscape
退出视频页, 返回其他页面
↓
Portrait
Apple 官方说明:
如果实现了这个方法。 系统会优先询问使用这里返回的方向,而不仅仅使用 Info.plist。
Flutter + iOS 常见方案
Info.plist
默认:
xml
Portrait
AppDelegate
动态控制:
swift
var orientationLock:
UIInterfaceOrientationMask = .portrait
进入视频页:
swift
orientationLock = .landscape
退出视频页:
swift
orientationLock = .portrait
然后 Flutter 调用:
dart
SystemChrome.setPreferredOrientations(...)
完成横竖屏切换。
Android iOS 横竖屏切换总结
Android:
text
Flutter 请求方向
系统通常直接执行
iOS:
text
Flutter 请求方向
↓
Info.plist 判断
↓
AppDelegate 判断(如果实现,覆盖Info.plist中条件)
↓
ViewController 判断(和AppDelegate/Info.plist取交集)
↓
UIKit 最终决定(交集结果)
因此:
- Android 通常只需要 Flutter 代码;
- iOS 除了 Flutter 代码外,还需要正确配置 Info.plist,还要通过
AppDelegate动态控制横竖屏。
视频播放器横屏实战
进入全屏:
dart
await SystemChrome
.setPreferredOrientations([
DeviceOrientation.landscapeLeft,
DeviceOrientation.landscapeRight,
]);
退出全屏:
dart
await SystemChrome
.setPreferredOrientations([
DeviceOrientation.portraitUp,
]);
这是最常见的使用场景。