鸿蒙6.0应用开发——丰富多彩的菜单(Menu)

【高心星出品】

文章目录

鸿蒙6.0应用开发------丰富多彩的菜单(Menu)

Menu是菜单接口,一般用于鼠标右键弹窗、点击弹窗等。具体用法请参考菜单控制

使用bindContextMenu并设置预览图,菜单弹出时有蒙层,此时为模态。

使用bindMenu或bindContextMenu未设置预览图时,菜单弹出无蒙层,此时为非模态。

创建默认样式的菜单

菜单需要调用bindMenu接口来实现。bindMenu响应绑定组件的点击事件,绑定组件后手势点击对应组件后即可弹出。

TypeScript 复制代码
Button('click for Menu')
  .bindMenu([
    {
      value: 'Menu1',
      action: () => {
        hilog.info(0xFF00, 'DialogProject', 'handle Menu1 select');
      }
    }
  ])

代码逻辑走读:

  1. 创建一个按钮组件,按钮的文本显示为"click for Menu"。
  2. 使用bindMenu方法将一个菜单绑定到按钮上。
  3. 菜单是一个数组,其中包含一个对象,该对象定义了一个菜单项。
  4. 菜单项的value属性设置为"Menu1",表示菜单项的文本内容。
  5. action属性是一个函数,当用户选择这个菜单项时,这个函数会被执行。
  6. action函数中,使用hilog.info方法记录一条日志,日志的标签为"DialogProject",日志级别为0xFF00,日志内容为"handle Menu1 select"。

创建自定义样式的菜单

当默认样式不满足开发需求时,可使用@Builder自定义菜单内容,通过bindMenu接口进行菜单的自定义。

使用@Builder自定义菜单内容

TypeScript 复制代码
import { hilog } from '@kit.PerformanceAnalysisKit';

class Tmp {
  // 请将$r('app.media.view_list_filled')替换为实际资源文件
  public iconStr2: ResourceStr = $r('app.media.view_list_filled');

  set(val: Resource) {
    this.iconStr2 = val;
  }
}

@Entry
@Component
export struct BuilderCustomMenuExample {
  @State select: boolean = true;
   // 请将$r('app.media.view_list_filled')替换为实际资源文件
  private iconStr: ResourceStr = $r('app.media.view_list_filled');
  private iconStr2: ResourceStr = $r('app.media.view_list_filled');
  // 请将$r('app.string.copy')替换为实际资源文件,在本示例中该资源文件的value值为"复制"
  private copy: ResourceStr = $r('app.string.copy');
  // 请将$r('app.string.paste')替换为实际资源文件,在本示例中该资源文件的value值为"粘贴"
  private paste: ResourceStr = $r('app.string.paste');

  @Builder
  SubMenu() {
    Menu() {
      MenuItem({ content: this.copy, labelInfo: 'Ctrl+C' })
      MenuItem({ content: this.paste, labelInfo: 'Ctrl+V' })
    }
  }

  @Builder
  MyMenu() {
    Menu() {
      // 请将$r('app.string.menu_selection')替换为实际资源文件,在本示例中该资源文件的value值为"菜单选项"
      // 请将$r('app.media.icon')替换为实际资源文件
      // 请将$r('app.media.arrow_right_filled')替换为实际资源文件
      MenuItem({ startIcon: $r('app.media.icon'), content: $r('app.string.menu_selection') })
      MenuItem({ startIcon: $r('app.media.icon'), content: $r('app.string.menu_selection') }).enabled(false)
      MenuItem({
        startIcon: this.iconStr,
        content: $r('app.string.menu_selection'),
        endIcon: $r('app.media.arrow_right_filled'),
        // 当builder参数进行配置时,表示与menuItem项绑定了子菜单。鼠标hover在该菜单项时,会显示子菜单。
        builder: this.SubMenu
      })
      // 请将$r('app.string.menu_subtitle')替换为实际资源文件,在本示例中该资源文件的value值为"小标题"
      MenuItemGroup({ header: $r('app.string.menu_subtitle') }) {
        // 请将$r('app.string.menu_selection')替换为实际资源文件,在本示例中该资源文件的value值为"菜单选项"
        MenuItem({ content: $r('app.string.menu_selection') })
          .selectIcon(true)
          .selected(this.select)
          .onChange((selected) => {
            hilog.info(0xFF00, 'DialogProject', 'menuItem select' + selected);
            let str: Tmp = new Tmp();
            str.set($r('app.media.icon'));
          })
        // 请将$r('app.string.menu_selection')替换为实际资源文件,在本示例中该资源文件的value值为"菜单选项"
        // 请将$r('app.media.view_list_filled')替换为实际资源文件
        // 请将$r('app.media.arrow_right_filled')替换为实际资源文件
        MenuItem({
          startIcon: $r('app.media.view_list_filled'),
          content: $r('app.string.menu_selection'),
          endIcon: $r('app.media.arrow_right_filled'),
          builder: this.SubMenu
        })
      }

      // 请将$r('app.string.menu_selection')替换为实际资源文件,在本示例中该资源文件的value值为"菜单选项"
      // 请将$r('app.media.arrow_right_filled')替换为实际资源文件
      MenuItem({
        startIcon: this.iconStr2,
        content: $r('app.string.menu_selection'),
        endIcon: $r('app.media.arrow_right_filled')
      })
    }
  }

  build() {
    // ...
  }
}

代码逻辑走读:

  1. 导入模块
    • 导入了 hilog用于日志记录,属于性能分析工具包。
  2. 定义辅助类 Tmp
    • 定义了一个类 Tmp,包含一个方法 set,用于设置 iconStr2属性。
  3. 组件定义
    • 使用 @Entry@Component装饰器定义了一个名为 BuilderCustomMenuExample的组件。
    • 定义了多个状态变量和资源字符串,用于初始化菜单项的属性。
  4. 子菜单构建
    • 定义了两个 @Builder方法 SubMenuMyMenu,用于构建子菜单和主菜单。
    • SubMenu构建一个简单的菜单,包含复制和粘贴菜单项。
    • MyMenu构建一个复杂的菜单,包含多个菜单项、菜单项组和带子菜单的菜单项。
  5. 菜单项属性设置
    • 使用 MenuItemMenuItemGroup定义菜单项和菜单项组。
    • 设置了菜单项的图标、启用状态、子菜单等属性。
    • 使用 onChange事件处理器来响应菜单项的选择变化,并使用 Tmp类来处理资源设置。
  6. 构建方法
    • 定义了 build方法,用于构建组件的 UI 结构。

使用bindMenu属性绑定组件

TypeScript 复制代码
Button('click for Menu')
  .bindMenu(this.MyMenu)

创建支持右键或长按的菜单

通过bindContextMenu接口自定义菜单,设置菜单弹出的触发方式,触发方式为右键或长按。使用bindContextMenu弹出的菜单项是在独立子窗口内的,可显示在应用窗口外部。

  • 使用@Builder自定义菜单内容,与上文写法相同。

  • 确认菜单的弹出方式,并使用bindContextMenu属性绑定组件。示例中为右键弹出菜单。

    TypeScript 复制代码
    Button('Right-click for Menu')
      .bindContextMenu(this.MyMenu, ResponseType.RightClick)

菜单弹出时振动效果

菜单从API version 18开始支持振动效果。菜单弹出时,默认不振动。若希望菜单弹出时有振动效果,可以通过ContextMenuOptions的hapticFeedbackMode属性,设置菜单弹出时的振动模式。

  • 只有一级菜单可配置弹出时振动效果。

  • 仅当应用具备ohos.permission.VIBRATE权限,且用户启用了触感反馈时才会生效。开启触控反馈时,需要在工程的module.json5中配置声明权限的requestPermissions字段开启振动权限,配置如下:

    cangjie 复制代码
    "requestPermissions": [
      {
        "name": "ohos.permission.VIBRATE",
      }
    ],
TypeScript 复制代码
Button('click for Menu')
  .id('click for Menu')
  .bindMenu(this.MyMenu, { hapticFeedbackMode: HapticFeedbackMode.ENABLED})

菜单支持避让中轴

从API version 18起,菜单支持中轴避让功能。从API version 20开始,在2in1设备上默认启用(仅在窗口处于瀑布模式时产生避让)。开发者可通过ContextMenuOptions中的enableHoverMode属性,控制菜单是否启用中轴避让。

说明

  • 如果菜单的点击位置在中轴区域,则菜单不会避让。
  • 2in1设备上需同时满足窗口处于瀑布模式才会产生避让。
TypeScript 复制代码
@Entry
@Component
export struct SupportAvoidCentralAxisMenuExample {
  @State message: string = 'Hello World';
  // 请在resources\base\element\string.json文件中配置name为'xxx',value为非空字符串的资源
  @State upScreen: string =
    this.getUIContext().getHostContext()?.resourceManager.getStringByNameSync('Upper_half_screen') as string;
  @State middleAxle: string =
    this.getUIContext().getHostContext()?.resourceManager.getStringByNameSync('Middle_axle') as string;
  @State lowerScreen: string =
    this.getUIContext().getHostContext()?.resourceManager.getStringByNameSync('Lower_half_screen') as string;
  @State zone: string =
    this.getUIContext().getHostContext()?.resourceManager.getStringByNameSync('zone') as string;
  @State hoverModeStart: string =
    this.getUIContext().getHostContext()?.resourceManager.getStringByNameSync('hoverMode_start') as string;
  // 请将$r('app.media.startIcon')替换为实际资源文件
  private iconStr: Resource = $r('app.media.startIcon');
  @State index: number = 0;
  @State arrayStr: Array<string> = [this.upScreen, this.middleAxle, this.lowerScreen];
  @State enableHoverMode: boolean | undefined = true;
  @State showInSubwindow: boolean = false;

  @Builder
  MyMenu1() {
    Menu() {
      // 请将$r('app.string.menu_selection')替换为实际资源文件,在本示例中该资源文件的value值为"菜单选项"
      MenuItem({ startIcon: this.iconStr, content: $r('app.string.menu_selection') })
      MenuItem({ startIcon: this.iconStr, content: $r('app.string.menu_selection') })
      MenuItem({ startIcon: this.iconStr, content: $r('app.string.menu_selection') })
      MenuItem({ startIcon: this.iconStr, content: $r('app.string.menu_selection') })
    }
  }

  @State isShow: boolean = false;

  build() {
    NavDestination() {
      Column({ space: 5 }) {
        Button(this.zone + this.arrayStr[this.index])
          .onClick(() => {
            if (this.index < 2) {
              this.index++
            } else {
              this.index = 0
            }
          })

        Button(this.hoverModeStart + this.enableHoverMode)
          .id('hoverMode_start')
          .onClick(() => {
            if (this.enableHoverMode === undefined) {
              this.enableHoverMode = true
            } else if (this.enableHoverMode === true) {
              this.enableHoverMode = false
            } else {
              this.enableHoverMode = undefined
            }
          })
        Button('Menu')
          .fontWeight(FontWeight.Bold)
          .bindMenu(this.MyMenu1(), {
            enableHoverMode: this.enableHoverMode,
            showInSubWindow: this.showInSubwindow
          })
      }
      .height('100%')
      .width('100%')
    }
    // ···

代码逻辑走读:

  1. 组件初始化
    • 使用@Entry@Component装饰器定义了一个组件。
    • 初始化了多个状态变量,如messageupScreenmiddleAxlelowerScreenzonehoverModeStarticonStrindexarrayStrenableHoverModeshowInSubwindow
  2. 资源加载
    • 使用this.getUIContext().getHostContext()?.resourceManager.getStringByNameSync()方法加载资源字符串,如Upper_half_screenMiddle_axleLower_half_screenzone等。
  3. 菜单构建
    • 定义了一个名为MyMenu1@Builder方法,用于构建菜单,包含多个MenuItem
  4. 界面构建
    • build方法中,使用NavDestinationColumn组件构建界面。
    • 添加了三个按钮,分别用于改变index值、切换enableHoverMode状态和展示菜单。
    • 按钮的点击事件通过onClick方法定义,根据不同的条件改变状态。
  5. 状态管理
    • 通过@State装饰器管理组件的状态,如indexenableHoverModeshowInSubwindow等。
    • 使用onClick事件处理函数来更新状态,实现界面的动态交互。

控制子窗菜单的事件透传

当菜单在子窗口中弹出时,默认情况下,菜单周围的事件会传递至所在窗口。从API version 20开始,开发者可通过ContextMenuOptions的modalMode属性设置子菜单弹出时的模态模式,以控制菜单周围事件是否传递。将modalMode设置为ModalMode.TARGET_WINDOW时,菜单周围的事件将不再传递,菜单下方的控件也不会响应事件。

TypeScript 复制代码
@Entry
@Component
export struct EventTransSubWindowMenuExample {
  build() {
    NavDestination() {
      Column() {
      }
      .id('click')
      .bindContextMenu(this.contextMenuBuilder, ResponseType.RightClick, {
        modalMode: ModalMode.TARGET_WINDOW
      })
      .onClick(() => {
        this.getUIContext().getPromptAction().showToast({
          message: 'Clicked!'
        })
      })
      .width('100%')
      .height('100%')
    }
    // ...
  }

  @Builder
  bindMenuBuilder() {
    Menu() {
      MenuItem({ content: 'bindMenu item' }) {

      }
    }
  }

  @Builder
  contextMenuBuilder() {
    Menu() {
      MenuItem({ content: 'contextMenu item' }) {

      }
    }
  }
}

代码逻辑走读:

  1. 组件定义 :使用@Entry@Component装饰器定义了一个名为EventTransSubWindowMenuExample的组件。
  2. 构建方法build()方法是组件的核心,用于定义组件的UI结构。
  3. 导航目的地 :在build()方法中,使用NavDestination创建了一个导航目的地。
  4. 列组件 :在NavDestination内部,使用Column创建了一个列组件。
  5. 绑定上下文菜单 :通过.bindContextMenu()方法,将contextMenuBuilder方法生成的菜单绑定到列组件上,响应右键点击事件,并指定模态窗口模式为目标窗口。
  6. 点击事件 :使用.onClick()方法,为列组件绑定了一个点击事件处理函数,当点击时,显示一个提示消息。
  7. 样式设置:设置列组件的宽度和高度为100%。
  8. 菜单构建 :定义了两个菜单构建器方法bindMenuBuildercontextMenuBuilder,分别用于生成绑定菜单和上下文菜单。
  9. 菜单项 :在bindMenuBuildercontextMenuBuilder方法中,使用MenuMenuItem定义了菜单项内容。

基于绑定组件指定位置弹出菜单

菜单从API version 20开始支持基于绑定组件在指定位置弹出。通过设置水平与垂直偏移量,控制菜单相对于绑定组件左上角的弹出位置。与单独使用offset接口不同,此方法可使菜单覆盖显示在绑定组件上。需要指定弹出位置时,可使用ContextMenuOptions的anchorPosition属性进行设置。

说明

  • 当菜单处于预览状态时,设定的定位偏移量将无法生效。
  • 预设的placement对齐参数将不再生效。
  • 叠加offset参数的偏移量,最终确定菜单的精确显示位置。
  • 当水平与垂直偏移量均设为负值时,菜单以绑定组件左下角为基准点进行显示。
  • 当水平或垂直偏移量存在负值时,组件将以绑定组件的左上角为定位基准点,通过叠加偏移量参数实现反向偏移。
TypeScript 复制代码
@Entry
@Component
export struct BindComponentMenuExample {
  @Builder
  MenuBuilder() {
    Column() {
      Menu() {
        MenuItemGroup() {
          // 请将$r('app.media.app_icon')替换为实际资源文件
          MenuItem({ startIcon: $r('app.media.app_icon'), content: 'Select Mixed Menu 1', labelInfo: '' })
          MenuItem({ startIcon: $r('app.media.app_icon'), content: 'Select Mixed Menu 2', labelInfo: '' })
          MenuItem({ startIcon: $r('app.media.app_icon'), content: 'Select Mixed Menu 3', labelInfo: '' })
        }
      }
    }
  }

  build() {
    NavDestination() {
      Column() {
        Text()
          .borderRadius(10)
          .width(200)
          .height(150)
          .borderWidth(1)
          .backgroundColor(Color.White)
          .borderColor(Color.Red)
          .margin({ top: 200, left: 125 })
          .bindContextMenu(this.MenuBuilder, ResponseType.RightClick, {
            anchorPosition: { x: 45, y: 50 },
          })
      }
      .alignItems(HorizontalAlign.Start)
      .width('100%')
      .height('100%')
      .backgroundColor('#F5F5F5')
    }
    // ...
  }
}

代码逻辑走读:

  1. 组件定义 :使用@Entry@Component装饰器定义了一个名为BindComponentMenuExample的组件。
  2. 菜单构建器 :定义了一个MenuBuilder方法,用于构建一个菜单,菜单包含一个MenuItemGroup,每个菜单项使用MenuItem定义,包含图标、内容和标签信息。
  3. 文本框构建 :在build方法中,创建了一个NavDestination,内部包含一个Column,其中一个Text元素被绑定到MenuBuilder生成的菜单,通过bindContextMenu方法绑定右键点击事件。
  4. 样式设置 :为Text元素设置了边框圆角、宽度、高度、边框宽度、背景色和边框颜色等样式属性,并通过margin设置了位置。
  5. 布局设置Column元素设置了水平对齐方式、宽度、高度和背景色,以确保文本框在布局中的位置和样式。
相关推荐
高心星5 小时前
鸿蒙6.0应用开发——V2装饰器@Monitor的使用
装饰器·monitor·鸿蒙6.0·harmonyos6.0·v2装饰器
高心星2 天前
鸿蒙6.0应用开发面试——ArkUI界面面试问题
面试·鸿蒙6.0·harmonyos6.0·鸿蒙面试
星辰徐哥3 天前
鸿蒙APP开发从入门到精通:ArkUI组件库详解与常用组件实战
华为·app·harmonyos·组件·arkui·组件库
Swift社区7 天前
HarmonyOS 中 MindSpore Lite 源码编译转换工具环境与步骤
华为·harmonyos·arkui
Swift社区8 天前
ArkUI 中 Flex 和 Grid 布局的选择与实践
harmonyos·arkui·flex
小学生波波9 天前
HarmonyOS6 - 鸿蒙ArkUI动画详解
华为·arkts·鸿蒙·arkui·鸿蒙开发·harmonyos6
高心星11 天前
鸿蒙6.0应用开发——V2装饰器@ObservedV2和@Trace的使用
装饰器·observed·鸿蒙6.0·harmonyos6.0·v2装饰器
小学生波波13 天前
HarmonyOS6 - 鸿蒙弹窗选择省市区实战案例
arkts·鸿蒙·arkui·鸿蒙开发·harmonyos6·省市区·级联选择
威哥爱编程15 天前
你的手势冲突解决了吗?鸿蒙事件拦截机制全解析
harmonyos·arkts·arkui