• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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}