1# 菜单控制(Menu) 2<!--Kit: ArkUI--> 3<!--Subsystem: ArkUI--> 4<!--Owner: @Armstrong15--> 5<!--Designer: @zhanghaibo0--> 6<!--Tester: @lxl007--> 7<!--Adviser: @HelloCrease--> 8 9Menu是菜单接口,一般用于鼠标右键弹窗、点击弹窗等。具体用法请参考[菜单控制](../reference/apis-arkui/arkui-ts/ts-universal-attributes-menu.md)。 10 11使用[bindContextMenu](../reference//apis-arkui/arkui-ts/ts-universal-attributes-menu.md#bindcontextmenu12)并设置预览图,菜单弹出时有蒙层,此时为模态。 12 13使用[bindMenu](../reference/apis-arkui/arkui-ts/ts-universal-attributes-menu.md#bindmenu11)或bindContextMenu未设置预览图时,菜单弹出无蒙层,此时为非模态。 14 15## 生命周期 16 17| 名称| 类型 | 说明 | 18| --- | --- | --- | 19| aboutToAppear | () => void | 菜单显示动效前的事件回调。 | 20| onAppear | () => void | 菜单弹出时的事件回调。 | 21| aboutToDisappear | () => void | 菜单退出动效前的事件回调。 | 22| onDisappear | () => void | 菜单消失时的事件回调。 | 23 24 25 26## 创建默认样式的菜单 27 28菜单需要调用bindMenu接口来实现。bindMenu响应绑定组件的点击事件,绑定组件后手势点击对应组件后即可弹出。 29 30```ts 31Button('click for Menu') 32 .bindMenu([ 33 { 34 value: 'Menu1', 35 action: () => { 36 console.info('handle Menu1 select'); 37 } 38 } 39 ]) 40``` 41 42 43 44## 创建自定义样式的菜单 45 46当默认样式不满足开发需求时,可使用[@Builder](../../application-dev/ui/state-management/arkts-builder.md)自定义菜单内容,通过bindMenu接口进行菜单的自定义。 47 48### 使用@Builder自定义菜单内容 49 50```ts 51class Tmp { 52 // $r('app.media.xxx')需要替换为开发者所需的图像资源文件。 53 iconStr2: ResourceStr = $r("app.media.view_list_filled"); 54 55 set(val: Resource) { 56 this.iconStr2 = val; 57 } 58} 59 60@Entry 61@Component 62struct menuExample { 63 @State select: boolean = true; 64 private iconStr: ResourceStr = $r("app.media.view_list_filled"); 65 private iconStr2: ResourceStr = $r("app.media.view_list_filled"); 66 67 @Builder 68 SubMenu() { 69 Menu() { 70 MenuItem({ content: "复制", labelInfo: "Ctrl+C" }) 71 MenuItem({ content: "粘贴", labelInfo: "Ctrl+V" }) 72 } 73 } 74 75 @Builder 76 MyMenu() { 77 Menu() { 78 MenuItem({ startIcon: $r("app.media.icon"), content: "菜单选项" }) 79 MenuItem({ startIcon: $r("app.media.icon"), content: "菜单选项" }).enabled(false) 80 MenuItem({ 81 startIcon: this.iconStr, 82 content: "菜单选项", 83 endIcon: $r("app.media.arrow_right_filled"), 84 // 当builder参数进行配置时,表示与menuItem项绑定了子菜单。鼠标hover在该菜单项时,会显示子菜单。 85 builder: this.SubMenu 86 }) 87 MenuItemGroup({ header: '小标题' }) { 88 MenuItem({ content: "菜单选项" }) 89 .selectIcon(true) 90 .selected(this.select) 91 .onChange((selected) => { 92 console.info("menuItem select" + selected); 93 let Str: Tmp = new Tmp(); 94 Str.set($r("app.media.icon")); 95 }) 96 MenuItem({ 97 startIcon: $r("app.media.view_list_filled"), 98 content: "菜单选项", 99 endIcon: $r("app.media.arrow_right_filled"), 100 builder: this.SubMenu 101 }) 102 } 103 104 MenuItem({ 105 startIcon: this.iconStr2, 106 content: "菜单选项", 107 endIcon: $r("app.media.arrow_right_filled") 108 }) 109 } 110 } 111 112 build() { 113 // ... 114 } 115} 116 117``` 118 119### 使用bindMenu属性绑定组件 120 121```ts 122Button('click for Menu') 123 .bindMenu(this.MyMenu) 124``` 125 126 127 128## 创建支持右键或长按的菜单 129 130通过bindContextMenu接口自定义菜单,设置菜单弹出的触发方式,触发方式为右键或长按。使用bindContextMenu弹出的菜单项是在独立子窗口内的,可显示在应用窗口外部。 131 132- 使用@Builder自定义菜单内容,与上文写法相同。 133- 确认菜单的弹出方式,并使用bindContextMenu属性绑定组件。示例中为右键弹出菜单。 134 135 ```ts 136 Button('click for Menu') 137 .bindContextMenu(this.MyMenu, ResponseType.RightClick) 138 ``` 139 140## 菜单弹出时振动效果 141 142菜单从API version 18开始支持振动效果。菜单弹出时,默认不振动。若希望菜单弹出时有振动效果,可以通过[ContextMenuOptions](../reference/apis-arkui/arkui-ts/ts-universal-attributes-menu.md#contextmenuoptions10)的hapticFeedbackMode属性,设置菜单弹出时的振动模式。 143 144- 只有一级菜单可配置弹出时振动效果。 145- 仅当应用具备ohos.permission.VIBRATE权限,且用户启用了触感反馈时才会生效。开启触控反馈时,需要在工程的module.json5中配置[声明权限](../security/AccessToken/declare-permissions.md)的requestPermissions字段开启振动权限,配置如下: 146 147 ```json 148 "requestPermissions": [ 149 { 150 "name": "ohos.permission.VIBRATE", 151 } 152 ] 153 ``` 154 155```ts 156 Button('click for Menu') 157 .bindContextMenu(this.MyMenu, ResponseType.RightClick, { hapticFeedbackMode: HapticFeedbackMode.ENABLED }) 158``` 159 160## 菜单支持避让中轴 161 162从API version 18起,菜单支持中轴避让功能。从API version 20开始,在2in1设备上默认启用(仅在窗口处于瀑布模式时产生避让)。开发者可通过[ContextMenuOptions](../reference/apis-arkui/arkui-ts/ts-universal-attributes-menu.md#contextmenuoptions10)中的enableHoverMode属性,控制菜单是否启用中轴避让。 163 164> **说明:** 165> - 如果菜单的点击位置在中轴区域,则菜单不会避让。 166> - 2in1设备上需同时满足窗口处于瀑布模式才会产生避让。 167 168```ts 169@Entry 170@Component 171struct Index { 172 @State message: string = 'Hello World'; 173 // $r('app.media.startIcon')需要替换为开发者所需的图像资源文件。 174 private iconStr: Resource = $r('app.media.startIcon'); 175 @State index: number = 0; 176 @State arrayStr: Array<string> = ['上半屏', '中轴', '下半屏']; 177 @State enableHoverMode: boolean | undefined = true; 178 @State showInSubwindow: boolean = false; 179 @State placement: Placement | undefined = undefined; 180 181 @Builder 182 MyMenu1() { 183 Menu() { 184 MenuItem({ startIcon: this.iconStr, content: '菜单选项' }) 185 MenuItem({ startIcon: this.iconStr, content: '菜单选项' }) 186 MenuItem({ startIcon: this.iconStr, content: '菜单选项' }) 187 MenuItem({ startIcon: this.iconStr, content: '菜单选项' }) 188 } 189 } 190 191 @State isShow: boolean = false; 192 193 build() { 194 RelativeContainer() { 195 Column() { 196 Button('区域:' + this.arrayStr[this.index]) 197 .onClick(() => { 198 if (this.index < 2) { 199 this.index++ 200 } else { 201 this.index = 0 202 } 203 }) 204 205 Button('hoverMode开启:' + this.enableHoverMode) 206 .onClick(() => { 207 if (this.enableHoverMode == undefined) { 208 this.enableHoverMode = true 209 } else if (this.enableHoverMode == true) { 210 this.enableHoverMode = false 211 } else { 212 this.enableHoverMode = undefined 213 } 214 }) 215 216 Button('MenuPlacement:' + this.placement) 217 .onClick(() => { 218 if (this.placement == undefined) { 219 this.placement = Placement.Bottom 220 } else if (this.placement == Placement.Bottom) { 221 this.placement = Placement.Top 222 } else { 223 this.placement = undefined 224 } 225 }) 226 } 227 228 Row() { 229 Button('Menu') 230 .fontWeight(FontWeight.Bold) 231 .bindMenu(this.MyMenu1(), { 232 enableHoverMode: this.enableHoverMode, 233 showInSubWindow: this.showInSubwindow, 234 placement: this.placement 235 }) 236 237 Select([{ value: 'text1' }, { value: 'text2' }, { value: 'text3' }, { value: 'text4' }, { value: 'text5' }, 238 { value: 'text6' }, { value: 'text7' }, { value: 'text8' }, { value: 'text9' }, { value: 'text10' }, { value: 'text11' }, 239 { value: 'text12' }]) 240 .value("Select") 241 242 } 243 .alignRules({ 244 center: { anchor: '__container__', align: VerticalAlign.Center }, 245 middle: { anchor: '__container__', align: HorizontalAlign.Center } 246 }) 247 .margin({ 248 top: this.index == 2 ? 330 : this.index == 1 ? 50 : 0, 249 bottom: this.index == 0 ? 330 : 0 250 }) 251 } 252 .height('100%') 253 .width('100%') 254 } 255} 256 257``` 258 259## 控制子窗菜单的事件透传 260 261当菜单在子窗口中弹出时,默认情况下,菜单周围的事件会传递至所在窗口。从API version 20开始,开发者可通过[ContextMenuOptions](../reference/apis-arkui/arkui-ts/ts-universal-attributes-menu.md#contextmenuoptions10)的modalMode属性设置子菜单弹出时的模态模式,以控制菜单周围事件是否传递。将modalMode设置为ModalMode.TARGET_WINDOW时,菜单周围的事件将不再传递,菜单下方的控件也不会响应事件。 262 263```ts 264@Entry 265@Component 266struct Index2 { 267 build() { 268 Column() { 269 } 270 .bindContextMenu(this.contextMenuBuilder, ResponseType.RightClick, { 271 modalMode: ModalMode.TARGET_WINDOW 272 }) 273 .onClick(() => { 274 this.getUIContext().getPromptAction().showToast({ 275 message: 'Clicked!' 276 }) 277 }) 278 .width('100%') 279 .height('100%') 280 } 281 282 @Builder 283 bindMenuBuilder() { 284 Menu() { 285 MenuItem({ content: 'bindMenu item' }) { 286 287 } 288 } 289 } 290 291 @Builder 292 contextMenuBuilder() { 293 Menu() { 294 MenuItem({ content: 'contextMenu item' }) { 295 296 } 297 } 298 } 299} 300 301``` 302 303## 基于绑定组件指定位置弹出菜单 304 305菜单从API version 20开始支持基于绑定组件在指定位置弹出。通过设置水平与垂直偏移量,控制菜单相对于绑定组件左上角的弹出位置。与单独使用offset接口不同,此方法可使菜单覆盖显示在绑定组件上。需要指定弹出位置时,可使用[ContextMenuOptions](../reference/apis-arkui/arkui-ts/ts-universal-attributes-menu.md#contextmenuoptions10)的anchorPosition属性进行设置。 306 307> **说明:** 308>- 当菜单处于预览状态时,设定的定位偏移量将无法生效。 309>- 预设的[placement](../reference/apis-arkui/arkui-ts/ts-universal-attributes-menu.md#contextmenuoptions10)对齐参数将不再生效。 310>- 叠加[offset](../reference/apis-arkui/arkui-ts/ts-universal-attributes-menu.md#contextmenuoptions10)参数的偏移量,最终确定菜单的精确显示位置。 311>- 当水平与垂直偏移量均设为负值时,菜单以绑定组件左下角为基准点进行显示。 312>- 当水平或垂直偏移量存在负值时,组件将以绑定组件的左上角为定位基准点,通过叠加偏移量参数实现反向偏移。 313 314```ts 315@Entry 316@Component 317struct DirectiveMenuExample { 318 @Builder 319 MenuBuilder() { 320 Column() { 321 Menu() { 322 MenuItemGroup() { 323 // $r('app.media.app_icon')需要替换为开发者所需的图像资源文件。 324 MenuItem({ startIcon: $r('app.media.app_icon'), content: "Select Mixed Menu 1", labelInfo: "" }) 325 MenuItem({ startIcon: $r('app.media.app_icon'), content: "Select Mixed Menu 2", labelInfo: "" }) 326 MenuItem({ startIcon: $r('app.media.app_icon'), content: "Select Mixed Menu 3", labelInfo: "" }) 327 } 328 } 329 } 330 } 331 332 build() { 333 Column() { 334 Text() 335 .borderRadius(10) 336 .width(200) 337 .height(150) 338 .borderWidth(1) 339 .backgroundColor(Color.White) 340 .borderColor(Color.Red) 341 .margin({ top: 200, left: 125 }) 342 .bindContextMenu(this.MenuBuilder, ResponseType.RightClick, { 343 anchorPosition: { x: 45, y: 50 }, 344 }) 345 } 346 .alignItems(HorizontalAlign.Start) 347 .width('100%') 348 .height('100%') 349 .backgroundColor('#F5F5F5') 350 } 351} 352``` 353 354 355 356