1/* 2 * Copyright (c) 2025 Huawei Device Co., Ltd. 3 * Licensed under the Apache License, Version 2.0 (the 'License'); 4 * you may not use this file except in compliance with the License. 5 * You may obtain a copy of the License at 6 * 7 * http://www.apache.org/licenses/LICENSE-2.0 8 * 9 * Unless required by applicable law or agreed to in writing, software 10 * distributed under the License is distributed on an 'AS IS' BASIS, 11 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 * See the License for the specific language governing permissions and 13 * limitations under the License. 14 */ 15 16import { curves, display, LengthMetrics, mediaquery } from '@kit.ArkUI'; 17import { image } from '@kit.ImageKit'; 18 19const LOG_TAG: string = 'CustomAppBar'; 20const VIEW_WIDTH: number = 80; 21const VIEW_HEIGHT: number = 36; 22const BUTTON_SIZE: number = 40; 23const IMAGE_SIZE: string = '20vp'; 24const MENU_RADIUS: string = '20vp'; 25const DIVIDER_HEIGHT: string = '16vp'; 26const BORDER_WIDTH: string = '1px'; 27const VIEW_MARGIN_RIGHT: number = 8; 28const ICON_SIZE: number = 27; 29const ICON_FILL_COLOR_DEFAULT: string = '#182431'; 30const BORDER_COLOR_DEFAULT: string = '#33000000'; 31const MENU_BACK_COLOR: string = '#99FFFFFF'; 32const MENU_BACK_BLUR: number = 5; 33const MENU_MARGIN_TOP: number = 10; 34const SM_MENU_MARGIN_END: number = 16; 35const MD_MENU_MARGIN_END: number = 24; 36const LG_MENU_MARGIN_END: number = 32; 37// 半屏参数 38const BUTTON_IMAGE_SIZE: number = 18; 39const HALF_CONTAINER_BORDER_SIZE: number = 32; 40const HALF_BUTTON_BACK_COLOR: string = '#0D000000'; 41const HALF_BUTTON_IMAGE_COLOR: string = '#0C000000'; 42const HALF_MENU_MARGIN: number = 16; 43const EYELASH_HEIGHT: number = 36; 44const CHEVRON_HEIGHT: number = 20; 45const CHEVRON_WIDTH: number = 10; 46const CHEVRON_MARGIN: number = 4; 47const TITLE_FONT_SIZE: number = 14; 48const TITLE_LINE_HEIGHT: number = 16; 49const TITLE_MARGIN_RIGHT: number = 12; 50const TITLE_MARGIN_TOP: number = 8; 51const TITLE_LABEL_MARGIN: number = 8.5; 52const TITLE_TEXT_MARGIN: number = 3; 53const MD_WIDTH: number = 480; 54const LG_WIDTH_LIMIT: number = 0.6; 55const LG_WIDTH_HEIGHT_RATIO: number = 1.95; 56const ARKUI_APP_BAR_COLOR_CONFIGURATION: string = 'arkui_app_bar_color_configuration'; 57const ARKUI_APP_BAR_MENU_SAFE_AREA: string = 'arkui_app_bar_menu_safe_area'; 58const ARKUI_APP_BAR_CONTENT_SAFE_AREA: string = 'arkui_app_bar_content_safe_area'; 59 60const ARKUI_APP_BAR_BAR_INFO: string = 'arkui_app_bar_info'; 61const ARKUI_APP_BAR_SCREEN: string = 'arkui_app_bar_screen'; 62const ARKUI_APP_BG_COLOR: string = 'arkui_app_bg_color'; 63const ARKUI_APP_BAR_SERVICE_PANEL: string = 'arkui_app_bar_service_panel'; 64const ARKUI_APP_BAR_CLOSE: string = 'arkui_app_bar_close'; 65const EVENT_NAME_CUSTOM_APP_BAR_MENU_CLICK = 'arkui_custom_app_bar_menu_click'; 66const EVENT_NAME_CUSTOM_APP_BAR_CLOSE_CLICK = 'arkui_custom_app_bar_close_click'; 67const EVENT_NAME_CUSTOM_APP_BAR_DID_BUILD = 'arkui_custom_app_bar_did_build'; 68const EVENT_NAME_CUSTOM_APP_BAR_CREATE_SERVICE_PANEL = 'arkui_custom_app_bar_create_service_panel'; 69 70/** 71 * 适配不同颜色模式集合 72 */ 73class ColorGroup { 74 public light: string = '#000000'; 75 public dark: string = '#FFFFFF'; 76 77 constructor(light: string, dark: string) { 78 this.light = light; 79 this.dark = dark; 80 } 81} 82 83enum BreakPointsType { 84 NONE = 'NONE', 85 SM = 'SM', 86 MD = 'MD', 87 LG = 'LG' 88} 89 90const menuMarginEndMap: Map<BreakPointsType, number> = new Map<BreakPointsType, number>([ 91 [BreakPointsType.NONE, SM_MENU_MARGIN_END], 92 [BreakPointsType.SM, SM_MENU_MARGIN_END], 93 [BreakPointsType.MD, MD_MENU_MARGIN_END], 94 [BreakPointsType.LG, LG_MENU_MARGIN_END] 95]); 96 97const colorMap: Map<string, ColorGroup> = new Map<string, ColorGroup>([ 98 [ICON_FILL_COLOR_DEFAULT, new ColorGroup('#182431', '#e5ffffff')], 99 [BORDER_COLOR_DEFAULT, new ColorGroup('#33182431', '#4Dffffff')], 100 [MENU_BACK_COLOR, new ColorGroup('#99FFFFFF', '#33000000')], 101 [HALF_BUTTON_BACK_COLOR, new ColorGroup('#0D000000', '#19FFFFFF')], 102 [HALF_BUTTON_IMAGE_COLOR, new ColorGroup('#000000', '#FFFFFF')] 103]); 104 105@Entry 106@Component 107export struct CustomAppBar { 108 @State menuResource: Resource = { 109 bundleName: '', 110 moduleName: '', 111 params: [], 112 id: 125830217, 113 type: 20000 114 }; 115 @State closeResource: Resource = { 116 bundleName: '', 117 moduleName: '', 118 params: [], 119 id: 125831084, 120 type: 20000 121 }; 122 @State menuFillColor: string = this.getResourceColor(ICON_FILL_COLOR_DEFAULT); 123 @State closeFillColor: string = this.getResourceColor(ICON_FILL_COLOR_DEFAULT); 124 @State menubarBorderColor: string = this.getResourceColor(BORDER_COLOR_DEFAULT); 125 @State menubarBackColor: string = this.getResourceColor(MENU_BACK_COLOR); 126 @State dividerBackgroundColor: string = this.getResourceColor(BORDER_COLOR_DEFAULT); 127 @State halfButtonBackColor: string = this.getResourceColor(HALF_BUTTON_BACK_COLOR); 128 @State halfButtonImageColor: string = this.getResourceColor(HALF_BUTTON_IMAGE_COLOR); 129 @State contentMarginTop: number = 0; 130 @State contentMarginLeft: number = 0; 131 @State contentMarginRight: number = 0; 132 @State contentMarginBottom: number = 0; 133 @State menuMarginEnd: number = SM_MENU_MARGIN_END; 134 // 半屏参数 135 @State isHalfScreen: boolean = true; 136 @State containerHeight: string | number = '0%'; 137 @State containerWidth: string | number = '100%'; 138 @State stackHeight: string = '100%'; 139 @State titleOpacity: number = 0; 140 @State buttonOpacity: number = 1; 141 @State titleHeight: number = 0; 142 @State titleOffset: number = 0; 143 @State maskOpacity: number = 0; 144 @State maskBlurScale: number = 0; 145 @State contentBgColor: ResourceColor = '#FFFFFFFF'; 146 @State statusBarHeight: number = 0; 147 @State ratio: number | undefined = undefined; 148 @State @Watch('onBreakPointChange') breakPoint: BreakPointsType = BreakPointsType.NONE; 149 @State serviceMenuRead: string = this.getStringByResourceToken(ARKUI_APP_BAR_SERVICE_PANEL); 150 @State closeRead: string = this.getStringByResourceToken(ARKUI_APP_BAR_CLOSE); 151 private isHalfToFullScreen: boolean = false; 152 private isDark: boolean = true; 153 private bundleName: string = ''; 154 private labelName: string = ''; 155 private icon: Resource | string | PixelMap = $r('sys.media.ohos_app_icon'); 156 private fullContentMarginTop: number = 0; 157 private windowWidth: number = 0; 158 private windowHeight: number = 0; 159 private smListener: mediaquery.MediaQueryListener = mediaquery.matchMediaSync('(0vp<width) and (width<600vp)'); 160 private mdListener: mediaquery.MediaQueryListener = mediaquery.matchMediaSync('(600vp<=width) and (width<840vp)'); 161 private lgListener: mediaquery.MediaQueryListener = mediaquery.matchMediaSync('(840vp<=width)'); 162 163 aboutToAppear(): void { 164 if (this.isHalfScreen) { 165 this.contentBgColor = Color.Transparent; 166 this.titleHeight = EYELASH_HEIGHT + 2 * TITLE_MARGIN_TOP + this.statusBarHeight; 167 this.halfScreenShowAnimation(); 168 } else { 169 this.containerHeight = '100%'; 170 this.containerWidth = '100%'; 171 } 172 } 173 174 aboutToDisappear(): void { 175 this.smListener.off('change'); 176 this.mdListener.off('change'); 177 this.lgListener.off('change'); 178 } 179 180 initBreakPointListener(): void { 181 this.smListener.on('change', (mediaQueryResult: mediaquery.MediaQueryResult) => { 182 if (mediaQueryResult.matches) { 183 this.breakPoint = BreakPointsType.SM; 184 } 185 }) 186 this.mdListener.on('change', (mediaQueryResult: mediaquery.MediaQueryResult) => { 187 if (mediaQueryResult.matches) { 188 this.breakPoint = BreakPointsType.MD; 189 } 190 }) 191 this.lgListener.on('change', (mediaQueryResult: mediaquery.MediaQueryResult) => { 192 if (mediaQueryResult.matches) { 193 this.breakPoint = BreakPointsType.LG; 194 } 195 }) 196 } 197 198 onBreakPointChange(): void { 199 if (this.windowWidth === 0) { 200 let displayData = display.getDefaultDisplaySync(); 201 this.windowWidth = px2vp(displayData.width); 202 this.windowHeight = px2vp(displayData.height); 203 } 204 if (menuMarginEndMap.has(this.breakPoint)) { 205 this.menuMarginEnd = menuMarginEndMap.get(this.breakPoint) as number; 206 } 207 if (this.isHalfScreen) { 208 if (this.breakPoint === BreakPointsType.SM) { 209 this.containerWidth = '100%'; 210 } else if (this.breakPoint === BreakPointsType.MD) { 211 this.containerWidth = MD_WIDTH; 212 } else if (this.breakPoint === BreakPointsType.LG) { 213 this.containerWidth = 214 this.windowWidth > this.windowHeight ? this.windowHeight * LG_WIDTH_LIMIT : this.windowWidth * LG_WIDTH_LIMIT; 215 } 216 } 217 } 218 219 parseBoolean(value: string): boolean { 220 if (value === 'true') { 221 return true; 222 } 223 return false; 224 } 225 226 getResourceColor(defaultColor: string): string { 227 if (colorMap.has(defaultColor)) { 228 const colorGroup = colorMap.get(defaultColor); 229 if (colorGroup) { 230 return this.isDark ? colorGroup.dark : colorGroup.light; 231 } 232 } 233 return defaultColor; 234 } 235 236 getStringByResourceToken(resName: string): string { 237 try { 238 return getContext(this).resourceManager.getStringByNameSync(resName); 239 } catch (err) { 240 console.error(LOG_TAG, `getAccessibilityDescription, error: ${err.toString()}`); 241 } 242 return ''; 243 } 244 245 /** 246 * atomicservice侧的事件变化回调 247 * @param eventName 事件名称 248 * @param param 事件参数 249 */ 250 setCustomCallback(eventName: string, param: string): void { 251 if (param === null || param === '' || param === undefined) { 252 console.error(LOG_TAG, 'invalid params'); 253 return; 254 } 255 if (eventName === ARKUI_APP_BAR_COLOR_CONFIGURATION) { 256 this.onColorConfigurationUpdate(this.parseBoolean(param)); 257 } else if (eventName === ARKUI_APP_BAR_MENU_SAFE_AREA) { 258 if (this.statusBarHeight === px2vp(Number(param))) { 259 return; 260 } 261 this.statusBarHeight = Number(param); 262 this.titleHeight = EYELASH_HEIGHT + 2 * TITLE_MARGIN_TOP + this.statusBarHeight; 263 } else if (eventName === ARKUI_APP_BAR_CONTENT_SAFE_AREA) { 264 //top left right bottom 265 let splitArray: string[] = param.split('|'); 266 if (splitArray.length < 4) { 267 return; 268 } 269 this.contentMarginTop = this.isHalfScreen ? 0 : Number(splitArray[0]); 270 this.fullContentMarginTop = Number(splitArray[0]); 271 this.contentMarginLeft = Number(splitArray[1]); 272 this.contentMarginRight = Number(splitArray[2]); 273 this.contentMarginBottom = Number(splitArray[3]); 274 } else if (eventName === ARKUI_APP_BAR_BAR_INFO) { 275 let splitArray: string[] = param.split('|'); 276 if (splitArray.length < 2) { 277 return; 278 } 279 this.bundleName = splitArray[0]; 280 this.labelName = splitArray[1]; 281 } else if (eventName === ARKUI_APP_BAR_SCREEN) { 282 this.isHalfScreen = this.parseBoolean(param); 283 this.initBreakPointListener(); 284 } else if (eventName === ARKUI_APP_BG_COLOR) { 285 this.contentBgColor = param; 286 } 287 } 288 289 /** 290 * 颜色变化设置 291 * @param isDark 是否是深色模式 292 */ 293 onColorConfigurationUpdate(isDark: boolean): void { 294 this.isDark = isDark; 295 this.menuFillColor = this.getResourceColor(ICON_FILL_COLOR_DEFAULT); 296 this.closeFillColor = this.getResourceColor(ICON_FILL_COLOR_DEFAULT); 297 this.menubarBorderColor = this.getResourceColor(BORDER_COLOR_DEFAULT); 298 this.dividerBackgroundColor = this.getResourceColor(BORDER_COLOR_DEFAULT); 299 this.menubarBackColor = this.getResourceColor(MENU_BACK_COLOR); 300 this.halfButtonBackColor = this.getResourceColor(HALF_BUTTON_BACK_COLOR); 301 this.halfButtonImageColor = this.getResourceColor(HALF_BUTTON_IMAGE_COLOR) 302 } 303 304 /** 305 * 标题栏图标回调 306 * @param pixelMap 307 */ 308 setAppIcon(pixelMap: image.PixelMap): void { 309 this.icon = pixelMap; 310 } 311 312 /** 313 * 服务面板按钮点击回调 314 */ 315 onMenuButtonClick(): void { 316 } 317 318 /** 319 * 关闭按钮点击回调 320 */ 321 onCloseButtonClick(): void { 322 } 323 324 /** 325 * 点击title栏 326 */ 327 onEyelashTitleClick(): void { 328 } 329 330 /** 331 * 触发构建回调 332 */ 333 onDidBuild(): void { 334 } 335 336 /** 337 * 半屏拉起动效 338 */ 339 halfScreenShowAnimation(): void { 340 animateTo({ 341 duration: 250, 342 curve: Curve.Sharp, 343 }, () => { 344 this.maskOpacity = 0.3; 345 this.maskBlurScale = 1; 346 }); 347 animateTo({ 348 duration: 250, 349 curve: curves.interpolatingSpring(0, 1, 328, 36), 350 }, () => { 351 this.containerHeight = '100%'; 352 this.ratio = this.breakPoint === BreakPointsType.LG ? 1 / LG_WIDTH_HEIGHT_RATIO : undefined; 353 354 }); 355 // 标题栏渐显 356 animateTo({ 357 duration: 100, 358 curve: curves.cubicBezierCurve(0.2, 0, 0.2, 1), 359 }, () => { 360 this.titleOpacity = 1; 361 }); 362 } 363 364 /** 365 * 半屏放大至全屏动效 366 */ 367 expendContainerAnimation(): void { 368 animateTo({ 369 duration: 150, 370 curve: curves.interpolatingSpring(0, 1, 328, 36), 371 onFinish: () => { 372 this.contentBgColor = '#FFFFFF'; 373 this.isHalfToFullScreen = true; 374 } 375 }, () => { 376 this.containerWidth = '100%'; 377 this.contentMarginTop = this.fullContentMarginTop; 378 this.titleOffset = -this.titleHeight; 379 this.isHalfScreen = false; 380 }); 381 // 标题栏渐隐 382 animateTo({ 383 duration: 100, 384 curve: curves.cubicBezierCurve(0.2, 0, 0.2, 1), 385 }, () => { 386 this.titleOpacity = 0; 387 }); 388 } 389 390 /** 391 * 嵌入式关闭动效 392 */ 393 closeContainerAnimation(): void { 394 if (this.isHalfScreen) { 395 this.closeHalfContainerAnimation(); 396 return; 397 } 398 if (this.isHalfToFullScreen) { 399 // 关闭弹框 400 animateTo({ 401 duration: 250, 402 curve: curves.interpolatingSpring(0, 1, 328, 36), 403 onFinish: () => { 404 this.onCloseButtonClick(); 405 } 406 }, () => { 407 this.stackHeight = '0%'; 408 }); 409 } else { 410 this.onCloseButtonClick(); 411 } 412 } 413 414 closeHalfContainerAnimation() { 415 // 关闭弹框 416 animateTo({ 417 duration: 250, 418 curve: curves.interpolatingSpring(0, 1, 328, 36), 419 onFinish: () => { 420 this.onCloseButtonClick(); 421 } 422 }, () => { 423 this.containerHeight = '0%'; 424 this.ratio = undefined; 425 }); 426 // 蒙层渐隐 427 animateTo({ 428 duration: 250, 429 curve: Curve.Sharp, 430 }, () => { 431 this.maskOpacity = 0; 432 this.maskBlurScale = 0; 433 }); 434 // 标题栏渐隐 435 animateTo({ 436 duration: 100, 437 curve: curves.cubicBezierCurve(0.2, 0, 0.2, 1), 438 }, () => { 439 this.titleOpacity = 0; 440 }); 441 } 442 443 @Builder 444 fullScreenMenubar() { 445 Row() { 446 Row() { 447 Button() { 448 Image(this.menuResource) 449 .id('AtomicServiceMenuIconId') 450 .width(IMAGE_SIZE) 451 .height(IMAGE_SIZE) 452 .fillColor(this.menuFillColor) 453 .draggable(false) 454 .interpolation(ImageInterpolation.High) 455 } 456 .id('AtomicServiceMenuId') 457 .type(ButtonType.Normal) 458 .borderRadius({ topLeft: MENU_RADIUS, bottomLeft: MENU_RADIUS }) 459 .backgroundColor(Color.Transparent) 460 .width(BUTTON_SIZE) 461 .height(VIEW_HEIGHT) 462 .accessibilityText(this.serviceMenuRead) 463 .onAccessibilityHover(() => { 464 this.serviceMenuRead = this.getStringByResourceToken(ARKUI_APP_BAR_SERVICE_PANEL); 465 }) 466 .gesture(TapGesture().onAction(() => { 467 this.onMenuButtonClick(); 468 })) 469 470 Divider() 471 .id('AtomicServiceDividerId') 472 .vertical(true) 473 .color(this.dividerBackgroundColor) 474 .lineCap(LineCapStyle.Round) 475 .strokeWidth(BORDER_WIDTH) 476 .height(DIVIDER_HEIGHT) 477 478 Button() { 479 Image(this.closeResource) 480 .id('AtomicServiceCloseIconId') 481 .width(IMAGE_SIZE) 482 .height(IMAGE_SIZE) 483 .fillColor(this.closeFillColor) 484 .draggable(false) 485 .interpolation(ImageInterpolation.High) 486 } 487 .id('AtomicServiceCloseId') 488 .type(ButtonType.Normal) 489 .backgroundColor(Color.Transparent) 490 .borderRadius({ topRight: MENU_RADIUS, bottomRight: MENU_RADIUS }) 491 .width(BUTTON_SIZE) 492 .height(VIEW_HEIGHT) 493 .accessibilityText(this.closeRead) 494 .onAccessibilityHover(() => { 495 this.closeRead = this.getStringByResourceToken(ARKUI_APP_BAR_CLOSE); 496 }) 497 .gesture(TapGesture().onAction(() => { 498 this.closeContainerAnimation(); 499 })) 500 } 501 .borderRadius(MENU_RADIUS) 502 .borderWidth(BORDER_WIDTH) 503 .borderColor(this.menubarBorderColor) 504 .backgroundColor(this.menubarBackColor) 505 .backdropBlur(MENU_BACK_BLUR) 506 .height(VIEW_HEIGHT) 507 .width(VIEW_WIDTH) 508 .align(Alignment.Top) 509 .draggable(false) 510 .geometryTransition('menubar') 511 .id('AtomicServiceMenubarId') 512 } 513 .id('AtomicServiceMenubarRowId') 514 .margin({ top: LengthMetrics.vp(this.statusBarHeight + MENU_MARGIN_TOP), end: LengthMetrics.vp(this.menuMarginEnd) }) 515 .justifyContent(FlexAlign.End) 516 .height(VIEW_HEIGHT) 517 .hitTestBehavior(HitTestMode.Transparent) 518 .width('100%') 519 } 520 521 @Builder 522 eyelashTitle() { 523 Column() { 524 Row() { 525 Row() { 526 Image(this.icon).height(ICON_SIZE).width(ICON_SIZE) 527 .margin({ 528 start: LengthMetrics.vp(CHEVRON_MARGIN) 529 }) 530 Text(this.labelName) 531 .fontSize(TITLE_FONT_SIZE) 532 .lineHeight(TITLE_LINE_HEIGHT) 533 .fontWeight(FontWeight.Medium) 534 .fontColor('#FFFFFF') 535 .margin({ start: LengthMetrics.vp(TITLE_LABEL_MARGIN) }) 536 .maxLines(1) 537 .textOverflow({ overflow: TextOverflow.Ellipsis }) 538 .ellipsisMode(EllipsisMode.END) 539 Text('提供服务') 540 .fontSize(TITLE_FONT_SIZE) 541 .lineHeight(TITLE_LINE_HEIGHT) 542 .fontColor('#FFFFFF') 543 .margin({ start: LengthMetrics.vp(TITLE_TEXT_MARGIN) }) 544 SymbolGlyph($r('sys.symbol.chevron_right')) 545 .height(CHEVRON_HEIGHT) 546 .width(CHEVRON_WIDTH) 547 .margin({ start: LengthMetrics.vp(CHEVRON_MARGIN), end: LengthMetrics.vp(CHEVRON_MARGIN) }) 548 .fontColor([Color.White]) 549 } 550 .height(EYELASH_HEIGHT) 551 .stateStyles({ 552 focused: { 553 .backgroundColor('#0D000000') 554 }, 555 pressed: { 556 .backgroundColor('#1A000000') 557 }, 558 normal: { 559 .backgroundColor(Color.Transparent) 560 } 561 }) 562 .borderRadius(EYELASH_HEIGHT / 2) 563 .onClick(() => { 564 this.onEyelashTitleClick(); 565 }) 566 .margin({ start: LengthMetrics.vp(TITLE_MARGIN_RIGHT) }) 567 } 568 .margin({ 569 top: LengthMetrics.vp(this.statusBarHeight + TITLE_MARGIN_TOP), 570 bottom: LengthMetrics.vp(TITLE_MARGIN_TOP) 571 }) 572 .opacity(this.titleOpacity) 573 .justifyContent(FlexAlign.Start) 574 .width('100%') 575 .hitTestBehavior(HitTestMode.Transparent) 576 } 577 .justifyContent(FlexAlign.Start) 578 .height(this.titleHeight) 579 .offset({ y: this.titleOffset }) 580 .hitTestBehavior(HitTestMode.Transparent) 581 } 582 583 @Builder 584 halfScreenMenuBar() { 585 Column() { 586 Row() { 587 Row() { 588 Button({ type: ButtonType.Circle }) { 589 SymbolGlyph($r('sys.symbol.arrow_up_left_and_arrow_down_right')) 590 .fontSize(BUTTON_IMAGE_SIZE) 591 .fontWeight(FontWeight.Medium) 592 .fontColor([this.halfButtonImageColor]) 593 }.width(BUTTON_SIZE).height(BUTTON_SIZE).backgroundColor(this.halfButtonBackColor) 594 .onClick(() => { 595 this.expendContainerAnimation(); 596 }) 597 598 Button({ type: ButtonType.Circle }) { 599 SymbolGlyph($r('sys.symbol.xmark')) 600 .fontSize(BUTTON_IMAGE_SIZE) 601 .fontWeight(FontWeight.Medium) 602 .fontColor([this.halfButtonImageColor]) 603 } 604 .width(BUTTON_SIZE) 605 .height(BUTTON_SIZE) 606 .margin({ 607 start: LengthMetrics.vp(VIEW_MARGIN_RIGHT), 608 }) 609 .backgroundColor(this.halfButtonBackColor) 610 .onClick(() => { 611 this.closeContainerAnimation(); 612 }) 613 } 614 .geometryTransition('menubar') 615 .justifyContent(FlexAlign.End) 616 .transition(TransitionEffect.OPACITY) 617 .borderRadius(MENU_RADIUS) 618 .height(BUTTON_SIZE) 619 .margin({ 620 top: LengthMetrics.vp(this.titleHeight + HALF_MENU_MARGIN), 621 end: LengthMetrics.vp(HALF_MENU_MARGIN) 622 }) 623 } 624 .width(this.containerWidth) 625 .height(this.containerHeight) 626 .aspectRatio(this.ratio) 627 .alignItems(VerticalAlign.Top) 628 .justifyContent(FlexAlign.End) 629 .opacity(this.buttonOpacity) 630 }.height('100%') 631 .width('100%') 632 .justifyContent(FlexAlign.End) 633 .hitTestBehavior(HitTestMode.Transparent) 634 } 635 636 build() { 637 Column() { 638 Stack({ alignContent: Alignment.TopEnd }) { 639 if (this.isHalfScreen) { 640 // 透明模糊背板 641 Column() 642 .width('100%') 643 .height('100%') 644 .backgroundColor('#262626') 645 .opacity(this.maskOpacity) 646 .foregroundBlurStyle(BlurStyle.BACKGROUND_REGULAR, { scale: this.maskBlurScale }) 647 .onClick(() => { 648 this.closeContainerAnimation(); 649 }) 650 } 651 Column() { 652 Column() { 653 if (this.isHalfScreen) { 654 this.eyelashTitle() 655 } 656 Row() { 657 } 658 .padding({ 659 top: this.contentMarginTop, 660 left: this.contentMarginLeft, 661 right: this.contentMarginRight, 662 bottom: this.contentMarginBottom 663 }) 664 .layoutWeight(1) 665 .backgroundColor(Color.Transparent) 666 .backgroundBlurStyle(BlurStyle.COMPONENT_ULTRA_THICK) 667 .borderRadius({ 668 topLeft: HALF_CONTAINER_BORDER_SIZE, 669 topRight: HALF_CONTAINER_BORDER_SIZE, 670 }) 671 .clip(true) 672 .alignItems(VerticalAlign.Bottom) 673 .hitTestBehavior(HitTestMode.Transparent) 674 .width('100%') 675 .id('AtomicServiceStageId') 676 } 677 .height(this.containerHeight) 678 .width(this.containerWidth) 679 .aspectRatio(this.ratio) 680 .justifyContent(FlexAlign.End) 681 .hitTestBehavior(HitTestMode.Transparent) 682 }.height('100%') 683 .width('100%') 684 .justifyContent(FlexAlign.End) 685 .hitTestBehavior(HitTestMode.Transparent) 686 687 if (this.isHalfScreen) { 688 this.halfScreenMenuBar() 689 } else { 690 this.fullScreenMenubar() 691 } 692 } 693 .height(this.stackHeight) 694 .width('100%') 695 .backgroundColor(this.contentBgColor) 696 .hitTestBehavior(HitTestMode.Transparent) 697 .id('AtomicServiceContainerId') 698 } 699 .height('100%') 700 .width('100%') 701 .justifyContent(FlexAlign.End) 702 .backgroundColor(Color.Transparent) 703 .hitTestBehavior(HitTestMode.Transparent) 704 } 705}