鸿蒙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元素设置了水平对齐方式、宽度、高度和背景色,以确保文本框在布局中的位置和样式。
相关推荐
JohnnyDeng9418 小时前
【鸿蒙】HarmonyOS 数据持久化:Preferences/KV Store/RelationalStore 选型指南
harmonyos·arkts·鸿蒙·数据持久化·arkui
JohnnyDeng941 天前
【鸿蒙】HarmonyOS 通知与后台任务:WorkScheduler 机制深度解析
harmonyos·arkts·鸿蒙·arkui·后台任务
JohnnyDeng942 天前
【鸿蒙】ArkUI 列表性能优化:LazyForEach 与组件复用深度解析
性能优化·harmonyos·arkts·鸿蒙·arkui
高心星3 天前
鸿蒙6.0应用开发——网络状态管理
网络·华为·网络状态·鸿蒙6.0·harmonyos6.0·网络重连
高心星3 天前
鸿蒙6.0应用开发——Preferences数据存储
华为·preferences·首选项·鸿蒙6.0·harmonyos6.0·用户首选项
高心星4 天前
鸿蒙6.0应用开发——实况窗开发
华为·通知·鸿蒙6.0·harmonyos6.0·实况窗
高心星8 天前
鸿蒙6.0应用开发——应用内存占用优化
性能优化·生命周期·内存优化·图片处理·鸿蒙6.0·harmonyos6.0
高心星13 天前
鸿蒙6.0应用开发——访问应用文件
华为·文件读写·fs·鸿蒙6.0·harmonyos6.0·应用文件·fileio
高心星15 天前
鸿蒙6.0应用开发——Web组件的生命周期
html·web组件·arkweb·鸿蒙6.0·harmonyos6.0
曲幽17 天前
FastApiAdmin 后端接口开发好了,前端管理界面怎么调用与显示?
python·vue3·api·fastapi·web·ant design·view·menu·frontend