鸿蒙开发之常见导航样式案例详解

42 阅读6分钟

概述

在鸿蒙应用开发中,导航是构建用户界面的重要组成部分。不同的页签导航会因产品形态的不同而呈现出不同的UI效果。本文基于HarmonyOS官方最佳实践,系统性地介绍了多种常见导航样式的实现方案,包括底部导航、顶部导航和侧边导航三大类共10种导航样式。

导航样式总览

图1:底部导航效果示意图

01-底部导航效果总览.png

图2:顶部导航效果示意图

02-顶部导航效果总览.png

图3:侧边导航效果示意图

03-侧边导航效果总览.png

一、底部导航

1.1 基础底部导航

效果展示

04-基础底部导航.png

基础底部导航是移动应用中最常见的导航方式,通常采用图标加文字的形式展示在页面底部。

核心实现思路:

  1. 使用 Tabs 组件,设置 barPositionBarPosition.End 实现底部展示
  2. 使用 TabContent 组件实现内容区,通过 tabBar 属性配置导航条
  3. 使用 Badge 组件实现消息提醒功能

关键代码示例:

build() {
  Tabs({
    barPosition: BarPosition.End,
    controller: this.tabsController
  }) {
    this.tabContentBuilder($r('app.string.message'),
      Constants.TAB_INDEX_ZERO, $r('app.media.activeMessage'), $r('app.media.message'))
    this.tabContentBuilder($r('app.string.people'),
      Constants.TAB_INDEX_ONE, $r('app.media.activePeople'), $r('app.media.people'))
    this.tabContentBuilder($r('app.string.activity'),
      Constants.TAB_INDEX_TWO, $r('app.media.activeStar'), $r('app.media.star'))
  }
  .width('100%')
  .backgroundColor('#F3F4F5')
  .barHeight(52)
  .barMode(BarMode.Fixed)
  .onAnimationStart((index: number, targetIndex: number) => {
    this.currentIndex = targetIndex;
  })
}

导航Tab构建器:

@Builder
tabBuilder(title: Resource, index: number, selectedImg: Resource, normalImg: Resource) {
  Column() {
    if (index === 0) {
      Badge({
        count: this.msgNum,
        style: { badgeSize: 14 },
        maxCount: 999,
        position: BadgePosition.RightTop
      }) {
        Image(this.currentIndex === index ? selectedImg : normalImg)
          .width(24)
          .height(24)
          .objectFit(ImageFit.Contain)
      }
      .width(30)
    } else {
      Image(this.currentIndex === index ? selectedImg : normalImg)
        .width(24)
        .height(24)
        .objectFit(ImageFit.Contain)
    }

    Text(title)
      .margin({ top: 4 })
      .fontSize(10)
      .fontColor(this.currentIndex === index ? '#3388ff' : '#E6000000')
  }
  .justifyContent(FlexAlign.Center)
  .height(52)
  .width('100%')
  .onClick(() => {
    this.currentIndex = index;
    this.tabsController.changeIndex(this.currentIndex);
  })
}

1.2 舵式底部导航

效果展示

05-舵式底部导航.png

舵式导航是基础底部导航的扩展形式,中间按钮通常为核心功能,在视觉设计上中心图标可以超出导航条的高度,形成视觉焦点。

实现要点:

  1. 中心按钮使用 offset 属性实现向上偏移效果
  2. 通过自定义布局替代 tabBar 属性
  3. 使用 Flex 布局均匀分布导航按钮

关键代码:

@Builder
Tab(selectImage: Resource, defaultImage: Resource, title: string | Resource, middleMode: boolean, index: number) {
  Column() {
    if (index === Constants.TAB_INDEX_TWO) {
      Image(defaultImage)
        .size({ width: 56, height: 56 })
        .offset({ y: -15 })  // 中心按钮向上偏移
    } else {
      Image(this.currentIndex === index ? selectImage : defaultImage)
        .size({ width: 22, height: 22 })
        .offset({
          y: (this.currentIndex === index && this.currentIndex !== Constants.TAB_INDEX_TWO)
            ? this.iconOffset : this.initNumber
        })
        .objectFit(ImageFit.Contain)
        .animation({
          duration: Constants.ANIMATION_DURATION,
          curve: Curve.Ease,
          playMode: PlayMode.Normal
        })
    }

    if (!middleMode) {
      Text(title)
        .fontSize(10)
        .margin({ top: 6 })
        .fontColor(this.currentIndex === index ? '#3388ff' : '#E6000000')
    }
  }
  .padding({ top: 11 })
  .width('100%')
  .backgroundColor('#F3F4F5')
  .height(90)
  .translate({ y: 40 })
  .onClick(() => {
    if (index !== Constants.TAB_INDEX_TWO) {
      this.currentIndex = index;
      this.controller.changeIndex(index);
      this.iconOffset = Constants.ICON_Offset;
    }
  })
}

二、顶部导航

2.1 居左对齐样式

效果展示

06-居左对齐样式.png

由于 Tabs 组件的导航默认居中展示,无法直接通过 tabBar 属性实现居左对齐。需要使用自定义布局来实现这一效果。

实现方案:

  1. 使用 Stack 组件叠加文字和下划线两层
  2. 通过 onAreaChange 监听计算下划线位置
  3. 在页签切换时动态更新下划线的位置和宽度

核心代码:

Stack({ alignContent: Alignment.TopStart }) {
  // 文字导航条
  Row() {
    ForEach(this.tabArray, (item: number, index: number) => {
      this.tab(this.tabStr + item, item, index);
    }, (item: number, _index: number) => item.toString())
    Blank()
    Text('+')
      .width(24)
      .height(24)
      .fontSize(24)
      .textAlign(TextAlign.Center)
      .margin({ right: 24 })
  }
  .justifyContent(FlexAlign.Start)
  .width('100%')

  // 下划线指示器
  Column()
    .width(this.indicatorWidth)
    .height(1.5)
    .backgroundColor('#0A59F7')
    .borderRadius(1)
    .margin({ left: this.indicatorLeftMargin, top: 35 })
}
.height(56)
.margin({ left: this.tabLeftOffset })

下划线位置计算:

@Builder
tab(tabName: string, _tabItem: number, tabIndex: number) {
  Row() {
    Text(tabName)
      .fontSize(16)
      .lineHeight(22)
      .fontColor(tabIndex === this.currentIndex ? '#0A59F7' : '#E6000000')
      .id(tabIndex.toString())
      .onAreaChange((_, newValue: Area) => {
        if (this.currentIndex === tabIndex && (this.indicatorLeftMargin === 0 || this.indicatorWidth === 0)) {
          let positionX: number;
          let width: number = Number.parseFloat(newValue.width.toString());
          if (newValue.position.x !== undefined) {
            positionX = Number.parseFloat(newValue.position.x?.toString())
            this.indicatorLeftMargin = Number.isNaN(positionX) ? 0 : positionX;
          }
          this.indicatorWidth = width;
        }
      })
  }
  .justifyContent(FlexAlign.Center)
  .constraintSize({ minWidth: 35 })
  .width(64)
  .height(35)
  .onClick(() => {
    this.controller.changeIndex(tabIndex);
    this.currentIndex = tabIndex;
  })
}

2.2 可滑动居左对齐样式

效果展示

07-可滑动居左对齐样式.png

在居左对齐的基础上,使用 List 组件实现横向滑动功能,适合页签数量较多的场景。

实现要点:

Row() {
  List({ initialIndex: 0, scroller: this.listScroller }) {
    ForEach(this.tabArray, (item: TabItem, index: number) => {
      this.Tab(item.name, index)
    }, (item: TabItem, index: number) => JSON.stringify(item) + index)
  }
  .listDirection(Axis.Horizontal)  // 横向滚动
  .height(30)
  .scrollBar(BarState.Off)
  .width('85%')
  .friction(0.6)
  .onWillScroll((xOffset: number) => {
    this.indicatorLeftMargin -= xOffset;  // 滚动时同步下划线位置
  })

  Image($r('app.media.more'))
    .width(20)
    .height(15)
    .margin({ left: 16 })
}
.height(52)
.width('100%')

2.3 下划线样式

效果展示

08-下划线样式.gif

下划线样式是最常见的顶部导航形式,采用文字加下划线的简洁设计。这是一个动态GIF效果图,展示了页签切换时下划线的平滑过渡动画。

实现方法:

Tabs({ barPosition: BarPosition.Start }) {
  ForEach(this.tabArray.slice(0, 4), (item: TabItem) => {
    TabContent() {
      Row() {
        Text(item.name)
          .height(300)
          .fontSize(30)
      }
      .width('100%')
      .justifyContent(FlexAlign.Center)
      .height('100%')
    }.tabBar(this.tabBuilder(item.id, item.name))
  }, (item: TabItem, index: number) => JSON.stringify(item) + index)
}

@Builder
tabBuilder(index: number, name: string | Resource) {
  Column() {
    Text(name)
      .fontColor(this.currentIndex === index ? '#0A59F7' : '#E6000000')
      .fontSize(16)
      .fontWeight(this.currentIndex === index ? FontWeight.Normal : FontWeight.Medium)
      .lineHeight(22)
      .margin({ top: 17, bottom: 7 })
    Divider()
      .width(48)
      .strokeWidth(Constants.STROKE_WIDTH)
      .color('#0A59F7')
      .opacity(this.currentIndex === index ? 1 : 0)  // 控制下划线显示
  }
}

2.4 背景高亮式

效果展示

09-背景高亮式.png

通过改变选中标签的背景色来突出当前激活的页签,视觉冲击力强。

核心实现:

@Builder
tabBuilder(name: string | Resource, index: number) {
  Row() {
    Text(name)
      .fontSize(14)
      .fontColor(this.currentIndex === index ? '#0A59F7' : '#E6000000')
  }
  .justifyContent(FlexAlign.Center)
  .constraintSize({ minWidth: 35 })
  .padding({ left: 16, right: 16 })
  .height(28)
  .borderRadius(16)
  .backgroundColor(this.currentIndex === index ? '#1A0A59F7' : Color.Transparent)
  .onClick(() => {
    this.controller.changeIndex(index);
    this.currentIndex = index;
  })
}

2.5 文字缩放式

效果展示

10-文字缩放式.png

通过字体大小和粗细变化来强调选中状态,交互反馈直观。

关键代码:

@Builder
tabBuilder(index: number, name: string | Resource) {
  Text(name)
    .fontColor(Color.Black)
    .fontSize(this.currentIndex === index ? 20 : 16)  // 选中时字体放大
    .fontWeight(this.currentIndex === index ? 600 : FontWeight.Normal)  // 字体加粗
    .lineHeight(22)
    .id(index.toString())
}

2.6 双层嵌套式

效果展示

11-双层嵌套式.png

双层嵌套样式拥有外层和内层两层导航,可以容纳更多的页签内容,适合复杂的信息架构。

实现架构:

TabContent() {
  Column() {
    Column() {
      Row() {
        // 内层导航 - 使用List实现横向滚动
        List({ initialIndex: Constants.TAB_INDEX_ZERO, scroller: this.listScroller }) {
          ForEach(this.tabArray, (item: TabItem, index: number) => {
            this.subTabBuilder(item.name, index)
          }, (item: TabItem, index: number) => JSON.stringify(item) + index)
        }
        .listDirection(Axis.Horizontal)
        .height(30)
        .scrollBar(BarState.Off)
        .width('85%')
        .friction(0.6)

        Image($r('app.media.more'))
          .width(20)
          .height(15)
          .margin({ left: 16 })
      }
      .height(25)
      .width('100%')
    }
    .alignItems(HorizontalAlign.Center)
    .width('100%')
    .padding({ left: 4 })

    // 内容区 - 子级Tabs
    Tabs({ barPosition: BarPosition.Start, controller: this.subController }) {
      // TabContent内容
    }
    .barHeight(0)  // 隐藏内层Tabs的导航条
    .animationDuration(Constants.ANIMATION_DURATION)
    .onAnimationStart((index: number, targetIndex: number) => {
      this.focusIndex = targetIndex;
      this.listScroller.scrollToIndex(targetIndex, true, ScrollAlign.CENTER);
    })
  }
}
.tabBar(this.tabBuilder(Constants.TAB_INDEX_ZERO, this.topTabData[Constants.TAB_INDEX_ZERO]))

三、侧边导航

3.1 基础侧边导航

效果展示

12-基础侧边导航.png

侧边导航采用左右布局,左侧为分类列表,右侧为内容展示区域。

实现方案:

Row() {
  // 左侧导航列表
  List({ scroller: this.classifyScroller }) {
    ForEach(this.ClassifyArray, (item: ClassifyModel, index?: number) => {
      ListItem() {
        ClassifyItem({
          classifyName: item.classifyName,
          isSelected: this.currentClassify === index,
          onClickAction: () => {
            if (index !== undefined) {
              this.classifyChangeAction(index, true);
            }
          }
        })
      }
    }, (item: ClassifyModel, index: number) => JSON.stringify(item) + index)
  }
  .height('110%')
  .width('27.8%')
  .backgroundColor('#F1F3F5')
  .scrollBar(BarState.Off)
  .margin({ top: 74 })

  // 右侧内容区
  Column() {
    ForEach(this.ClassifyArray, (item: ClassifyModel, index: number) => {
      Text(this.currentClassify === index ? item.classifyName : '')
        .fontSize(30)
    },(item: ClassifyModel, index: number) => JSON.stringify(item) + index)
  }
  .width('72.2%')
}

3.2 抽屉式侧边导航

效果展示

13-抽屉式侧边导航.png

抽屉式导航的核心思路是"隐藏",点击入口或侧滑可以像抽屉一样拉出菜单,节省屏幕空间。

实现要点:

使用 SideBarContainer 组件实现:

SideBarContainer(SideBarContainerType.Overlay) {
  // 侧边栏内容
  Column() {
    ForEach(this.navList, (item: number, index: number) => {
      Column() {
        Row() {
          Image(this.active === item ? $r('app.media.activeList') : $r('app.media.list'))
            .width(24)
            .height(24)
          Text($r('app.string.list_name'))
            .fontSize(16)
            .fontColor(Color.Black)
            .fontWeight(FontWeight.Medium)
            .margin({ left: 17 })
        }
        .height(48)
        .width('100%')

        if (this.navList.length - 1 !== index) {
          Row()
            .height(0.5)
            .backgroundColor('#0D000000')
            .width('90%')
        }
      }
      .onClick(() => {
        this.active = item;
      })
      .justifyContent(FlexAlign.Center)
      .width(264)
      .height(48)
      .padding({ left: 13 })
      .borderRadius(16)
      .backgroundColor(this.active === item ? '#1A0A59F7' : '')
    }, (item: number, index: number) => JSON.stringify(item) + index)
  }
  .height('100%')
  .padding({ top: 104 })
  .backgroundColor('#E9EAEC')
  .width(272)
  .height(344)
  .backgroundColor(Color.White)
  .borderRadius(20)

  // 主内容区
  Column() {
    // 内容
  }
  .onClick(() => {
    this.getUIContext().animateTo({
      duration: Constants.ANIMATION_DURATION,
      curve: Curve.EaseOut,
      playMode: PlayMode.Normal,
    }, () => {
      this.show = false;
    })
  })
  .width('100%')
  .height('110%')
  .backgroundColor(this.show ? '#c1c2c4' : '')
}
.showSideBar(this.show)  // 控制显示/隐藏
.controlButton({
  left: 16,
  top: 48,
  height: 40,
  width: 40,
  icons: {
    shown: $r('app.media.changeBack'),
    hidden: $r('app.media.change'),
    switching: $r('app.media.change')
  }
})
.onChange((value: boolean) => {
  this.show = value;
})

四、最佳实践建议

1. 导航样式选择原则

导航类型适用场景典型应用
基础底部导航3-5个主要功能模块微信、支付宝、淘宝
舵式底部导航核心功能突出显示闲鱼、小红书
顶部导航内容分类较多今日头条、抖音
侧边导航分类层级较深设置页面、文件管理器

2. 性能优化建议

  • 使用 ForEach 渲染列表时,务必提供唯一的 keyGenerator
  • 对于大量页签的场景,建议使用 LazyForEach 进行懒加载
  • 合理设置动画时长(推荐200-300ms),避免过度动画影响用户体验
  • 导航图标建议使用矢量图(SVG)或提供2x/3x分辨率的位图

3. 交互设计要点

  • 保持导航状态的视觉反馈清晰明确
  • 页签切换应提供平滑的过渡动画
  • 确保可点击区域足够大,符合可用性标准(推荐最小44vp)
  • 选中状态应有明显的视觉差异(颜色、大小、下划线等)

4. 代码复用

  • 将通用的导航样式封装为自定义组件
  • 使用 @Builder 装饰器创建可复用的UI构建函数
  • 统一管理导航相关的常量和样式
  • 建立导航样式的配置化体系,提高可维护性

5. 无障碍设计

  • 为导航按钮添加语义化描述
  • 确保色彩对比度符合WCAG标准
  • 支持键盘导航和屏幕阅读器

五、总结

本文详细介绍了鸿蒙应用开发中10种常见导航样式的实现方案:

底部导航类(2种)

  • 基础底部导航:图标+文字,支持Badge消息提醒
  • 舵式底部导航:中心按钮突出,视觉焦点明确

顶部导航类(6种)

  • 居左对齐样式:自定义布局实现左对齐+动态下划线
  • 可滑动居左对齐样式:支持横向滚动的多页签导航
  • 下划线样式:简洁经典的下划线指示器
  • 背景高亮式:背景色突出选中状态
  • 文字缩放式:字体大小变化展示激活状态
  • 双层嵌套式:二级导航,适合复杂信息架构

侧边导航类(2种)

  • 基础侧边导航:左右布局,分类明确
  • 抽屉式侧边导航:节省空间,按需展示

每种导航样式都有其适用场景和实现技巧。开发者应根据应用的具体需求、信息架构和用户习惯,选择最合适的导航方式。通过合理运用 TabsListSideBarContainer 等组件,配合自定义布局和动画效果,可以打造出流畅、美观的用户体验。