【OpenHarmony/HarmonyOs 】悬浮导航栏与沉浸光感:物理视界的全新视觉交互体验
【OpenHarmony/HarmonyOs 】悬浮导航栏与沉浸光感:物理视界的全新视觉交互体验
本文基于我的 OpenHarmony/HarmonyOS 项目「物理视界 PhysicsVision」整理。项目是一个面向初高中物理学习的 ArkTS 应用,包含 28 个物理实验模型、物理公式计算器、知识挑战、成就系统、收藏与最近浏览等模块。
这一篇主要分享:如何在教育类应用中做出更轻盈的悬浮导航栏、沉浸式顶部区域、Canvas 光感模拟,以及更顺滑的页面交互体验。✨
一、为什么物理学习 App 更需要“沉浸感”?
传统学习类应用很容易做成列表工具:打开、搜索、点击、看内容。功能可以用,但体验不一定有吸引力。
「物理视界」的目标不是单纯堆知识点,而是让学生像进入一个实验室一样学习:
- 🔬 打开首页,先看到学习进度、分类、推荐模型;
- 🌈 进入光学、电磁学模型后,通过 Canvas 看到物理现象;
- 🎚️ 调整参数后,画面和数据实时变化;
- 🧭 底部悬浮导航始终可达,学习路径更清晰;
- 🌙 深色模式下,Canvas、卡片、文本仍保持可读性。
所以界面设计的关键词是:沉浸、轻量、即时反馈、可探索。
二、项目首页结构:Stack 承载内容与悬浮导航
项目首页 Index.ets 使用了 Tabs 承载六个主模块:
- 实验室
- 计算器
- 挑战
- 成就
- 收藏
- 设置
不过这里没有使用系统默认 TabBar,而是把 Tabs 的默认栏隐藏,再用 Stack 在底部叠加自定义导航栏。这样做的好处是:
- 导航栏视觉完全可控;
- 可以做阴影、缩放、透明度动画;
- 可以与安全区结合,适配全面屏;
- 内容区保持完整沉浸感。
关键结构如下:
Stack({ alignContent: Alignment.Bottom }) {
Tabs({ barPosition: BarPosition.End, controller: this.controller }) {
TabContent() { LabTab() }
TabContent() { PhysicsCalculatorComponent() }
TabContent() { ChallengeTab() }
TabContent() { AchievementTab() }
TabContent() { FavoritesTab() }
TabContent() { SettingsTab() }
}
.barHeight(0)
.scrollable(false)
.width('100%')
.height('100%')
Row() {
this.TabItem(0)
this.TabItem(1)
this.TabItem(2)
this.TabItem(3)
this.TabItem(4)
this.TabItem(5)
}
.width('100%')
.height(64)
.backgroundColor($r('app.color.bg_nav'))
.shadow({ radius: 20, color: 'rgba(0,0,0,0.08)', offsetY: -2 })
.expandSafeArea([SafeAreaType.SYSTEM], [SafeAreaEdge.BOTTOM])
}
这里的设计思路很清楚:Tabs 只负责页面切换,自定义 Row 负责导航表现。在复杂项目中,我更推荐这种写法,因为页面结构和视觉控制会更干净。
三、悬浮导航栏:用状态驱动颜色和动画
自定义导航项由图标和文字组成,选中态使用蓝色强调,未选中态使用资源色。点击时更新 currentIndex,并调用 TabsController 切换页面。
@Builder TabItem(idx: number) {
Column({ space: 3 }) {
Image(this.tabs[idx].icon)
.width(22)
.height(22)
.fillColor(this.currentIndex === idx ? '#1A73E8' : $r('app.color.icon_inactive'))
.animation({ duration: 200, curve: Curve.EaseOut })
Text(this.tabs[idx].label)
.fontSize(9)
.fontWeight(FontWeight.Medium)
.fontColor(this.currentIndex === idx ? '#1A73E8' : $r('app.color.icon_inactive'))
.animation({ duration: 200, curve: Curve.EaseOut })
}
.layoutWeight(1)
.justifyContent(FlexAlign.Center)
.onClick(() => {
animateTo({ duration: 250, curve: Curve.EaseOut }, () => {
this.currentIndex = idx
})
this.controller.changeIndex(idx)
})
}
这个导航栏虽然代码不复杂,但交互体验比较完整:
- 选中态图标和文字同步变色;
- 点击切换有 200ms 级别的轻动画;
- 六个入口宽度平均,适合学习类 App 高频切换;
- 不依赖默认 TabBar,因此更容易扩展成悬浮圆角、毛玻璃或半透明样式。
四、启动动效:让导航栏“浮”出来
在 aboutToAppear 中,项目通过 navAnim 控制底部导航的缩放和透明度。
aboutToAppear(): void {
PersistentStorage.persistProp('favorites', '')
PersistentStorage.persistProp('highScore', 0)
PersistentStorage.persistProp('totalPlayed', 0)
PersistentStorage.persistProp('visitedModels', '')
setTimeout(() => {
animateTo({ duration: 600, curve: curves.springCurve(0, 1, 328, 28) }, () => {
this.navAnim = true
})
}, 500)
}
底部导航栏再根据 navAnim 做缩放与渐入:
.scale({ x: this.navAnim ? 1 : 0.95, y: this.navAnim ? 1 : 0.95 })
.opacity(this.navAnim ? 1 : 0)
这种动画很适合首页:不是很夸张,但能让用户感到界面是“活”的。对于学习产品来说,动画不应该抢注意力,而应该让操作更自然。
五、沉浸式顶部区域:全屏布局 + 安全区适配
项目在 EntryAbility.ets 中把主窗口设置为全屏布局:
windowStage.getMainWindow().then((win: window.Window) => {
win.setWindowLayoutFullScreen(true)
})
然后在各个页面 Header 中主动扩展顶部安全区,例如实验室首页:
.padding({ left: 20, right: 20, top: 60, bottom: 16 })
.linearGradient({
angle: 135,
colors: [['#1A73E8', 0], ['#1565C0', 0.5], ['#0D47A1', 1]]
})
.expandSafeArea([SafeAreaType.SYSTEM], [SafeAreaEdge.TOP])
这样做出来的效果是:顶部背景色延伸到状态栏区域,不会出现割裂的白边。
对于实验室、挑战、成就、收藏这类主页面,沉浸式 Header 能明显提升整体完成度。
六、沉浸光感:Canvas 让光学模型真的“发光”
在「光的干涉」模型中,项目用 Canvas 绘制光源、双缝、光波和干涉条纹。颜色不是写死的,而是根据波长动态计算。
getColorFromWavelength(): string {
let wl: number = this.wavelength
if (wl < 440) return '#8B00FF'
if (wl < 490) return '#0000FF'
if (wl < 510) return '#00BFFF'
if (wl < 550) return '#00FF00'
if (wl < 590) return '#FFFF00'
if (wl < 630) return '#FF8C00'
return '#FF0000'
}
绘制光源时,还加入了阴影光晕:
ctx.fillStyle = this.getColorFromWavelength()
ctx.beginPath()
ctx.arc(sourceX, cy, 10, 0, Math.PI * 2)
ctx.fill()
ctx.shadowColor = this.getColorFromWavelength()
ctx.shadowBlur = 15
ctx.fill()
ctx.shadowBlur = 0
配合参数调节,用户拖动波长滑块时,颜色会从紫、蓝、绿、黄、橙逐步过渡到红色。🌈
Slider({ value: this.wavelength, min: 380, max: 700, step: 10 })
.trackColor($r('app.color.slider_track'))
.selectedColor(this.getColorFromWavelength())
.onChange((v: number) => {
this.wavelength = v
if (this.canvasReady) this.drawScene()
})
这就是教育应用中“光感”的价值:不是装饰,而是把抽象知识可视化。
七、实时反馈:参数、公式、图像同步变化
光的干涉模型中,条纹间距由公式 Δy = λL/d 计算:
getFringeSpacing(): number {
return (this.wavelength * 1e-9 * this.screenDist) / (this.slitSpacing * 1e-3) * 1000
}
页面同时展示:
- 当前波长;
- 缝间距;
- 屏距;
- 条纹间距;
- Canvas 中的干涉条纹。
这种“滑块一动,公式和图像同时变化”的设计,对物理学习很重要。学生不只是看到结论,还能观察变量之间的关系。
八、深色模式:资源色 + Canvas 自适应
项目提供了浅色和深色两套资源颜色,例如:
{
"name": "bg_page",
"value": "#F7F8FA"
}
深色模式中对应为:
{
"name": "bg_page",
"value": "#121212"
}
物理模型页面中也根据系统颜色模式调整 Canvas 绘制颜色:
@StorageProp('colorMode') @Watch('onColorModeChange')
colorMode: number = ConfigurationConstant.ColorMode.COLOR_MODE_LIGHT
private isDark(): boolean {
return this.colorMode === ConfigurationConstant.ColorMode.COLOR_MODE_DARK
}
private canvasBg(): string {
return this.isDark() ? '#1E1E1E' : '#FAFAFA'
}
onColorModeChange(): void {
if (this.canvasReady) this.drawScene()
}
这样能避免一个常见问题:外层页面变暗了,但 Canvas 仍然刺眼或不可读。
九、交互体验小结
这套实现给我的启发是:OpenHarmony/HarmonyOS 的 ArkUI 很适合做“状态驱动型”的教育应用。
项目中几个比较值得复用的实践:
- 🧭 用
Stack + Tabs + 自定义 Row实现悬浮导航; - 🎬 用
animateTo + springCurve增强页面生命力; - 🌈 用 Canvas 表达光学、电磁学等动态现象;
- 🎚️ 用 Slider 连接参数、公式和图像;
- 🌙 用资源色和
colorMode兼顾深色模式; - 📱 用
expandSafeArea做全面屏沉浸式布局。
十、后续可以继续优化的方向
如果继续打磨这套视觉交互,我会考虑:
- 底部导航加入半透明或模糊背景,让“悬浮感”更强;
- 对不同物理分类使用独立主题色,例如力学蓝、电磁橙、光学紫;
- Canvas 动画加入帧循环,让波动、干涉、电场线更动态;
- 模型详情页增加“观察记录”,帮助学生写实验结论;
- 适配平板横屏,把左侧分类和右侧模型详情做成双栏布局。
结语
「物理视界」这个项目让我感觉,学习类 App 也可以做得很有质感。
悬浮导航栏负责降低操作成本,沉浸式 Header 负责建立场景感,Canvas 光感负责把物理知识从文字变成可观察的现象。
对于 OpenHarmony/HarmonyOS 开发来说,这套方案并不依赖复杂库,核心就是 ArkUI 的组件组合、状态管理和 Canvas 绘制。把这些基础能力用好,就能做出一个既实用又有探索感的学习应用。🚀

更多推荐



所有评论(0)