Flutter for OpenHarmony实战:底部导航栏适配与避坑指南
发布时间:2026/10/15 4:35:06 锦皓数字建站

1. 项目概述与核心思路1.1 这个项目到底在解决什么问题Flutter for OpenHarmony 项目听了很久真正动手才发现大家关注最多的不是路由、不是网络层而是最不起眼的底部应用栏。底部应用栏是几乎每个应用的标配但恰恰是它在 OpenHarmony 上的表现和 Android、iOS 完全不一样。这个项目要做的就是把一个 Flutter 写的 App 跑在 OpenHarmony 设备上用底部应用栏承载主导航切换并验证它在真实设备上的渲染、点击、安全区适配和状态保持行为。说得直白一点这不是一篇教你“画几个按钮”的教程而是一次实战。你会看到我从创建工程到最终跑通完整 Tab 切换链路走过的每一步包括哪些 Flutter 原生组件能用、哪些必须自己做兼容处理、真机上的坑在哪。适合谁来参考呢两类人特别合适一类是准备把现有 Flutter 代码迁移到 OpenHarmony 的团队需要知道底部栏这种公共组件在目标平台上要改多少另一类是刚开始接触 Flutter for OpenHarmony 的开发者想找一个不那么复杂、又能覆盖大量常见问题的练手场景。底部应用栏刚好是这样一个场景它涉及系统安全区、字体渲染、手势点击、动画过渡和状态管理麻雀虽小五脏俱全。1.2 我在动手前做的三个关键判断先说说我拿到这个任务时的第一反应。老实说一开始我以为底部应用栏就是照搬 Android 那套写法无非是Scaffold里放一个bottomNavigationBar底部堆三个按钮加IndexedStack收工。但真正看过 OpenHarmony 上 Flutter 的渲染机制之后我发现事情没那么简单。我做的第一个判断是必须先在真机上跑一个最简 Demo确认 Flutter 引擎的基础渲染、文本、触摸事件三条链路在 OpenHarmony 上是否完整。不要一上来就写业务代码。因为底部应用栏出问题通常不是布局代码的问题而是底层引擎对某些 Widget 的支持不完整。先用一个只含Text和GestureDetector的页面跑通能排除大量干扰项。第二个判断是底部栏的点击区域和系统导航条的关系要放在第一位处理。OpenHarmony 设备大多有虚拟手势条区域这部分空间不算 Flutter 的安全区如果直接让底部栏贴在屏幕最底端很容易出现“按钮看着在页面上实际点击无效”或者“点击有响应但视觉被遮挡”的问题。这个必须从项目一开始就纳入设计而不是等测试反馈再打补丁。第三个判断是状态管理尽量做减法。底部应用栏这个场景用StatefulWidget加一个IndexedStack就够了完全没有必要引入重量级状态管理框架。这样一方面减少对 OpenHarmony Flutter 适配层的依赖另一方面也方便定位问题——如果页面切换状态丢了大概率是我自己的代码问题而不是状态管理库的兼容问题。在动手之前我先把这个项目的最终验收标准列了出来三个底部按钮能在真机上稳定切换切换过程中页面状态不丢失底部栏不被系统手势条遮挡中文字体渲染正常。后面所有的工作都是围绕这四条验收标准展开的。2. 环境准备与工程初始化2.1 工具链与版本组合Flutter for OpenHarmony 和普通 Flutter 开发最大的区别在于你拿到的 Flutter SDK 本身就是带 “ohos” 平台的定制分支。我用的是 Flutter 3.7.12-ohos 分支配合 OpenHarmony SDK 4.1 Beta 和 DevEco Studio 4.0 版本这个组合当时在社区里相对稳定。这里要提醒一个容易踩坑的点不要直接用官方 Flutter 稳定版去创建 OpenHarmony 工程。官方稳定版里根本没有 ohos 目录模板你运行flutter create之后连ohos目录都生成不出来后面的一切都是白搭。必须使用 OpenHarmony 社区维护的 Flutter 分支并且在环境变量里把FLUTTER_STORAGE_BASE_URL指向可以访问的镜像仓库。# 我用的环境配置示例 export FLUTTER_HOME/opt/flutter_ohos export PATH$FLUTTER_HOME/bin:$PATH # 检查当前分支是否支持 ohos 平台 flutter doctor -vflutter doctor -v的输出里如果看到OHOS相关的工具链信息说明你当前 Flutter 分支是支持 OpenHarmony 的。如果没有回到第一个步骤换分支不要在这一步将就。然后是 DevEco Studio 的配置。DevEco Studio 本质上是个基于 IDE 的鸿蒙应用开发工具它在 Flutter for OpenHarmony 项目里的角色是签名、打包、生成 HAP 文件以及把 HAP 安装到真机或模拟器。你不需要在 DevEco Studio 里写 Flutter 代码但你必须在里面配置好签名和自动签名选项否则后面flutter run -d ohos会卡在签名这一步。2.2 创建 Flutter for OpenHarmony 工程的完整过程环境准备与工程初始化确认工具链没问题之后我按下面的步骤创建工程flutter create --platforms ohos,android . --project-name ohos_bottom_app这里我保留了android平台原因很简单我需要对同一个 UI 代码在 Android 和 OpenHarmony 上的表现做对比方便定位是业务问题还是平台适配问题。如果你没有对比需求只保留ohos平台也可以。创建完工程之后第一件事不是写代码而是检查生成的目录结构里有没有ohos目录。如果flutter create结束后你看到了ohos目录并且里面包含entry/src/main/ets之类的子目录说明工程骨架正常接下来就可以直接在你的原生工程里打开它用 DevEco Studio 导入ohos目录不是导入整个 Flutter 工程。flutter build hap --debug这条命令会生成 OpenHarmony 的应用包 HAP 文件。如果这个构建命令能顺利执行完说明 Flutter 代码和原生侧已经打通了。我第一次跑这条命令时卡了几分钟还以为是死循环了后来发现只是第一次要编译整个 Flutter 引擎的 ohos 目标产物耐心等一等就好。有一点需要特别注意OpenHarmony 的工程默认会有entry模块和feature模块的区分Flutter for OpenHarmony 的模板只使用了entry模块。在 DevEco Studio 里做签名配置的时候要选中entry模块的module.json5来配置别配错模块不然会报“签名校验失败”之类的令人崩溃的错误。2.3 目录结构分析与基础骨架工程创建完成后Flutter 侧的目录结构和普通 Flutter 工程几乎一致关键区别是多了ohos目录ohos_bottom_app/ ├── android/ ├── ohos/ │ ├── entry/ │ │ ├── src/ │ │ └── build-profile.json5 │ └── build-profile.json5 ├── lib/ │ ├── main.dart │ └── pages/ ├── pubspec.yaml └── ...写 Flutter 业务代码时我完全不建议去动ohos/entry/src/main/ets里的原生代码。保持原样就好因为 Flutter 的 Widget 层代码最终会由 Flutter 引擎渲染到 OpenHarmony 的显示层你在 ets 里写任何 UI 组件反而会干扰 Flutter 的渲染树。我的代码组织方式很简单lib/main.dart只负责启动入口lib/pages/存放三个业务页面模拟首页、消息页、我的页lib/widgets/存放底部应用栏相关的自定义组件。这样的结构对排查问题非常友好尤其是在你需要在 Android 和 OpenHarmony 之间对比运行的时候改动范围清晰可见。3. 底部应用栏设计三种方案取舍3.1 方案对比BottomNavigationBar、BottomAppBar、自研容器我在这个项目里对比了三种实现方案下面用一个表格归纳它们在 OpenHarmony 上的表现方案Flutter 组件适配成本OpenHarmony 上的主要风险方案A原生 BottomNavigationBarMaterial 库自带低系统性安全区处理较弱底部被手势条遮挡文字和图标混排偶发渲染异常方案BBottomAppBar 容器Material 库自带中能自定义形状但需要手动处理安全区FAB 槽口位置偏色方案C自研底部栏容器纯自写组件高完全可控但边距、涟漪效果、点击热区都要自己接先说方案 A。BottomNavigationBar确实是 Flutter 里最省事的底部栏它自带currentIndex、onTap回调配合type: BottomNavigationBarType.fixed就能实现常见的效果。在 Android 上它几乎零成本但在 OpenHarmony 上它渲染出来的结果更像是“拿 Android 的样式硬套到鸿蒙上”底部导航条区域没有被正确计入系统安全区内容会被系统手势条遮挡而且当底部栏有 4 个以上选项时选项间距和文字字重都跟着变形。方案 B 是BottomAppBar。官方文档里的典型用法是img width100% src1.png搭配 FAB 悬浮球它支持shape参数来给底部栏挖一个圆形凹槽。这个方案你可以自由地把FloatingActionButton嵌到凹槽里UI 效果相当灵活。在 OpenHarmony 上BottomAppBar的适配效果优于BottomNavigationBar因为它的布局本质是一个自定义容器不依赖系统导航栏的参数。方案 C 则是我最后实际采用的方式在最底下自己搭了一个Container作为主体里面放三个Expanded均分点击区域每个子项用GestureDetector或InkWell包裹。这种方案能完全规避平台差异因为你不依赖 Material 的导航栏组件而是依赖最基本的面板、手势、文本绘制而这些底层能力在 Flutter for OpenHarmony 上已被验证是稳定的。3.2 我为什么最终选择 BottomAppBar 自研容器组合最终我采用的并不是纯的哪一个方案而是组合拳底层容器用自研组件外层包一层BottomAppBar。外层选BottomAppBar是因为它在布局层面会占住Scaffold的bottomNavigationBar插槽天然参与底部布局计算省去了手动计算页面可用高度的麻烦。内层我自己写面板是为了对每个子项的背景、点击态、间距做到完全可控。这一步是真正跑通 Android 和 OpenHarmony 双端对比之后才确定的。用纯BottomNavigationBar在 Android 上完美但换到 OpenHarmony 模拟器上就出现底部两条内容重叠用纯自研Container做底部栏则会出现 FAB 悬浮按钮与底部栏之间没有过渡凹槽视觉上比较突兀。两者结合之后我在 OpenHarmony 侧对BottomAppBar增加了自定义SafeArea包裹在内层对点击区域增加了一个高于系统导航条的paddingBottom这才把所有问题收敛掉。这里也顺便给一个判断标准如果你只是做一个纯导航的底部栏不需要 FAB也不需要特殊形状方案 A 在 OpenHarmony 上改一改安全区也能用。但只要你需要 FAB 联动、需要自定义形状、需要兼容多个屏幕比例就老老实实用方案 C。不要贪图省事后面适配会花更多时间。3.3 动手前必须要懂的安全区与导航条概念OpenHarmony 设备和 Android 一样底部可能有手势导航条也可能有系统返回键。这部分系统 UI 占用的区域不在 Flutter 的MediaQuery.padding范围内这是 Flutter for OpenHarmony 上最常见的坑之一。普通 Flutter 中MediaQuery.of(context).padding.bottom会告诉你底部安全区高度。在我的测试机型上Android 的返回值是 48 逻辑像素而 OpenHarmony 在这种高度为 129 逻辑像素左右。如果你不做适配底栏就被手势条吃掉了一大块。我在项目中封装了一个工具方法double bottomSafeHeight(BuildContext context) { final mediaQuery MediaQuery.of(context); final defaultPadding mediaQuery.padding.bottom; // 兼容 OpenHarmony 模拟器手动累加系统导航条高度 final ohosNavBarHeight Platform.isOhos ? 129.0 : 0.0; return max(defaultPadding, ohosNavBarHeight); }这里用到了Platform.isOhos判断需要引入 Flutter 官方对 OpenHarmony 平台扩展包。这种方法的好处是当底栏在安全区内浮动时你可以明确知道“底栏本体有多少高度是点击得到的、有多少高度是被系统遮住的”不会再出现点击区域偏移的问题。4. 实操过程底部应用栏从 0 到 14.1 搭建基础 UI 骨架底部应用栏的基础骨架其实不复杂。我直接在一个Scaffold中使用bottomNavigationBar插槽把自定义底部栏组件塞进去。每个页面不需要关心底部栏长什么样只需要接收一个bodyWidget 即可。这样页面和底部栏之间做到弱耦合。return Scaffold( backgroundColor: const Color(0xFFF5F6FA), body: IndexedStack( index: _currentIndex, children: _pages, ), bottomNavigationBar: BottomBar( currentIndex: _currentIndex, onTap: _onBottomTap, items: _bottomTabItems, ), floatingActionButton: _currentIndex 1, );底部栏组件内部我分三层搭建最外层是一个BottomAppBar设置color为白色elevation设为 8用来和内容区域形成层次。中间层用Container包裹设置一个padding: EdgeInsets.only(bottom: safeAreaBottom)保障系统手势条下方有条空白避免设备“吃掉”点击区。内层是Row里面放三个Expanded子项每个子项包含Icon和Text中间用SizedBox控制间距。这层结构看起来普通但每层都解决了具体问题。最外层让底部栏参与Scaffold布局计算中间层消化安全区高度内层保证点击热区均分。4.2 底部栏按钮状态管理与页面切换底部栏的核心逻辑在于点击切换。我需要一个状态对象来记录当前索引并在点击时刷新页面区域及底部栏的选中状态。我的做法是在StatefulWidget的State中维护_currentIndex通过setState触发重建。class _BottomBarState extends StateStatefulWidget { int _currentIndex 0; void _onBottomTap(int index) { if (index _currentIndex) return; setState(() { _currentIndex index; }); } }这里我加了一个小优化如果点击的是当前索引直接返回不做无谓的setState。因为某些页面在切换时会有下拉刷新或滚动定位如果用户重复点击当前 Tab页面不应该被重建或滚动。选中状态我统一用一个模型类来描述class BottomTabItem { final IconData icon; final IconData activeIcon; final String label; const BottomTabItem({ required this.icon, required this.activeIcon, required this.label, }); }用activeIcon和icon区分选中与未选中状态比用同一图标换颜色要更有辨识度。用户选中的 Tab 图标实心、颜色高亮未选中的空心、颜色灰暗整个交互反馈就清晰了。4.3 自定义形状与 FAB 切槽实现底部应用栏如果只有一个“平铺几个按钮”的面板会有些单调。很多应用会在中间放一个凸起的 FAB 按钮两侧各分布两个 Tab。我在这个项目中也做了一个同样的设计FAB 悬浮在底部栏上方中间位置底部栏在对应位置挖一个半圆形凹槽。在BottomAppBar中这个效果是通过shape参数实现的。OpenHarmony 的 Flutter 分支对shape参数的支持是完整的因为它本质是一个CustomPainter绘制路径。我实际写的形状代码是把底栏上方挖掉一个圆让悬浮 FAB 可以嵌进去BottomAppBar( shape: const CircularNotchedRectangle(), notchMargin: 6.0, child: Row( children: _buildItems(), ), )CircularNotchedRectangle是 Material 库里自带的凹槽形状它通过一个小算法计算出底栏上边缘的圆弧缺口。notchMargin控制 FAB 离凹槽边缘的间距。如果凹槽和 FAB 视觉上总对不齐优先调大notchMargin让 FAB 的凸出部分有更多空间。如果你希望凹槽不是正圆而是偏方形或者自定义弧形可以自己实现一个CustomPainter重写getOuterPath方法。这在 OpenHarmony 上同样奏效因为 Flutter 绘图层在两端是一致的。唯一要注意的是性能CustomPainter在每次页面重建时都会重绘我应该加一个shouldRepaint到 false 来避免无用重绘。4.4 让底部栏悬浮并避开系统导航条底部栏除了作为导航外还有一些安全问题必须处理。当底部栏贴近系统手势条时点击手势条区域会出现“页面无响应”或“触控偏位”这在 OpenHarmony 上有一定概率出现。我的处理方式是让整个底部栏在安全区上方悬浮底部预留安全高度。Container( decoration: const BoxDecoration( color: Colors.white, border: Border(top: BorderSide(color: Color(0xFFEEEEEE))), ), child: Padding( padding: EdgeInsets.only(bottom: getBottomSafeArea()), child: Row(...), ), )有些同学也喜欢用SafeArea包一层SafeArea( top: false, child: Row(...), )我在实际测试中觉得用Padding比SafeArea更稳因为SafeArea在某些模拟器上会把顶部安全区也吃进来导致底部栏高度不一致。后来我干脆用Padding数据值来自MediaQuery这样无论 Android 还是 OpenHarmony我都能通过日志直接看出底部到底预留了多少高度排查问题会快很多。5. 联动效果切换动画与页面状态保持5.1 用 IndexedStack 保住页面状态底部应用栏切换时最大的一个体验问题是如果使用普通的_pages[_currentIndex]来做 body页面切走再切回来时页面的滚动位置、输入框内容、接口加载状态全部重置。这对用户来说是不可接受的。我用的方案是IndexedStack。它会同时构建所有子页面但只显示当前索引的页面。换句话说每个 Tab 页面的 State 都会被保存下来切回来时直接显示内存中的状态而不是重新构建。body: IndexedStack( index: _currentIndex, children: _pages, )使用IndexedStack有一个前提这些页面不能太重。如果某个 Tab 页面包含大量图片加载或视频播放同时构建出来会比较耗内存。对于本项目的三个轻量页面来说完全没问题。如果你的页面比较重可以考虑把_pages做成懒加载具体做法是首次切换到某个 Tab 时才把页面加入列表配合一个缓存映射.5.2 给切换加一点过渡动画底部栏的 Tab 切换默认是瞬间完成的。这看起来有点干。我在这个项目中给内容区加了一个简单的AnimatedSwitcher过渡效果让页面在切换时有轻微的水平滑动与淡入淡出。通常有两种做法对IndexedStack整体做一个透明度动画但刷新过程中会出现页面“同时存在”的叠影。包裹一层AnimatedSwitcher时在切换时让前后内容同时出现在布局里。后者有时候会让 OpenHarmony 端的 GPU 合成有明显卡顿。所以我把动画简化在AnimatedSwitcher的transitionBuilder里只做淡入淡出时长 200ms。因为 OpenHarmony 的 Flutter 分支对于位移动画的支持还算稳定但对同时使用平移和透明变换的组合动画渲染负载会明显提升有时会出现掉帧。AnimatedSwitcher( duration: const Duration(milliseconds: 200), child: KeyedSubtree( key: ValueKeyint(_currentIndex), child: _pages[_currentIndex], ), )注意这里的KeyedSubtree很关键。如果缺少 keyAnimatedSwitcher会认为前后是同一个 Widget从而不触发过渡动画。加上ValueKey(_currentIndex)后每次切换都会产生新旧两个子树动画就出来了。5.3 状态管理相关的一个小优化底部栏和页面之间除了 index 之外经常还需要传递一些信息。比如页面内部可能需要主动切换 Tab例如“去设置页”。我在这个项目里用一个ValueNotifierint来替代setState的一部分工作。final ValueNotifierint _tabIndexNotifier ValueNotifier(0);然后在底部栏的点击回调里修改_tabIndexNotifier.value在页面区域通过ValueListenableBuilder监听切换。这样做的收益是当页面内部主动切换 Tab 时不需要再把_currentIndex一级级传递回State减少了不必要的setState范围。这个优化在 Android 上的体感不明显但在 OpenHarmony 的低端设备上有意义因为setState如果写在根State上会导致整棵页面 Widget 树重建。而ValueListenableBuilder只会重建它包裹的那个子树重建成本小得多。6. 常见问题排查与避坑记录6.1 问题速查表这一部分是我在真机测试过程里收集到的问题整理成表问题现象可能原因解决方式点击底部栏按钮没有反应触摸区域被系统导航条遮挡给底部栏外层加Padding(bottom: safeArea)底部栏文字/图标挤在一起字体缩放或本地化导致的文本换行固定按钮宽度文字maxLines: 1,overflow: TextOverflow.ellipsis切换 Tab 后页面空白IndexedStack页面未初始化完成在页面初始化时预加载数据而不是懒加载FAB 凹槽位置对不齐notchMargin太小或 FAB 尺寸匹配不对调整notchMargin确保 FAB 直径和凹槽半径匹配中文字体显示为方块OpenHarmony 字体包未设置中文字体在MaterialApp中设置fontFamily或加入自定义字体底部栏背景色和主题色不一致Material 主题未生效到BottomAppBar显式传入color不要依赖主题默认色这些坑没有一个是特别复杂的但它们共同构成了“在 OpenHarmony 上实现底部栏”的真实工作量。6.2 排查手记点击响应区域错位我在第一版 Demo 里就踩了点击区域错位的坑。现象是第一个按钮在屏幕左侧点击它却触发了第二个按钮。起初我以为是Row里的Expanded写错了但后来发现关闭系统手势条后这个问题消失了。原因在于系统导航条占据的屏幕区域有自己的触摸事件优先级这会导致底部栏的触摸事件热区向下偏移。我通过给BottomAppBar外层加SafeArea(top: false)解决了大部分问题但是仍然有一个隐藏问题Scaffold的resizeToAvoidBottomInset默认是 true当系统导航条高度变化时Scaffold内部高度也会变化最终导致底部栏向上移动。最终我把处理方案稳定为在Scaffold上显式设置resizeToAvoidBottomInset: false然后在底部栏外部自己处理安全区。这避免了 Flutter 对系统 UI 变化的持续监听干扰。6.3 排查手记中文字形显示异常OpenHarmony 设备上的 Flutter 默认字体如果不作处理中文会显示为“口口”状的方块字。这是一个经典的坑而且一旦出现整个应用看起来就像在报错严重影响体验。我处理的方式是在MaterialApp的主题里显式设置中文字体MaterialApp( theme: ThemeData( fontFamily: HarmonyOS Sans SC, ), )如果你当前的 Flutter 分支没有内置HarmonyOS Sans SC就需要在assets/fonts/目录下放置系统字体文件并在pubspec.yaml中声明fonts: - family: HarmonyOS Sans SC assets: - assets/fonts/HarmonyOS_Sans_SC_Regular.ttf这个问题之所以容易出现是因为普通 Android 系统自带大量中西文 fallback 字体而 OpenHarmony 作为新系统系统字体 fallback 设置和 Flutter 引擎的兼容度还需要打磨。所以做 Flutter for OpenHarmony 项目时不管你做不做底部栏项目中文团队务必先把字体问题尽早纳入验证。6.4 排查手记底部栏背景色不跟随主题色你可能不信我遇到过底部栏背景色怎么设置都变灰的情况。BottomAppBar的color参数明明设置了白色但整个底部栏在真机上显示为灰色。后来发现是ThemeData里canvasColor被设置成了灰色而BottomAppBar在没有指定color时会使用主题的canvasColor作为默认背景色。解决方案很直白刚才代码里已经写了显式传color。但传了color之后背景色又和 Card 的 elevation 产生了冲突底栏顶部的阴影视图在 OpenHarmony 上被渲染成了半透明黑色导致整体颜色看起来偏深。我最后给底栏加了一层Material包裹设置elevation: 0彻底取消阴影再在外面手动加一个 1 像素的顶部边框线。这样既保持了视觉效果也不会在真机上出现莫名其妙的阴影层。7. 性能与包体影响评估7.1 底层适配带来的额外开销Flutter for OpenHarmony 的底层适配比我原本预想的要重一些。同样一个空页面OpenHarmony 的 APK/HAP 首帧渲染时间比 Android 慢 200 毫秒左右。这主要是 Flutter 引擎在 OpenHarmony 上的线程调度、渲染框架对接还处于迭代中的结果。底部应用栏因为涉及页面切换和布局重建如果代码不够精简掉帧会更明显。实测来看我用了IndexedStack后在 OpenHarmony 真机上切换 Tab 的平均帧间隔约为 18ms边缘场景会有 25ms 的掉帧。而在 Android 上同一套代码的帧间隔基本稳定在 12ms 以内。这个差距初学者要心里有数不要以为 OpenHarmony 上 Flutter 跑不爽的卡顿一定是代码问题。7.2 包体体积与首帧耗时实测数据我记录了一个对比数据在我的测试设备上平台Debug 包体积/MB冷启动首帧/sTab 切换平均帧间隔/msAndroid431.812OpenHarmony未统计2.118因为 OpenHarmony 打包时会对依赖库做整体编译不同设备、不同 Flutter 版本会明显影响这个数值所以我不会把这个数据当成绝对结论。但有一点可以确定同一个 Flutter 应用在 OpenHarmony 上的包体积和运行时内存占用普遍略高于 Android。如果你做的是发布版一定记得用--release构建不要拿 Debug 包的性能数据去评估正式体验。7.3 不同设备上的“安全区”差异最后提醒一个容易被忽略的环节OpenHarmony 的“安全区”在不同设备上差异很大。有的设备是全面屏手势条有的设备是三键导航有的设备还支持底部小横条。仅仅依赖MediaQuery.padding.bottom来获取安全区高度在某些设备上会得到 0。我的兜底方案是读取MediaQuery的安全区后再和系统导航条高度常量取最大值。Platform.isOhos时的导航条高度常量默认 129 逻辑像素这样即便系统没有上报安全区也不会被手势条遮挡。这种兜底方式并不完美它的风险在于如果未来 OpenHarmony 主流设备都取消手势条会造成底部空白过大。但就现在而言多留一点空白比内容被遮住要稳妥得多。
锦
锦皓数字建站
深耕本土企业品牌数字化升级,专注原创端正雅致商务官网,从视觉设计到稳定运维全程保驾护航。