07 首页 Tabs 与自定义 TabBar首页是应用的核心入口通常采用多 Tab 布局来组织不同功能模块。「柚兔学伴」的首页包含待办、字帖、口语、我的四个 Tab使用Tabs组件承载内容区域并通过自定义CustomTabBar替代原生 TabBar 以实现更灵活的样式和交互控制。本文将详细讲解实现细节。1. HomePageV2 装饰器与 Tabs 结构1.1 ComponentV2 LocalEntryComponentV2struct HomePage{privatetabController:TabsControllernewTabsController();LocalcurrentIndex:number0;privatepageContext:PageContextAppStorage.get(pageContext)asPageContext;privateappPathInfo:NavPathStackthis.pageContext.navPathStack;// ...}本项目首页使用了ComponentV2和Local装饰器这是 ArkUI V2 状态管理的新方案V1 装饰器V2 装饰器说明ComponentComponentV2V2 组件声明支持更高效的状态追踪StateLocal组件内部状态仅本组件可读写PropParam单向数据传递父→子LinkProviderConsumer跨层级双向数据流Local替代了State语义更明确——表示该状态仅在组件本地使用不会向子组件传递。1.2 Tabs 与 TabsControllerBuilderTabComponent(){Tabs({controller:this.tabController,index:this.currentIndex}){TabContent(){TodoView()}.height(100%)TabContent(){StrokeView()}.height(100%)TabContent(){ChatView()}.height(100%)TabContent(){MineView()}.height(100%)}.onAnimationStart((index:number,targetIndex:number){this.currentIndextargetIndex;}).animationMode(AnimationMode.NO_ANIMATION).barWidth(0).barHeight(0).scrollable(false).layoutWeight(1).width(100%)}核心设计TabsController控制器实例可通过tabController.changeIndex()编程式切换 Tab。index: this.currentIndex绑定当前选中索引实现状态驱动的 Tab 切换。.barWidth(0).barHeight(0)隐藏原生 TabBar因为我们要使用自定义 TabBar。.scrollable(false)禁止手势滑动切换避免与自定义 TabBar 的点击切换冲突。.onAnimationStartTab 切换动画开始时回调同步更新currentIndex。.animationMode(AnimationMode.NO_ANIMATION)禁用原生切换动画切换更干脆。1.3 整体布局build(){Navigation(this.appPathInfo){Column(){this.TabComponent()CustomTabBar({currentIndex:this.currentIndex,tabBarChange:(currentIndex:number){this.currentIndexcurrentIndex;}})}.height(100%).width(100%)LoadingView()}.backgroundColor($r(app.color.color_background)).hideTitleBar(true).mode(NavigationMode.Stack).height(100%).width(100%)}Column中先放Tabs通过layoutWeight(1)占满剩余空间再放CustomTabBar固定在底部形成经典的「内容区 底部导航栏」布局。2. TabBarModel数据模型定义2.1 TabBarType 枚举exportenumTabBarType{TODO0,STROKE1,SPEAK2,MINE3,}枚举值与Tabs中TabContent的顺序一一对应id即为 Tab 索引。2.2 TabBarData 接口exportinterfaceTabBarData{id:TabBarType;title:ResourceStr;icon:Resource;}id对应TabBarType枚举用于标识 Tab 和匹配选中状态。titleTab 标题类型为ResourceStr支持字符串或$r()资源引用。iconTab 图标类型为Resource引用系统 Symbol 图标资源。2.3 TABS_LIST 数据源exportconstTABS_LIST:TabBarData[][{id:TabBarType.TODO,icon:$r(sys.symbol.archivebox_fill),title:$r(app.string.tab_todo),},{id:TabBarType.STROKE,icon:$r(sys.symbol.paintbrush_fill),title:$r(app.string.tab_stroke),},{id:TabBarType.SPEAK,icon:$r(sys.symbol.English_square_fill),title:$r(app.string.tab_speak),},{id:TabBarType.MINE,icon:$r(sys.symbol.person_crop_circle_fill_1),title:$r(app.string.tab_mine),},]sys.symbol.*是 HarmonyOS 提供的系统 Symbol 图标库包含 1500 矢量图标无需额外引入图片资源支持多色渲染和动画效果。3. CustomTabBar自定义底部导航栏3.1 组件声明与状态Componentexportstruct CustomTabBar{StorageProp(GlobalInfoModel)globalInfoModel:GlobalInfoModelAppStorage.get(GlobalInfoModel)!;StorageProp(BlurRenderGroup)blurRenderGroup:booleanfalse;PropRequirecurrentIndex:number;StateisLoggedIn:booleanfalsetabBarChange:(index:number)void(index:number){};// ...}StorageProp(GlobalInfoModel)单向绑定AppStorage中的全局信息模型用于读取断点信息和设备参数。Prop Require currentIndexProp表示从父组件单向传入Require表示该参数为必传项。tabBarChange回调函数当用户点击 Tab 时通知父组件更新currentIndex。3.2 TabItemBuilder单个 Tab 项BuilderTabItemBuilder(tabBar:TabBarData){Column(){SymbolGlyph(tabBar.icon).fontSize($r(sys.float.Title_M)).fontColor(tabBar.idthis.currentIndex?[$r(app.color.app_primary)]:[$r(sys.color.font_tertiary)]).renderingStrategy(SymbolRenderingStrategy.MULTIPLE_OPACITY).symbolEffect(newBounceSymbolEffect(EffectScope.LAYER,EffectDirection.UP),tabBar.idthis.currentIndex)Text(tabBar.title).fontSize($r(sys.float.Caption_M)).margin({top:$r(sys.float.padding_level1)}).fontWeight(FontWeight.Medium).fontColor(tabBar.idthis.currentIndex?$r(app.color.app_primary):$r(sys.color.font_tertiary))}.width(100%).height(50).onClick((){this.isLoggedInUserInfoManager.isLoggedIn();if(!this.isLoggedIntabBar.id!0){this.login()}else{if(this.currentIndex!tabBar.id){this.tabBarChange(tabBar.id);}}}).alignItems(HorizontalAlign.Center).justifyContent(FlexAlign.Center)}核心要点SymbolGlyphHarmonyOS 系统图标组件支持多色渲染策略和动画效果。BounceSymbolEffect选中时的弹跳动画效果EffectScope.LAYER表示按图层动画EffectDirection.UP表示向上弹跳。颜色切换选中状态使用app_primary主题色未选中使用font_tertiary系统三级文字色。登录拦截未登录用户点击非首页 TabtabBar.id ! 0时触发登录流程而非切换 Tab。3.3 响应式布局BreakpointTypeFlex({direction:newBreakpointType({sm:FlexDirection.ColumnReverse,md:FlexDirection.ColumnReverse,lg:FlexDirection.Row,}).getValue(this.globalInfoModel.currentBreakpoint),alignItems:ItemAlign.Center,}){// 内层 FlexTab 按钮区域Flex({direction:newBreakpointType({sm:FlexDirection.Row,md:FlexDirection.Row,lg:FlexDirection.Column,}).getValue(this.globalInfoModel.currentBreakpoint),justifyContent:FlexAlign.SpaceAround,}){ForEach(TABS_LIST,(item:TabBarData){this.TabItemBuilder(item)},(item:TabBarData)JSON.stringify(item)this.currentIndex)}// ...}.size(newBreakpointTypeSizeOptions({sm:{width:100%,height:CommonConstants.TAB_BAR_HEIGHTthis.globalInfoModel.naviIndicatorHeight},md:{width:100%,height:CommonConstants.TAB_BAR_HEIGHTthis.globalInfoModel.naviIndicatorHeight},lg:{width:CommonConstants.TAB_BAR_WIDTH,height:100%},}).getValue(this.globalInfoModel.currentBreakpoint))BreakpointType是项目封装的响应式工具类根据当前断点sm/md/lg返回不同的值断点设备类型TabBar 方向TabBar 位置sm手机竖屏水平排列底部md平板竖屏水平排列底部lg平板横屏/折叠屏垂直排列侧边sm/mdTabBar 在底部宽度铺满高度 TabBar高度 导航条高度。lgTabBar 在左侧宽度固定高度铺满。3.4 毛玻璃效果.backgroundBlurStyle(BlurStyle.COMPONENT_THICK).renderGroup(this.blurRenderGroup)backgroundBlurStyle背景毛玻璃模糊效果COMPONENT_THICK为厚模糊风格。renderGroup控制是否开启渲染组合用于优化模糊效果的性能。4. Tab 切换的完整数据流用户点击 CustomTabBar 的 TabItem ↓ tabBarChange(tabBar.id) 回调 ↓ HomePage 中 this.currentIndex currentIndex ↓ Tabs({ index: this.currentIndex }) 感知变化 ↓ 切换到对应 TabContent关键CustomTabBar不直接控制Tabs而是通过回调将选中索引传回HomePage由HomePage统一管理状态。这种单向数据流确保了状态一致性。5. 登录拦截机制onClick((){this.isLoggedInUserInfoManager.isLoggedIn();if(!this.isLoggedIntabBar.id!0){this.login()}else{if(this.currentIndex!tabBar.id){this.tabBarChange(tabBar.id);}}})逻辑说明首先检查用户登录状态。如果未登录且点击的不是首页tabBar.id ! 0则触发登录弹窗。只有已登录或点击的是首页时才执行 Tab 切换。额外判断this.currentIndex ! tabBar.id避免重复触发相同 Tab 的切换。这种设计保证首页待办 Tab对所有用户开放而其他功能需要登录后才能使用。6. 总结知识点说明ComponentV2LocalV2 状态管理Local替代StateTabsTabsController多 Tab 容器与编程式控制.barWidth(0).barHeight(0)隐藏原生 TabBarSymbolGlyph系统矢量图标组件BounceSymbolEffect选中弹跳动画StorageProp单向绑定 AppStorage 全局状态Prop Require必传的单向数据传递BreakpointType响应式断点工具类tabBarChange回调子→父通信单向数据流登录拦截未登录用户点击非首页 Tab 触发登录自定义 TabBar 虽然需要编写更多代码但带来了完全的样式控制权、响应式布局适配能力和业务逻辑注入点如登录拦截是中大型应用的常见做法。