前言

一个 App 用起来爽不爽,很大程度取决于操作区设计得好不好。顶部菜单栏放什么操作,底部工具栏怎么排列,这些细节直接影响用户的操作效率。HarmonyOS7 给我们提供了 MenuBar 和 Toolbar 两套组件,一个管菜单一个管工具栏,搭配起来用效果拔群。
之前做工具类 App,每次到了操作区设计就头疼。手写一堆按钮排列,状态管理乱七八糟,多级菜单更是一团糟。后来发现 ArkUI 自带 ToolBar 组件和 Menu 组件,直接省掉了大半工作量。这玩意儿用对了,操作区设计可以很优雅。

工具栏设计原则

在写代码之前,先聊聊设计。工具栏不是按钮堆砌,得讲究方法:

  1. 高频操作放底部——用户单手够得到的地方才是黄金位置
  2. 5 个入口是上限——超过就收纳到"更多"里,别让界面太挤
  3. 图标+文字双保险——纯图标容易猜错含义,加上文字就清晰了
  4. 状态即反馈——禁用的灰掉、激活的高亮,别让用户猜

MenuBar 基础

MenuBar 一般放在页面顶部,承载文件、编辑这类菜单操作。配合 Menu 组件使用,实现下来很直观。

@Entry
@Component
struct MenuBarDemo {
  @State selectedMenu: string = '无'

  build() {
    Column() {
      Row() {
        Text('文件')
          .fontSize(14)
          .padding({ left: 12, right: 12, top: 8, bottom: 8 })
          .bindMenu([
            { value: '新建', action: () => { this.selectedMenu = '新建' } },
            { value: '打开', action: () => { this.selectedMenu = '打开' } },
            { value: '保存', action: () => { this.selectedMenu = '保存' } }
          ])
        Text('编辑')
          .fontSize(14)
          .padding({ left: 12, right: 12, top: 8, bottom: 8 })
          .bindMenu([
            { value: '撤销', action: () => { this.selectedMenu = '撤销' } },
            { value: '重做', action: () => { this.selectedMenu = '重做' } }
          ])
      }
      .width('100%')
      .height(48)
      .backgroundColor('#F1F3F5')
      .justifyContent(FlexAlign.Start)

      Text(`当前选择: ${this.selectedMenu}`)
        .margin({ top: 40 })
        .fontSize(16)
    }
    .width('100%')
    .height('100%')
  }
}

A hand-drawn doodle illustration on pure white pap

逐行讲解

  • bindMenu 是关键方法——它把一个 Menu 绑定到任意组件上,点击组件就弹出菜单
  • value 是菜单项的显示文字,action 是点击回调
  • 顶部 Row 充当菜单栏容器,横向排列各个菜单入口
  • @State selectedMenu 追踪当前选择,实时更新显示

Toolbar 配置

Toolbar 是底部工具栏组件,自动处理超量项的收纳。超过 5 个入口时,多余的自动收进"更多"子项。

import { ToolBar, ToolBarOptions, ItemState } from '@kit.ArkUI'

@Entry
@Component
struct ToolbarDemo {
  @State toolbarList: ToolBarOptions = new ToolBarOptions()

  aboutToAppear() {
    this.toolbarList.push({
      content: '收藏',
      icon: $r('sys.media.ohos_ic_public_favor'),
      action: () => { console.info('收藏') }
    })
    this.toolbarList.push({
      content: '复制',
      icon: $r('sys.media.ohos_ic_public_copy'),
      action: () => { console.info('复制') },
      state: ItemState.DISABLE
    })
    this.toolbarList.push({
      content: '粘贴',
      icon: $r('sys.media.ohos_ic_public_paste'),
      action: () => { console.info('粘贴') },
      state: ItemState.ACTIVATE
    })
    this.toolbarList.push({
      content: '全选',
      icon: $r('sys.media.ohos_ic_public_select_all'),
      action: () => { console.info('全选') }
    })
    this.toolbarList.push({
      content: '分享',
      icon: $r('sys.media.ohos_ic_public_share'),
      action: () => { console.info('分享') }
    })
    this.toolbarList.push({
      content: '删除',
      icon: $r('sys.media.ohos_ic_public_remove'),
      action: () => { console.info('删除') }
    })
  }

  build() {
    Stack({ alignContent: Alignment.Bottom }) {
      Column() {
        Text('内容区域')
          .fontSize(20)
      }
      .width('100%')
      .height('100%')
      .justifyContent(FlexAlign.Center)

      ToolBar({ toolBarList: this.toolbarList, activateIndex: 2 })
    }
    .width('100%')
    .height('100%')
  }
}

关键点讲解

  • ToolBarOptions 是工具栏数据模型,用 push 逐个添加菜单项
  • 每个项有三个核心属性:content(文字)、icon(图标)、action(点击回调)
  • state 控制按钮状态:ENABLE 正常可点、DISABLE 灰色不可点、ACTIVATE 高亮激活
  • activateIndex 指定默认激活项的索引
  • 第 6 项"删除"会自动收进"更多"菜单,不需要你额外处理

菜单项交互

点击菜单项后的交互逻辑,可以直接在 action 回调里写:

@Entry
@Component
struct MenuInteractionDemo {
  @State items: string[] = ['项目A', '项目B', '项目C']
  @State toolbarList: ToolBarOptions = new ToolBarOptions()

  aboutToAppear() {
    this.toolbarList.push({
      content: '添加',
      icon: $r('sys.media.ohos_ic_public_add'),
      action: () => {
        this.items.push(`项目${this.items.length + 1}`)
      }
    })
    this.toolbarList.push({
      content: '清空',
      icon: $r('sys.media.ohos_ic_public_remove'),
      action: () => {
        this.items = []
      }
    })
  }

  build() {
    Stack({ alignContent: Alignment.Bottom }) {
      List() {
        ForEach(this.items, (item: string) => {
          ListItem() {
            Text(item).fontSize(16).padding(12)
          }
        })
      }
      .width('100%')

      ToolBar({ toolBarList: this.toolbarList })
    }
    .width('100%')
    .height('100%')
  }
}

这里把工具栏操作和列表数据联动起来了——点"添加"就追加项目,点"清空"就全删。因为 items@State 修饰的,数据变化会自动驱动 UI 刷新。

图标与文字搭配

系统预置了一波图标资源,用 $r('sys.media.xxx') 就能引用。常用图标:

图标引用 用途
ohos_ic_public_copy 复制
ohos_ic_public_paste 粘贴
ohos_ic_public_share 分享
ohos_ic_public_favor 收藏
ohos_ic_public_add 添加
ohos_ic_public_remove 删除
ohos_ic_public_select_all 全选

建议:能用系统图标就用系统的,风格统一还省资源。如果系统图标不满足需求,再用自定义图标放在 resources 目录下。

多级菜单

有些场景需要嵌套菜单,比如"新建"下面再分"新建文档"、“新建表格”。bindMenu 支持这种结构:

Text('文件')
  .fontSize(14)
  .padding(12)
  .bindMenu([
    {
      value: '新建',
      action: () => {}
    },
    {
      value: '新建文档',
      icon: $r('sys.media.ohos_ic_public_add'),
      action: () => { console.info('新建文档') }
    },
    {
      value: '导出',
      action: () => {}
    },
    {
      value: '导出为PDF',
      action: () => { console.info('导出PDF') }
    },
    {
      value: '导出为图片',
      action: () => { console.info('导出图片') }
    }
  ])

血泪教训:多级菜单别超过 3 层,不然用户找选项找到崩溃。菜单层级越深,操作效率越低。

如果需要更复杂的菜单交互,可以配合 @CustomDialog 自己画菜单面板,灵活度更高。

写在最后

MenuBar 和 Toolbar 各有分工——顶部菜单栏放低频但分类多的操作,底部工具栏放高频操作。两者搭配使用,操作区的用户体验直接拉满。

我觉得 Toolbar 最贴心的设计就是自动收纳超量项。你不用操心"按钮太多了怎么办",组件帮你搞定。省心。

如果你正在做工具类或内容编辑类 App,这套组合真的值得试试。代码不多,效果很好。

Logo

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

更多推荐