上一篇文章,我们体验了一下声明式导航Navigation组件,为了巩固一下,本文我们复用上文的商品信息页面,再添加上标签页(Tab)这种导航方式。

我们的目标是构建一个包含"商城"和"我的"两个标签页的应用。

用户可以在商城页面浏览商品列表,然后点击查看详情;

在个人中心查看用户信息,进入设置页面。

第一步:理解应用架构

虽然简单,但是在开始编码之前,我们梳理下整个应用的结构。我们的应用采用分层架构:

 主页面(MainPage)
 ├── 标签页1:商城(ProductListPage)
 │   └── 商品详情页(ProductDetailPage)
 └── 标签页2:我的(MyProfileTabPage)
     └── 设置页(SettingsPage)

每个标签页都有独立的导航栈,用户可以在不同标签页间自由切换,不会丢失各自的浏览状态。

第二步:数据模型设计

我们首先定义两个核心数据模型,Product是上文已经有的,这次新增了User这个数据模型。

// models/ProductModels.ets
 export interface Product {
   id: string;
   name: string;
   description: string;
 }
 ​
 export interface User {
   uid: string;
   username: string;
   avatar: Resource;
   level: number;
 }

第三步:构建主标签页容器

主页面是整个应用的入口,使用Tabs组件来管理多个标签页。让我们看看如何实现:

@Entry
 @Component
 struct MainPage {
   @State currentIndex: number = 0
 ​
   @Builder
   private tabBuilder(title: string, targetIndex: number, icon: Resource) {
     Column() {
       Image(icon)
         .width(28)
         .height(28)
         .margin({ bottom: 2 })
       Text(title)
         .fontSize(12)
         .fontColor(this.currentIndex === targetIndex ? '#007DFF' : '#666666')
     }
     .width('100%')
     .height('100%')
     .justifyContent(FlexAlign.Center)
   }
 ​
   build() {
     Tabs({ barPosition: BarPosition.End }) {
       TabContent() {
         ProductListPage()
       }
       .tabBar(this.tabBuilder('商城', 0, $r('app.media.ic_shop')))
 ​
       TabContent() {
         MyProfileTabPage()
       }
       .tabBar(this.tabBuilder('我的', 1, $r('app.media.ic_profile')))
     }
     .onChange((index) => {
       this.currentIndex = index;
     })
     .animationDuration(300)
     .barWidth('100%')
     .barHeight(56)
   }
 }

注意现在MainPage.ets作为了应用的入口,在day5_navigation模块中,ProductListPage是应用入口。拷贝到day6_tab模块后,ProductListPage上的@Entry就可以去掉了。

然后把day6_tab模块src/main/ets/day6_tabability/Day6_tabAbility.ets中的loadContent修改成MainPage。


状态管理 @State currentIndex: number = 0 用来跟踪当前选中的标签页。当用户切换标签的时候,这个状态会自动更新,触发UI重新渲染。

自定义标签样式 @Builder 装饰器可以让我们我们创建可复用的UI构建器。这里我们自定义了标签页的样式,包括图标、文字和选中状态的颜色变化。

标签页配置 barPosition: BarPosition.End 把标签栏放在底部,一般的手机app都这么布局。animationDuration(300) 设置了切换动画的持续时间。

第四步:商品列表、详情页

直接把前文的ProductListPage和ProductDetailPage页面拷贝过来就可以了。

第五步:构建个人中心页

个人中心页展示了用户信息管理和设置入口:

@Component
 export struct MyProfileTabPage {
   @State path: NavPathStack = new NavPathStack();
 ​
   @Builder
   destinationBuilder(name: string, param: object) {
     if (name === 'Settings') {
       NavDestination() {
         SettingsPage()
       }
       .title('设置')
     }
   }
 ​
   build() {
     Navigation(this.path) {
       Column({ space: 20 }) {
         Image(currentUser.avatar).width(100).height(100).borderRadius(50)
         Text(currentUser.username).fontSize(24).fontWeight(FontWeight.Bold)
         Text(`等级: Lv.${currentUser.level}`).fontSize(18).fontColor(Color.Gray)
 ​
         Button('进入设置页面')
           .margin({ top: 30 })
           .onClick(() => {
             this.path.pushPathByName('Settings', null);
           })
       }
       .width('100%').height('100%')
       .justifyContent(FlexAlign.Center)
       .backgroundColor('#f1f3f5')
     }
     .title('我的')
     .navDestination(this.destinationBuilder)
   }
 }

独立的导航栈 每个标签页都有自己的 NavPathStack,用户在"商城"标签页浏览商品详情,然后切换到"我的"标签页,再回到"商城"时,之前的浏览状态会被保留。

用户信息展示 我们使用模拟的用户数据来展示用户信息,包括头像、用户名和等级。在实际应用中,这些数据通常来自用户登录后的API响应。

第六步:实现设置页面

设置页面展示了状态管理和用户交互:

@Component
 export struct SettingsPage {
   @State isNotificationOn: boolean = true;
   @State isDarkMode: boolean = false;
 ​
   build() {
     Column() {
       List({ space: 10 }) {
         ListItem() {
           Row() {
             Text('开启消息通知').fontSize(18)
             Toggle({ type: ToggleType.Switch, isOn: this.isNotificationOn })
               .onChange((isOn) => {
                 this.isNotificationOn = isOn;
               })
           }
           .width('100%').justifyContent(FlexAlign.SpaceBetween).padding(20).backgroundColor(Color.White)
         }
         .borderRadius(15)
 ​
         ListItem() {
           Row() {
             Text('夜间模式').fontSize(18)
             Toggle({ type: ToggleType.Switch, isOn: this.isDarkMode })
               .onChange((isOn) => {
                 this.isDarkMode = isOn;
               })
           }
           .width('100%').justifyContent(FlexAlign.SpaceBetween).padding(20).backgroundColor(Color.White)
         }
         .borderRadius(15)
       }
       .width('95%')
       .margin({top: 20})
     }
     .width('100%').height('100%')
     .backgroundColor('#f1f3f5')
     .alignItems(HorizontalAlign.Start)
   }
 }

状态管理的重要性: 每个开关都有对应的 @State 变量来管理其状态。当用户操作开关时,onChange 回调会更新对应的状态,触发UI重新渲染。这种响应式的状态管理是 HarmonyOS 开发的核心特性。

运行效果

主页面:

我的页面:

设置界面:

总结

通过这个标签页应用的学习,我们掌握了HarmonyOS开发的核心技能:

  • 组件化开发:如何设计和实现可复用的组件

  • 状态管理:如何使用@State@Prop管理应用状态

  • 页面导航:如何使用NavigationNavPathStack实现页面跳转

  • 用户交互:如何响应用户操作并更新UI

这些技能是构建复杂HarmonyOS应用的基础。掌握了这些概念,你就可以开始构建更复杂、更有趣的应用了。

Logo

讨论HarmonyOS开发技术,专注于API与组件、DevEco Studio、测试、元服务和应用上架分发等。

更多推荐