【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 做全面屏沉浸式布局。

十、后续可以继续优化的方向

如果继续打磨这套视觉交互,我会考虑:

  1. 底部导航加入半透明或模糊背景,让“悬浮感”更强;
  2. 对不同物理分类使用独立主题色,例如力学蓝、电磁橙、光学紫;
  3. Canvas 动画加入帧循环,让波动、干涉、电场线更动态;
  4. 模型详情页增加“观察记录”,帮助学生写实验结论;
  5. 适配平板横屏,把左侧分类和右侧模型详情做成双栏布局。

结语

「物理视界」这个项目让我感觉,学习类 App 也可以做得很有质感。
悬浮导航栏负责降低操作成本,沉浸式 Header 负责建立场景感,Canvas 光感负责把物理知识从文字变成可观察的现象。

对于 OpenHarmony/HarmonyOS 开发来说,这套方案并不依赖复杂库,核心就是 ArkUI 的组件组合、状态管理和 Canvas 绘制。把这些基础能力用好,就能做出一个既实用又有探索感的学习应用。🚀

img

Logo

开源鸿蒙跨平台开发社区汇聚开发者与厂商,共建“一次开发,多端部署”的开源生态,致力于降低跨端开发门槛,推动万物智联创新。

更多推荐