【OpenHarmony/HarmonyOs 】CheckMe 悬浮导航栏与沉浸光感体验实践:从系统栏到实时仪表盘的视觉升级
项目背景:本文基于我的 HarmonyOS 项目
CheckMe展开。它不是一个单纯的信息展示 Demo,而是一个面向真实设备状态监控的工具类应用,覆盖 CPU、内存、电池、网络、定位、媒体能力、硬件检测和桌面卡片等模块。
很多 HarmonyOS 工具类应用容易做成“表格列表”:能用,但缺少视觉记忆点。CheckMe的目标不只是把设备参数列出来,而是让用户一打开应用,就能感受到一种实时、清爽、有层次的设备看板体验。✨
这篇文章重点拆解三个方向:
- 悬浮感底部导航栏
- 沉浸式系统栏与安全区适配
- 光感卡片、实时数据与交互动效
本文不重复展开服务卡片、设备信息采集、WorkScheduler 等工程链路,而是更聚焦 UI 体验与交互设计。
一、为什么工具类 App 也需要“视觉体验”
设备信息类应用的核心当然是数据准确,但只追求“把数据展示出来”是不够的。
用户真正打开这类应用时,往往有几个典型场景:
- 想看当前 CPU 是否过高
- 想快速确认电池、存储、网络状态
- 想进行一次硬件检测
- 想知道系统状态是否异常
这些场景都强调“快速判断”。因此页面设计应该让用户一眼看到重点,而不是陷入密密麻麻的参数列表。
CheckMe采用的思路是:
- 首页做成实时 Dashboard,而不是普通设置页。
- 底部导航固定在可触达区域,降低切换成本。
- 系统栏、底栏和页面背景颜色保持一致,营造沉浸感。
- 图表和卡片使用柔和光感,不做过度炫技。
- 只让当前可见页面运行轮询和动画,避免为了“好看”牺牲性能。
这也是我认为 HarmonyOS 工具应用可以变得更高级的地方:不是堆控件,而是把系统能力、状态数据和视觉反馈组织成一个统一体验。
二、沉浸式系统栏:先把页面“铺满”
项目里系统栏处理放在EntryAbility.ets中。核心代码如下:
privateapplySystemBarToWindow(win:window.Window):void{constisDark =this.isEffectiveDarkMode();constbarBackground = isDark ?'#FF1E293B':'#FFFFFFFF';constbarContent = isDark ?'#FFFFFFFF':'#FF000000';constprops:window.SystemBarProperties= {statusBarColor: barBackground,statusBarContentColor: barContent,navigationBarColor: barBackground,navigationBarContentColor: barContent }; win.setWindowLayoutFullScreen(true); win.setWindowSystemBarProperties(props); }这里有两个关键点:
setWindowLayoutFullScreen(true):让应用内容进入全屏布局语境。setWindowSystemBarProperties(props):主动控制状态栏、导航栏背景与文字颜色。
很多页面看起来“不高级”,原因不是组件写得不好,而是系统栏和页面割裂:页面是白色,导航栏是黑色;页面是深色,状态栏文字又不清楚。CheckMe在浅色和深色模式下分别计算系统栏颜色,让系统区域和页面卡片区域保持一致。
官方文档中也提到窗口避让和沉浸布局相关能力,实际开发时需要结合设备形态、安全区域和系统栏属性综合处理。
三、底部导航栏:固定可触达,但不遮挡内容
CheckMe的主界面采用底部导航,包含:
- 概览
- 硬件
- 工具
- 位置
- 媒体
底部导航代码片段:
@BuilderBottomTabBar(){Column(){Divider().color($r('app.color.border_divider')) .strokeWidth(0.5).width('100%')Row(){ this.TabItemBuilder('overview', '概览', $r('sys.symbol.house')) this.TabItemBuilder('hardware', '硬件', $r('sys.symbol.externaldrive_fill')) this.TabItemBuilder('tools', '工具', $r('sys.symbol.gearshape_fill')) this.TabItemBuilder('location', '位置', $r('sys.symbol.map')) this.TabItemBuilder('media', '媒体', $r('sys.symbol.music_fill')) } .width('100%') .height(49)Column().width('100%') .height(this.bottomSafeInsetVp) } .width('100%') .backgroundColor($r('app.color.card_background')) }这个底栏看似简单,但它做了一个很重要的细节:把系统导航指示条区域单独补出来。
在全面屏设备上,如果底部栏只设置固定高度,可能出现文字贴底、被手势条遮挡、视觉重心下坠等问题。CheckMe用bottomSafeInsetVp单独计算底部避让高度,让导航栏既贴近系统区域,又不会压住内容。
四、安全区高度如何计算
在Index.ets中,项目通过window.getWindowAvoidArea()获取系统导航指示条区域:
privateupdateBottomSafeInset():void{constctx: common.UIAbilityContext=getContext(this)ascommon.UIAbilityContext;window.getLastWindow(ctx).then((win:window.Window) =>{constavoid:window.AvoidArea= win.getWindowAvoidArea(window.AvoidAreaType.TYPE_NAVIGATION_INDICATOR);constpxH = avoid.bottomRect.height;constd = display.getDefaultDisplaySync();constdpi = d.densityDPI;this.bottomSafeInsetVp= (pxH *160) / dpi; }).catch((_err:Error) =>{this.bottomSafeInsetVp=0; }); }这里把像素高度转换成vp:
this.bottomSafeInsetVp= (pxH *160) / dpi;这段代码非常适合写进项目亮点,因为它体现了一个成熟 App 的思维:不是只在模拟器上看起来正常,而是考虑真实设备、安全区、不同 DPI 和系统手势区域。
📌 我的经验是:底部导航栏想做出“悬浮感”和“系统感”,避让区比阴影、圆角更重要。
五、导航交互:切 Tab 不只是改状态
CheckMe切换 Tab 时没有直接改currentTab就结束,而是做了过渡动画,并在切换时停止不可见页面的轮询和装饰动画。
privateonTabChange(tabKey: string): void {if(tabKey ===this.currentTab) {return; }constpreviousTab: string =this.currentTab;this.stopDecorAndSecondaryTimers();this.stopCpuUsagePolling();this.stopRefreshRatePolling();this.stopOverviewDashboardPolling();this.tabContentOpacity =0;this.tabContentOffset =20; setTimeout(() => {this.currentTab = tabKey; animateTo({ duration:250, curve: Curve.EaseOut }, () => {this.tabContentOpacity =1;this.tabContentOffset =0; });this.applyPageVisiblePollingAndAnimations(); },120); }这里最值得学习的不是animateTo,而是前面的几行:
this.stopDecorAndSecondaryTimers();this.stopCpuUsagePolling();this.stopRefreshRatePolling();this.stopOverviewDashboardPolling();这说明项目把“视觉动效”和“资源管理”放在一起考虑。切走页面后,动画和轮询应该停止,否则用户看不到,系统还在刷新@State,这对工具类应用来说非常浪费。
六、沉浸光感:卡片不是贴图,而是状态表达
CheckMe的首页不是单纯使用静态卡片,而是将 CPU、内存、存储、电池、网络等状态做成实时数据图表。AdvancedDashboard.ets中有大量 Canvas 绘制逻辑,比如平滑折线和面积图:
private drawSmoothArea( ctx: CanvasRenderingContext2D,points: ChartPoint[], baselineY: number ):void{if(points.length===0) {return; }constsegments: SmoothSegment[] = buildSmoothSegments(points); ctx.beginPath(); ctx.moveTo(points[0].x,points[0].y);for(let i =0; i < segments.length; i++) {constsegment = segments[i]; ctx.bezierCurveTo( segment.cp1x, segment.cp1y, segment.cp2x, segment.cp2y, segment.x, segment.y ); } ctx.lineTo(points[points.length-1].x, baselineY); ctx.lineTo(points[0].x, baselineY); ctx.closePath(); }这种实现比普通进度条更适合设备监控,因为设备状态不是一个静态值,而是连续变化的趋势。
比如 CPU 使用率:
- 当前值告诉用户“现在怎么样”
- 曲线告诉用户“刚才发生了什么”
- 峰值和波动告诉用户“是不是异常”
这就是“光感仪表盘”的价值:它不只是装饰,而是让数据更容易被感知。📈
七、主题适配:浅色和深色模式都要舒服
项目中单独抽出了ThemeHelper,集中管理图表、卡片、文本、状态颜色。
publicgetCardBackground():string{returnthis.isDarkMode ?'#1E293B':'#FFFFFF'; }publicgetCardShadowColor():string{returnthis.isDarkMode ?'rgba(0,0,0,0.25)':'rgba(15,23,42,0.09)'; }publicgetSoftTrendPrimary():string{returnthis.isDarkMode ?'#60A5FA':'#2F7CF6'; }这样做的好处是:
- 页面不会到处散落颜色值
- Canvas 图表也能跟随深浅色模式变化
- 后续调整视觉风格时成本更低
尤其是 Canvas 绘图,如果颜色直接写死,很容易出现深色模式下看不清、浅色模式下太刺眼的问题。
八、实时体验背后的生命周期控制
视觉体验要高级,不能只看“动起来”。真正的关键是:该动的时候动,不该动的时候停。
AdvancedDashboard中的轮询逻辑是这样设计的:
privatestartPolling(): void {if(!this.isComponentVisible || !AppLifecycleManager.getInstance().isAppInForeground()) {return; }this.stopPolling(); voidthis.loadMetrics();this.pollTimer = setInterval(() => {if(this.isComponentVisible && AppLifecycleManager.getInstance().isAppInForeground()) { voidthis.loadMetrics(); } },2000)asnumber; }这段代码体现了一个原则:
实时刷新必须服从页面可见性和应用前后台状态。
项目还用AppLifecycleManager统一分发前后台状态:
publicsetForegroundState(isForeground:boolean): void {if(this.isInForeground !== isForeground) {this.isInForeground = isForeground;this.notifyListeners(); } }这样的处理让首页实时仪表盘既有“活着”的感觉,也不会在后台持续消耗资源。
九、文章小结
这篇文章从视觉体验角度拆解了CheckMe的几个实现点:
- 用
setWindowLayoutFullScreen和系统栏属性做沉浸式页面基础 - 用
getWindowAvoidArea适配底部安全区 - 用底部 Tab 降低工具类应用的页面切换成本
- 用 Canvas 曲线和柔和色彩表达实时设备状态
- 用生命周期管理保证动画和轮询只在需要时运行
如果要给这个主题取一个关键词,我觉得不是“炫酷”,而是:克制的实时感。
工具类应用不适合做过度花哨的动效,但非常适合用轻量动画、光感图表、状态色和安全区适配提升专业感。CheckMe的这套实现思路,也可以迁移到性能监控、网络诊断、电池健康、设备管理等 HarmonyOS 应用中。
参考资料
- 华为开发者文档:Implementing the Avoid Area for the Window-top Control Bar
- 华为开发者文档:Form Kit