【OpenHarmony/HarmonyOs 】CheckMe 悬浮导航栏与沉浸光感体验实践:从系统栏到实时仪表盘的视觉升级
【OpenHarmony/HarmonyOs 】CheckMe 悬浮导航栏与沉浸光感体验实践:从系统栏到实时仪表盘的视觉升级
项目背景:本文基于我的 HarmonyOS 项目
CheckMe展开。它不是一个单纯的信息展示 Demo,而是一个面向真实设备状态监控的工具类应用,覆盖 CPU、内存、电池、网络、定位、媒体能力、硬件检测和桌面卡片等模块。
很多 HarmonyOS 工具类应用容易做成“表格列表”:能用,但缺少视觉记忆点。CheckMe 的目标不只是把设备参数列出来,而是让用户一打开应用,就能感受到一种实时、清爽、有层次的设备看板体验。✨
这篇文章重点拆解三个方向:
- 悬浮感底部导航栏
- 沉浸式系统栏与安全区适配
- 光感卡片、实时数据与交互动效
本文不重复展开服务卡片、设备信息采集、WorkScheduler 等工程链路,而是更聚焦 UI 体验与交互设计。
一、为什么工具类 App 也需要“视觉体验”
设备信息类应用的核心当然是数据准确,但只追求“把数据展示出来”是不够的。
用户真正打开这类应用时,往往有几个典型场景:
- 想看当前 CPU 是否过高
- 想快速确认电池、存储、网络状态
- 想进行一次硬件检测
- 想知道系统状态是否异常
这些场景都强调“快速判断”。因此页面设计应该让用户一眼看到重点,而不是陷入密密麻麻的参数列表。
CheckMe 采用的思路是:
- 首页做成实时 Dashboard,而不是普通设置页。
- 底部导航固定在可触达区域,降低切换成本。
- 系统栏、底栏和页面背景颜色保持一致,营造沉浸感。
- 图表和卡片使用柔和光感,不做过度炫技。
- 只让当前可见页面运行轮询和动画,避免为了“好看”牺牲性能。
这也是我认为 HarmonyOS 工具应用可以变得更高级的地方:不是堆控件,而是把系统能力、状态数据和视觉反馈组织成一个统一体验。
二、沉浸式系统栏:先把页面“铺满”
项目里系统栏处理放在 EntryAbility.ets 中。核心代码如下:
private applySystemBarToWindow(win: window.Window): void {
const isDark = this.isEffectiveDarkMode();
const barBackground = isDark ? '#FF1E293B' : '#FFFFFFFF';
const barContent = isDark ? '#FFFFFFFF' : '#FF000000';
const props: window.SystemBarProperties = {
statusBarColor: barBackground,
statusBarContentColor: barContent,
navigationBarColor: barBackground,
navigationBarContentColor: barContent
};
win.setWindowLayoutFullScreen(true);
win.setWindowSystemBarProperties(props);
}
这里有两个关键点:
setWindowLayoutFullScreen(true):让应用内容进入全屏布局语境。setWindowSystemBarProperties(props):主动控制状态栏、导航栏背景与文字颜色。
很多页面看起来“不高级”,原因不是组件写得不好,而是系统栏和页面割裂:页面是白色,导航栏是黑色;页面是深色,状态栏文字又不清楚。CheckMe 在浅色和深色模式下分别计算系统栏颜色,让系统区域和页面卡片区域保持一致。
官方文档中也提到窗口避让和沉浸布局相关能力,实际开发时需要结合设备形态、安全区域和系统栏属性综合处理。
三、底部导航栏:固定可触达,但不遮挡内容
CheckMe 的主界面采用底部导航,包含:
- 概览
- 硬件
- 工具
- 位置
- 媒体
底部导航代码片段:
@Builder
BottomTabBar() {
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() 获取系统导航指示条区域:
private updateBottomSafeInset(): void {
const ctx: common.UIAbilityContext = getContext(this) as common.UIAbilityContext;
window.getLastWindow(ctx).then((win: window.Window) => {
const avoid: window.AvoidArea =
win.getWindowAvoidArea(window.AvoidAreaType.TYPE_NAVIGATION_INDICATOR);
const pxH = avoid.bottomRect.height;
const d = display.getDefaultDisplaySync();
const dpi = 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 就结束,而是做了过渡动画,并在切换时停止不可见页面的轮询和装饰动画。
private onTabChange(tabKey: string): void {
if (tabKey === this.currentTab) {
return;
}
const previousTab: 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;
}
const segments: SmoothSegment[] = buildSmoothSegments(points);
ctx.beginPath();
ctx.moveTo(points[0].x, points[0].y);
for (let i = 0; i < segments.length; i++) {
const segment = 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,集中管理图表、卡片、文本、状态颜色。
public getCardBackground(): string {
return this.isDarkMode ? '#1E293B' : '#FFFFFF';
}
public getCardShadowColor(): string {
return this.isDarkMode
? 'rgba(0,0,0,0.25)'
: 'rgba(15,23,42,0.09)';
}
public getSoftTrendPrimary(): string {
return this.isDarkMode ? '#60A5FA' : '#2F7CF6';
}
这样做的好处是:
- 页面不会到处散落颜色值
- Canvas 图表也能跟随深浅色模式变化
- 后续调整视觉风格时成本更低
尤其是 Canvas 绘图,如果颜色直接写死,很容易出现深色模式下看不清、浅色模式下太刺眼的问题。
八、实时体验背后的生命周期控制
视觉体验要高级,不能只看“动起来”。真正的关键是:该动的时候动,不该动的时候停。
AdvancedDashboard 中的轮询逻辑是这样设计的:
private startPolling(): void {
if (!this.isComponentVisible ||
!AppLifecycleManager.getInstance().isAppInForeground()) {
return;
}
this.stopPolling();
void this.loadMetrics();
this.pollTimer = setInterval(() => {
if (this.isComponentVisible &&
AppLifecycleManager.getInstance().isAppInForeground()) {
void this.loadMetrics();
}
}, 2000) as number;
}
这段代码体现了一个原则:
实时刷新必须服从页面可见性和应用前后台状态。
项目还用 AppLifecycleManager 统一分发前后台状态:
public setForegroundState(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

更多推荐


所有评论(0)