去年在Flutter鸿蒙项目里做视频播放器时,以为全屏播放就是藏状态栏和导航栏那么简单,结果在鸿蒙真机上踩了一串坑:退出全屏后状态栏图标颜色不对、横竖屏切换布局错乱、不同页面方向锁定需求不同。折腾几天才把 SystemChrome 这套 API 的门道摸清。下面把实战中的场景、踩坑和最终代码组织方式分享出来,顺便对比鸿蒙 ArkTS 的原生窗口控制 API。
一、SystemChrome 能做什么
Flutter 的 SystemChrome 位于 package:flutter/services.dart,通过 platform channel 与原生平台通信,控制系统栏、屏幕方向等系统级 UI 元素。核心用到的三个静态方法:setPreferredOrientations(屏幕方向)、setEnabledSystemUIMode(系统栏显示模式)、setSystemUIOverlayStyle(系统栏样式)。注意这些方法都是全局生效的,调一次整个应用都受影响。
二、屏幕方向锁定:允许列表而非设置方向
视频播放页需要支持横竖屏切换,但首页和列表页必须锁竖屏。一开始只在首页 initState 里调 setPreferredOrientations,发现播放页 pop 回首页后方向没恢复。原因:setPreferredOrientations 传入的是允许的方向列表(白名单),全局生效。正确做法是在每个需要控制方向的页面主动设置。
竖屏锁定最好同时传入 portraitUp 和 portraitDown,避免用户躺着时方向错乱。横屏也一样,landscapeLeft 和 landscapeRight 都写上。
- void _setOrientation(Orientation orientation) {
- if (orientation == Orientation.portrait) {
- SystemChrome.setPreferredOrientations([
- DeviceOrientation.portraitUp,
- DeviceOrientation.portraitDown,
- ]);
- } else if (orientation == Orientation.landscape) {
- SystemChrome.setPreferredOrientations([
- DeviceOrientation.landscapeLeft,
- DeviceOrientation.landscapeRight,
- ]);
- } else {
- SystemChrome.setPreferredOrientations(DeviceOrientation.values);
- }
- }
复制代码
三、全屏模式选 immersiveSticky
视频播放时需隐藏状态栏和导航栏。SystemChrome 提供几种 SystemUiMode:edgeToEdge(内容延伸到系统栏区域但系统栏仍可见)、leanBack(点击屏幕才恢复系统栏,且不会自动再隐藏)、immersive(滑动显示系统栏,不会自动隐藏)、immersiveSticky(滑动显示系统栏,几秒后自动隐藏)。
视频播放器推荐 immersiveSticky,用户从边缘滑动调出系统栏调节亮度/音量,松手后自动藏回,体验最自然。
- void _toggleFullScreen() {
- if (_isFullScreen) {
- SystemChrome.setEnabledSystemUIMode(SystemUiMode.immersiveSticky);
- } else {
- SystemChrome.setEnabledSystemUIMode(SystemUiMode.edgeToEdge);
- }
- }
复制代码
注意:在鸿蒙某些版本上,从底部上滑呼出导航栏后,应用会收到 didChangeSystemUIOverlays 回调,可通过 WidgetsBindingObserver 监听并做 UI 调整(如暂停字幕或显示进度条)。
四、状态栏样式适配深色模式
深色模式下状态栏图标应为浅色(白色),浅色模式下应为深色(黑色),否则图标与背景混在一起。SystemUiOverlayStyle 包含 statusBarColor(背景色)、statusBarIconBrightness(图标亮度)、systemNavigationBarColor 和 systemNavigationBarIconBrightness(导航栏样式)等。
- void _applyStatusBarStyle() {
- SystemChrome.setSystemUIOverlayStyle(SystemUiOverlayStyle(
- statusBarColor: _statusBarColor,
- statusBarIconBrightness: _useLightIcons ? Brightness.light : Brightness.dark,
- systemNavigationBarColor: _isFullScreen ? Colors.black : Colors.white,
- systemNavigationBarIconBrightness: _isFullScreen ? Brightness.light : Brightness.dark,
- ));
- }
复制代码
建议在 MaterialApp 的 builder 里根据当前主题亮度统一调用,避免每个页面重复写。
- MaterialApp(
- builder: (context, child) {
- final brightness = Theme.of(context).brightness;
- SystemBarManager.applyTheme(brightness);
- return child!;
- },
- )
复制代码
五、导航栏显示控制
全屏阅读器等场景可能需要隐藏底部导航栏。用 manual 模式加空 overlays 列表可隐藏所有系统 UI,但用户从底部上滑时导航栏仍会重新出现,应用无法阻止。
- void _toggleNavigationBar() {
- if (_systemNavVisible) {
- SystemChrome.setEnabledSystemUIMode(SystemUiMode.edgeToEdge);
- } else {
- SystemChrome.setEnabledSystemUIMode(SystemUiMode.manual, overlays: []);
- }
- }
复制代码
也可灵活控制只隐藏状态栏或导航栏:overlays 传 [SystemUiOverlay.top] 或 [SystemUiOverlay.bottom]。
六、与鸿蒙 ArkTS 原生 API 对比
鸿蒙 ArkTS 通过 window 模块(WindowStage/UIAbilityContext)直接操作窗口对象,API 更底层。例如:- import { window } from '@kit.ArkUI';
- let windowClass = window.getLastWindow(getContext(this));
- windowClass.setWindowLayoutFullScreen(true);
- windowClass.setStatusBarProperties({ statusBarColor: '#00000000', statusBarContentColor: '#FFFFFF' });
- windowClass.setPreferredOrientation(window.Orientation.PORTRAIT);
- windowClass.setSpecificSystemBarEnabled('navigation', false); // 鸿蒙特有,可单独禁用手势导航栏
复制代码
主要区别:
- Flutter 是声明式封装,跨平台统一但无法覆盖平台特有功能(如鸿蒙的 setSpecificSystemBarEnabled)。
- 鸿蒙 API 更灵活,但页面生命周期切换需手动管理(onPageShow/onPageHide)。
- 方向控制:Flutter 用允许列表(白名单),鸿蒙用固定方向,Flutter 更合理。
- 生命周期绑定:两边都需自行处理,半斤八两。
七、踩过的坑
1. setPreferredOrientations 全局影响:pop 回首页方向不恢复。解决:在 dispose 中恢复方向,或首页每次 initState 主动设置。
2. restoreSystemUIOverlays 的“栈”式行为容易混乱,建议弃用,直接在 dispose 里明确恢复已知状态。
3. immersiveSticky 与 leanBack 区别:leanBack 点击屏幕恢复系统栏且不自动隐藏,immersiveSticky 滑动临时显示并自动隐藏。视频播放用 immersiveSticky 更合适。
4. 状态栏颜色和图标亮度需同时考虑,否则图标不可见。
5. 设置 statusBarColor: Colors.transparent 后,内容延伸到状态栏区域,必须用 SafeArea 包裹 AppBar 避免标题遮挡。
6. 前后台切换导致样式丢失:在 WidgetsBindingObserver 的 didChangeAppLifecycleState 监听 resumed 状态,重新应用样式。
7. 鸿蒙真机特殊表现:immersiveSticky 底部上滑会触发“智慧多窗”而非临时显示导航栏,这是系统 ROM 行为,应用层无法修改,暂时接受。
八、项目代码组织
建议封装一个 SystemBarService 类,统一管理所有 SystemChrome 调用:- class SystemBarService {
- static void initialize() { /* 应用启动时初始化 */ }
- static void enterVideoFullscreen() { /* 进入视频全屏 */ }
- static void exitFullscreen() { /* 退出全屏 */ }
- static void applyTheme(Brightness brightness) { /* 根据主题应用样式 */ }
- }
复制代码
在 main.dart 调 initialize,MaterialApp builder 调 applyTheme,各页面调 enterVideoFullscreen/exitFullscreen。所有操作集中管理,便于排查。
九、验证提示
务必在真机上测试系统栏控制,模拟器行为与真机差异大(特别是鸿蒙)。验证环境:Flutter + HarmonyOS 6.0 + nova。 |