
本篇深入讲解 ImportantDays 的主入口页面 MainEntry包括 Navigation 容器、Tabs 底部导航、全屏沉浸式布局以及 Tab 栏的自定义实现。一、MainEntry 页面概述MainEntry是应用的入口页面使用Entry装饰器标记。它包含一个底部 Tab 导航三个 Tab 分别对应三个主页面┌─────────────────────────────┐ │ │ │ Tab 1: 重要日列表 │ │ (ImportantDayListPage) │ │ │ ├─────────────────────────────┤ │ 重要日 | 日历 | 我的 │ └─────────────────────────────┘二、完整代码分析2.1 页面结构// pages/MainEntry.etsimport{AppStorageV2}fromkit.ArkUI;import{AppConstants}from../common/Constants;import{TAB_LIST}from../common/TabList;import{TabListItem}from../types/Types;import{AvoidArea}from../model/AvoidArea;import{MainViewModel}from../viewmodel/MainViewModel;import{ImportantDayListPage}from./ImportantDayListPage;import{CalendarPage}from./CalendarPage;import{MinePage}from./MinePage;EntryComponentV2struct MainEntry{privatevm:MainViewModelMainViewModel.instance;LocalavoidArea:AvoidAreaAppStorageV2.connect(AvoidArea,()newAvoidArea(0,0))!;build(){Navigation(){Column(){Tabs({barPosition:BarPosition.End,index:this.vm.curTabIndex}){TabContent(){ImportantDayListPage()}.tabBar(this.tabBarBuilder(TAB_LIST[0],0));TabContent(){CalendarPage()}.tabBar(this.tabBarBuilder(TAB_LIST[1],1));TabContent(){MinePage()}.tabBar(this.tabBarBuilder(TAB_LIST[2],2));}.scrollable(false).height(AppConstants.FULL_HEIGHT).animationDuration(0).barMode(BarMode.Fixed).onChange((index:number){this.vm.curTabIndexindex;})}.width(AppConstants.FULL_WIDTH).height(AppConstants.FULL_HEIGHT).backgroundColor($r(sys.color.background_primary)).padding({bottom:px2vp(this.avoidArea.bottomRectHeight)})}.height(AppConstants.FULL_HEIGHT).width(AppConstants.FULL_WIDTH).hideTitleBar(true).hideToolBar(true).hideBackButton(true).mode(NavigationMode.Stack)}BuildertabBarBuilder(item:TabListItem,index:number){Column(){Image(this.vm.curTabIndexindex?item.iconChecked:item.icon).width(24).height(24)Text(item.label).fontColor(this.vm.curTabIndexindex?$r(app.color.icon_checked):$r(app.color.font_secondary)).fontSize($r(app.string.font_size_10)).margin({top:$r(app.string.margin_xs)});}.width(AppConstants.FULL_WIDTH);}}2.2 关键设计点Navigation 容器Navigation(){// 内容}.mode(NavigationMode.Stack).hideTitleBar(true).hideToolBar(true).hideBackButton(true)使用Navigation作为根容器设置为 Stack 模式。隐藏了 Navigation 自带的标题栏、工具栏和返回按钮因为本项目使用自定义的 Tab 导航和页面标题。Navigation 的作用是提供路由栈管理子页面AddEditDayPage、DayDetailPage通过router.pushUrl跳转时会压入栈中。Tabs 组件Tabs({barPosition:BarPosition.End,index:this.vm.curTabIndex}){// ...}.scrollable(false).animationDuration(0).barMode(BarMode.Fixed).onChange((index:number){this.vm.curTabIndexindex;})关键配置barPosition: BarPosition.EndTab 栏在底部index: this.vm.curTabIndex绑定当前选中索引到 ViewModelscrollable(false)禁止左右滑动切换 Tab避免与日历页的 Swiper 滑动冲突animationDuration(0)取消切换动画响应更迅速barMode(BarMode.Fixed)Tab 栏固定不滚动全屏沉浸式布局LocalavoidArea:AvoidAreaAppStorageV2.connect(AvoidArea,()newAvoidArea(0,0))!;// 在布局中.padding({bottom:px2vp(this.avoidArea.bottomRectHeight)})avoidArea.bottomRectHeight是底部导航指示器的高度。通过px2vp转换为 vp 单位后作为底部 padding确保内容不被导航指示器遮挡。Tab 栏自定义BuildertabBarBuilder(item:TabListItem,index:number){Column(){Image(this.vm.curTabIndexindex?item.iconChecked:item.icon).width(24).height(24)Text(item.label).fontColor(this.vm.curTabIndexindex?$r(app.color.icon_checked):$r(app.color.font_secondary)).fontSize($r(app.string.font_size_10)).margin({top:$r(app.string.margin_xs)});}.width(AppConstants.FULL_WIDTH);}自定义 Tab 栏包含图标和文字选中时使用iconChecked图标和icon_checked颜色蓝色未选中时使用icon图标和font_secondary颜色灰色图标 24x24文字 10fp三、Tab 配置3.1 TabList 定义// common/TabList.etsimport{TabListItem}from../types/Types;exportconstTAB_LIST:TabListItem[][{label:$r(app.string.tab_important_days),icon:$r(app.media.days_unchecked),iconChecked:$r(app.media.days_checked),},{label:$r(app.string.tab_calendar),icon:$r(app.media.calendar_unchecked),iconChecked:$r(app.media.calendar_checked),},{label:$r(app.string.tab_mine),icon:$r(app.media.mine_unchecked),iconChecked:$r(app.media.mine_checked),},];3.2 资源引用Tab 的文字标签使用资源引用$r(app.string.xxx)而非硬编码字符串// string.json{name:tab_important_days,value:重要日},{name:tab_calendar,value:日历},{name:tab_mine,value:我的}这种方式支持国际化——只需要添加其他语言的element/string.json即可。3.3 图标资源每个 Tab 有两个图标选中/未选中放在resources/base/media/目录下days_checked.png/days_unchecked.pngcalendar_checked.png/calendar_unchecked.pngmine_checked.png/mine_unchecked.png四、Tab 切换的状态管理4.1 ViewModel 中存储 Tab 索引// MainViewModelTracecurTabIndex:number0;Tab 索引存储在 ViewModel 中这样即使页面被销毁重建当前 Tab 也能恢复。4.2 双向同步// Tabs 的 index 属性Tabs({index:this.vm.curTabIndex})// onChange 回调.onChange((index:number){this.vm.curTabIndexindex;})ViewModel → Tabs当curTabIndex变化时Tabs 组件自动切换Tabs → ViewModel用户手动切换 Tab 时更新curTabIndex五、子页面的安全区域处理每个子页面都需要处理顶部安全区域状态栏// ImportantDayListPageRow(){Text(重要日).fontSize($r(app.float.title_font_size)).fontColor($r(app.color.font_primary)).fontWeight(FontWeight.Bold);}.width(100%).height(56).padding({left:20,right:20}).padding({top:px2vp(this.avoidArea.topRectHeight)});// 顶部安全区域avoidArea.topRectHeight是状态栏高度通过px2vp转换后作为顶部 padding。六、Navigation 路由栈6.1 页面跳转// 从列表页跳转到详情页router.pushUrl({url:pages/DayDetailPage});// 从列表页跳转到添加页router.pushUrl({url:pages/AddEditDayPage});// 返回上一页router.back();6.2 路由栈结构MainEntry (Tab 容器) ├── ImportantDayListPage (Tab 1) │ ├── → DayDetailPage (详情) │ │ └── → AddEditDayPage (编辑) │ └── → AddEditDayPage (添加) ├── CalendarPage (Tab 2) │ └── → DayDetailPage (详情) └── MinePage (Tab 3)router.pushUrl将页面压入栈中router.back()弹出栈顶页面。Navigation 的 Stack 模式管理这个栈。七、scrollable(false) 的重要性Tabs(...).scrollable(false)// 禁止滑动切换这个配置很重要。日历页面内部有一个 Swiper 实现的无限滚动日历如果 Tabs 也支持滑动切换用户在日历上左右滑动时会误触发 Tab 切换。设置scrollable(false)后只能通过点击 Tab 栏切换。八、性能考虑8.1 TabContent 懒加载ArkUI 的 Tabs 组件默认只渲染当前选中的 TabContent。当用户切换到某个 Tab 时该 Tab 的内容才会被创建和渲染。这减少了初始渲染的开销。8.2 避免不必要的重建由于三个子页面都以组件形式嵌入 TabContent它们的生命周期与 Tabs 组件一致。切换 Tab 不会销毁和重建子页面只是显示/隐藏。九、小结MainEntry 作为应用入口通过 Navigation Tabs 的组合实现了底部三 Tab 导航架构。沉浸式全屏布局通过 AvoidArea 模型统一管理安全区域。Tab 栏的自定义 Builder 实现了图标文字的双行布局。