【OpenHarmony/HarmonyOs 】数学视界实战:悬浮导航栏、沉浸光感与全新交互体验
【OpenHarmony/HarmonyOs 】数学视界实战:悬浮导航栏、沉浸光感与全新交互体验
项目类型:OpenHarmony / HarmonyOS ArkTS 数学学习应用
项目名称:数学视界
关键词:ArkUI、Tabs、沉浸式视觉、深色模式、底部导航、点击动效、响应式布局 ✨
一、为什么要做“轻沉浸”的数学学习体验?
数学类应用很容易做成“工具集合”:计算器、公式表、单位换算、题库练习各自独立,页面之间缺少情绪连接。我的这个项目希望让用户打开应用时,不只是看到一堆功能按钮,而是进入一个有节奏、有反馈、有学习目标的空间。
所以在 UI 设计上,我重点做了三件事:
- 🌈 用暖色光感做首页视觉入口,让学习氛围更轻松;
- 🧭 用底部导航承载高频页面:首页、挑战、成就、收藏、我的;
- 👆 给按钮、卡片、弹窗都加上统一点击反馈,让每一次操作都有回应。
最终效果上,应用并不是简单堆 ArkUI 组件,而是围绕“今日目标 -> 功能探索 -> 学习数据 -> 成就反馈”形成一个完整体验闭环。
二、项目首页结构:Tabs 承载五大核心场景
项目的主入口在entry/src/main/ets/pages/Index.ets,首页没有使用多个独立 Ability,而是通过Tabs把几个主要场景组织到同一个主页面里。
核心结构如下:
Tabs({barPosition: BarPosition.End,controller:this.tabController}){TabContent(){ this.buildHomePage()} .tabBar(this.buildBottomTab(0, '🏠', '首页'))TabContent(){ this.buildChallengeTab()} .tabBar(this.buildBottomTab(1, '🎯', '挑战'))TabContent(){ this.buildAchievementTab()} .tabBar(this.buildBottomTab(2, '🏆', '成就'))TabContent(){ this.buildFavoritesTab()} .tabBar(this.buildBottomTab(3, '💖', '收藏'))TabContent(){MyPage()} .tabBar(this.buildBottomTab(4, '👤', '我的')) } .barHeight(56).barBackgroundColor(this.getColor('#FFFDF7', '#1C1C1E')) .expandSafeArea([SafeAreaType.SYSTEM], [SafeAreaEdge.BOTTOM])这里有几个细节很关键:
barPosition: BarPosition.End:把导航固定在底部,更符合移动端单手操作习惯。TabsController:为后续主动切换 Tab 留出扩展空间。expandSafeArea:处理底部安全区域,让导航不会被系统手势区域遮挡。- 自定义
tabBar:不用系统默认样式,而是自己绘制图标、文字、角标。
三、底部导航栏:不只是切换页面,还要承担状态表达
数学视界的底部导航有一个小设计:收藏 Tab 会显示收藏数量角标。这样用户不用进入收藏页,也能知道自己积累了多少内容。
@Builder buildBottomTab(index:number,icon:string,title:string){Column({space: 2 }){Stack({alignContent: Alignment.Center }){Text(icon).fontSize(24)if(index===3&&AppState.favorites.length >0) {Text(AppState.favorites.length> 99 ? '99+' : AppState.favorites.length.toString()) .fontSize(9).fontColor('#FFFFFF').backgroundColor(this.isDarkMode? '#FF7A8A' : '#FF6B9D').borderRadius(10).padding({ left:3, right:3, top:1, bottom:1}) .offset({ x:14, y: -10}) } }Text(title).fontSize(11).fontWeight(this.currentIndex===index? FontWeight.Bold : FontWeight.Normal).fontColor(this.currentIndex===index?getThemeColors().primary : this.getColor('#AAAAAA', '#666666')) .textAlign(TextAlign.Center)} }这个写法的好处是:
- 当前选中态通过字重和颜色表达,用户不会迷路;
- 收藏数量直接关联
AppState.favorites.length,数据变化会反映到 UI; - 深色模式下角标颜色也会调整,不会出现浅色主题好看、深色主题刺眼的问题。
四、沉浸光感:从首页头图到进度卡片
首页最醒目的区域是顶部标题和今日进度。它们都使用了比较明亮的暖粉色,形成统一的品牌感。
Text('🌟 数学视界').fontSize(this.isLargeScreen? 26 : 22).fontWeight(FontWeight.Bold).fontColor('#FFFFFF')Text('探索数学的奇妙世界').fontSize(this.isLargeScreen? 14 : 12).fontColor('rgba(255,255,255,0.85)')进度卡片则把“今日目标”直接视觉化:
Column() .width(Math.min(AppState.studyData.todayCount / AppState.studyData.dailyGoal *100,100) +'%') .height('100%') .backgroundColor('#FFFFFF') .borderRadius(10)这里我没有把学习数据藏在“我的”页面里,而是放在首页首屏。原因很简单:学习类产品最重要的是持续激励,用户一打开就应该知道“今天还差多少”。
页面文案也做了轻量反馈:
Text( AppState.studyData.todayCount >= AppState.studyData.dailyGoal ?'🎉 太棒了!今日目标达成!':'💪 再完成 '+ Math.max(AppState.studyData.dailyGoal - AppState.studyData.todayCount,0) +' 次即可达成今日目标!')这类微文案虽然代码很短,但对学习应用的体验很重要。它让进度条不只是一个数字,而是一个“被鼓励”的瞬间。
五、统一点击动效:让交互有手感
项目中很多按钮、卡片、弹窗关闭按钮都用了同一套点击反馈。核心思路是:点击时记录当前元素的id,短时间内让该元素缩小和降低透明度。
pulseAnim(id:string):void{this.animId= idif(this.animTimer>=0)clearTimeout(this.animTimer)this.animTimer=setTimeout(():void=>{this.animId=''},200)asnumber}animScale(id:string):number{returnthis.animId=== id ?AnimScale.PRESSED:1}animAlpha(id:string):number{returnthis.animId=== id ?AnimAlpha.PRESSED:1}在功能卡片上使用:
.scale({ x: this.animScale('mod_'+ title), y: this.animScale('mod_'+ title) }).opacity(this.animAlpha('mod_'+ title)).animation({ duration: AnimDuration.NORMAL, curve: Curve.EaseOut}).onClick(() => { this.pulseAnim('mod_'+ title) action() })体验上会变成这样:
- 用户点卡片时,卡片轻微下压;
- 点击反馈在 200ms 左右结束;
- 动画不会抢戏,但会让页面更“活”。
我把这类动效抽到了AnimationUtils.ets,统一维护时长、缩放值和透明度:
export const AnimDuration={FAST:100,NORMAL:150,SLOW:300,VERY_SLOW:500,}export const AnimScale={PRESSED:0.93,SMALL:0.95,LARGE:1.05,}export const AnimAlpha={PRESSED:0.82,DISABLED:0.5,}这样后续新增页面时,不需要每个页面都重新想一套动画参数,整套应用的手感会保持一致。
六、深色模式:不是简单反色,而是主题系统
项目中有一个统一的ThemeManager,用于管理浅色主题、深色主题、图标背景映射和响应式工具。
exportconstLightTheme: ThemeColors = { primary:'#FF7A8A', background:'#FFF8F0', surface:'#FFFFFF', textPrimary:'#2C3E50', tabBarBackground:'#FFFDF7', }exportconstDarkTheme: ThemeColors = { primary:'#FF7A8A', background:'#000000', surface:'#1C1C1E', textPrimary:'#F2F2F7', tabBarBackground:'#1C1C1E', }深色模式最容易踩坑的地方是图标背景。浅色模式下很多柔和色块很舒服,但放到黑色背景上会显得发灰或发脏。因此项目里单独维护了图标背景映射:
exportfunctiongetIconBgColor(lightColor:string,isDark:boolean):string{if(!isDark) return lightColor const mapping = IconBgColorMap[lightColor]if(mapping) return mapping.dark return adjustColorForDark(lightColor)}这样首页、我的页面、挑战页都可以复用:
getIconBg(bgColor:string):string{ return getIconBgColor(bgColor,this.isDarkMode)}七、状态栏同步:让系统区域也融入视觉
在EntryAbility.ets中,应用会读取系统配置并更新主题状态,同时设置状态栏颜色。
onConfigurationUpdate(newConfig: Configuration): void { ThemeManager.getInstance().applyColorMode(newConfig.colorMode)this.applyStatusBar() }privateapplyStatusBar(): void {if(this.mainWindow ===null)returnconstisDark = ThemeManager.getInstance().isDark()this.mainWindow.setWindowSystemBarProperties({ statusBarColor: isDark ?'#1C1C1E':'#FF9A8B', statusBarContentColor:'#FFFFFF'}) }这一步会让应用不只是内容区变色,连状态栏也与页面主色协调起来。对于“沉浸光感”来说,这种细节非常重要。
八、响应式适配:手机和平板都要能用
项目在ThemeManager.ets中封装了ResponsiveUtils:
exportclassResponsiveUtils{privatestaticreadonlyBASE_WIDTH=375staticgetScreenWidth(): number {returngetScreenSize().width }staticisLargeScreen():boolean{returngetScreenSize().width >=600}staticgetGridColumns(): number {returnResponsiveUtils.isLargeScreen() ?4:2} }首页里根据屏幕宽度调整字号、间距和内容宽度:
getisLargeScreen() {returnthis.screenWidth >=600}这让应用在手机上保持紧凑,在平板上则不会显得内容过小。
九、实现总结
这篇文章对应的主题是“悬浮导航栏、沉浸光感、全新视觉与交互体验”。在数学视界项目里,我主要通过以下方式实现:
- 🧭 使用
Tabs + 自定义 tabBar构建底部导航; - 🌈 使用暖色头部、进度卡片和轻渐变营造学习氛围;
- 👆 抽象
AnimDuration / AnimScale / AnimAlpha,统一点击动效; - 🌙 使用
ThemeManager管理深色模式和图标背景映射; - 📱 使用
ResponsiveUtils处理手机和平板适配; - 🧩 把首页、挑战、成就、收藏、我的组成完整学习闭环。
如果你也在做 OpenHarmony / HarmonyOS 应用,不建议一开始就追求复杂动画。先把导航、主题、点击反馈、状态栏、安全区域这些基础体验做好,应用的完成度会立刻提升一大截。🚀
