一、HarmonyOS 导航体系概览
HarmonyOS NEXT 推荐使用Navigation+NavPathStack替代传统的router进行页面导航。Navigation 提供了统一的导航容器,支持栈管理、转场动画和路由拦截,是构建复杂应用的标配方案。
二、主页面导航架构
MeCharts 的主页面Index.ets采用了Navigation + Tabs的组合架构:
@Entry@ComponentV2struct Index{privatetabController:TabsController=newTabsController();@LocalcurrentIndex:number=0;privatepageContext:PageContext=AppStorage.get('pageContext')asPageContext;privateappPathInfo:NavPathStack=this.pageContext.navPathStack;aboutToAppear():void{BackupManagerService.getInstance().initializeBackupManager();letcallback:Callback<emitter.EventData>=(eventData:emitter.EventData)=>{this.tabController.changeIndex(2);};emitter.on(CommonConstants.EVENT_ID,callback);}aboutToDisappear():void{emitter.off(CommonConstants.EVENT_ID);}build(){Navigation(this.appPathInfo){Column(){this.TabComponent()CustomTabBar({currentIndex:this.currentIndex,tabBarChange:(currentIndex:number)=>{this.currentIndex=currentIndex;}})}.height('100%').width('100%')}.hideTitleBar(true).mode(NavigationMode.Stack).height('100%').width('100%')}@BuilderTabComponent(){Tabs({controller:this.tabController,index:this.currentIndex}){TabContent(){HomeView()}.height('100%')TabContent(){ConsultView()}.height('100%')TabContent(){MineView()}.height('100%')}.onAnimationStart((index:number,targetIndex:number)=>{this.currentIndex=targetIndex;}).animationMode(AnimationMode.NO_ANIMATION).barWidth(0).barHeight(0).scrollable(false).layoutWeight(1).width('100%')}}2.1 关键设计点
1)Navigation 与 NavPathStack 绑定
privateappPathInfo:NavPathStack=this.pageContext.navPathStack;Navigation(this.appPathInfo){// 内容区}Navigation 组件接收NavPathStack作为参数,所有的页面跳转都通过操作这个栈来实现。
2)隐藏原生 TabBar
.barWidth(0).barHeight(0).scrollable(false)将 Tabs 的原生导航栏宽高设为 0,禁用手势滑动,完全由自定义 TabBar 控制切换。
3)事件驱动的跨组件通信
aboutToAppear():void{letcallback:Callback<emitter.EventData>=(eventData:emitter.EventData)=>{this.tabController.changeIndex(2);// 切换到"我的"Tab};emitter.on(CommonConstants.EVENT_ID,callback);}使用emitter(HarmonyOS 的事件总线)监听来自其他页面的事件。例如,AI 咨询页检测到用户未登录时,发送事件切换到"我的"Tab 触发登录。
三、自定义 TabBar 组件
MeCharts 没有使用 Tabs 自带的导航栏,而是实现了一个功能更丰富的自定义 TabBar:
@Componentexportstruct CustomTabBar{@StorageProp('GlobalInfoModel')globalInfoModel:GlobalInfoModel=AppStorage.get('GlobalInfoModel')!;@StorageProp('BlurRenderGroup')blurRenderGroup:boolean=false;@Prop@RequirecurrentIndex:number;tabBarChange:(index:number)=>void=(index:number)=>{};@BuilderTabItemBuilder(tabBar:TabBarData){Column(){SymbolGlyph(tabBar.icon).fontSize($r('sys.float.Title_M')).fontColor(tabBar.id===this.currentIndex?[$r('sys.color.interactive_active')]:[$r('sys.color.font_tertiary')]).renderingStrategy(SymbolRenderingStrategy.MULTIPLE_OPACITY).symbolEffect(newBounceSymbolEffect(EffectScope.LAYER,EffectDirection.UP),tabBar.id===this.currentIndex)Text(tabBar.title).fontSize($r('sys.float.Caption_M')).margin({top:$r('sys.float.padding_level1')}).fontWeight(FontWeight.Medium).fontColor(tabBar.id===this.currentIndex?$r('sys.color.interactive_active'):$r('sys.color.font_tertiary'))}.width('100%').height(50).onClick(()=>{if(this.currentIndex!==tabBar.id){this.tabBarChange(tabBar.id);}}).alignItems(HorizontalAlign.Center).justifyContent(FlexAlign.Center)}build(){Flex({direction:newBreakpointType({sm:FlexDirection.ColumnReverse,md:FlexDirection.ColumnReverse,lg:FlexDirection.Row,}).getValue(this.globalInfoModel.currentBreakpoint),alignItems:ItemAlign.Center,}){Flex({direction:newBreakpointType({sm:FlexDirection.Row,md:FlexDirection.Row,lg:FlexDirection.Column,}).getValue(this.globalInfoModel.currentBreakpoint),alignItems:ItemAlign.Center,justifyContent:FlexAlign.SpaceAround,}){ForEach(TABS_LIST,(item:TabBarData)=>{this.TabItemBuilder(item)},(item:TabBarData)=>JSON.stringify(item)+this.currentIndex)}.margin({bottom:newBreakpointType({sm:this.globalInfoModel.naviIndicatorHeight,md:this.globalInfoModel.naviIndicatorHeight,lg:0,}).getValue(this.globalInfoModel.currentBreakpoint),})}.backgroundBlurStyle(BlurStyle.COMPONENT_THICK).renderGroup(this.blurRenderGroup)}}3.1 核心技术点
1)SymbolGlyph 系统图标 + 动效
SymbolGlyph(tabBar.icon).renderingStrategy(SymbolRenderingStrategy.MULTIPLE_OPACITY).symbolEffect(newBounceSymbolEffect(EffectScope.LAYER,EffectDirection.UP),tabBar.id===this.currentIndex)HarmonyOS 提供了丰富的系统 Symbol 图标,支持多色渲染策略和弹性动效(BounceSymbolEffect),在 Tab 切换时呈现精致的动画反馈。
2)响应式布局
direction:newBreakpointType({sm:FlexDirection.ColumnReverse,// 小屏:底部水平排列md:FlexDirection.ColumnReverse,// 中屏:底部水平排列lg:FlexDirection.Row,// 大屏:左侧垂直排列}).getValue(this.globalInfoModel.currentBreakpoint)使用自定义的BreakpointType工具类,根据当前断点动态决定布局方向。小屏设备(手机)TabBar 在底部水平排列;大屏设备(平板)TabBar 在左侧垂直排列。
3)毛玻璃效果
.backgroundBlurStyle(BlurStyle.COMPONENT_THICK)TabBar 使用COMPONENT_THICK模糊样式,在滚动内容透过时呈现毛玻璃效果,提升视觉层次感。
4)底部安全区域避让
.margin({bottom:newBreakpointType({sm:this.globalInfoModel.naviIndicatorHeight,// 手机:留出导航条空间md:this.globalInfoModel.naviIndicatorHeight,lg:0,// 平板:无需避让}).getValue(this.globalInfoModel.currentBreakpoint),})四、TabBar 数据模型
TabBar 的数据来源于TabBarModel.ets:
exportinterfaceTabBarData{id:TabBarType;title:ResourceStr;icon:Resource;}exportconstTABS_LIST:TabBarData[]=[{id:TabBarType.HOME,icon:$r('sys.symbol.archivebox_fill'),title:$r('app.string.tab_home'),},{id:TabBarType.CONSULT,icon:$r('sys.symbol.discover_fill'),title:$r("app.string.tab_consult"),},{id:TabBarType.MINE,icon:$r('sys.symbol.person_crop_circle_fill_1'),title:$r('app.string.tab_mine'),},]使用sys.symbol.*引用系统图标,使用app.string.*引用应用字符串资源,便于国际化。
五、PageContext 导航封装
MeCharts 将NavPathStack操作封装为PageContext,提供统一的导航接口:
exportinterfaceRouterParam{routerName:string;param?:object;onReturn?:(data?:object)=>void;}exportclassPageContextimplementsIPageContext{privatereadonlypathStack:NavPathStack;privatereturnCallback?:(data?:object)=>void;publicopenPage(data:RouterParam,animated:boolean=true):void{this.returnCallback=data.onReturn;this.pathStack.pushPath({name:data.routerName,param:data.param,},animated);}publicpopPage(animated:boolean=true,returnData?:object):void{if(this.returnCallback){this.returnCallback(returnData);this.returnCallback=undefined;}this.pathStack.pop(animated);}publicreplacePage(data:RouterParam,animated:boolean=true):void{this.pathStack.replacePath({name:data.routerName,param:data.param,},animated);}}5.1 页面跳转示例
从首页跳转到 ABC 量表页面:
this.pageContext.openPage({param:{title:'ABC量表',}asResultParams,routerName:'AbcListPage',},true);从答题页面替换为结果页面(不可返回):
this.pageContext.replacePage({routerName:'ResultPage',param:{score:totalScore,type:FormType.TYPE_ABC,title:this.title}asResultParams,},true);5.2 NavDestination 接收参数
子页面通过NavDestination的onReady回调接收参数:
NavDestination(){// 页面内容}.onReady((ctx:NavDestinationContext)=>{constparams=ctx.pathInfo.paramasResultParams;this.title=params.titleasstring;this.type=params.typeasnumber;})5.3 路由注册
每个子页面需要在路由配置文件中注册,并导出 Builder 函数:
@BuilderexportfunctionAbcListPageBuilder(){AbcListPage()}这个 Builder 函数在router_map.json中被引用,Navigation 在路由跳转时通过它创建页面实例。
六、小结
本篇讲解了 Navigation + NavPathStack 的导航体系实现,以及自定义 TabBar 的响应式布局与系统动效。MeCharts 通过PageContext封装导航操作,使页面跳转逻辑简洁统一。下一篇将深入首页(HomeView)的布局实现,讲解 Swiper 轮播与 Grid 网格布局。