• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1/*
2 * Copyright (c) 2024 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 { BusinessError, Callback } from '@ohos.base';
17import { curves, LengthMetrics, SymbolGlyphModifier, window, AtomicServiceBar } from '@kit.ArkUI';
18import { bundleManager, common } from '@kit.AbilityKit';
19import { hilog } from '@kit.PerformanceAnalysisKit';
20import { i18n } from '@kit.LocalizationKit';
21
22/**
23 * 背景渐变色相关数据
24 */
25const backGroundColor: string[] = ['rgb(0,0,0)', 'rgb(255,255,255)', 'rgb(241,243,245)'];
26const backGroundTransparentGradientColor: string[][] = [['rgba(0,0,0,0)', 'rgba(0,0,0,1)'],
27  ['rgba(255,255,255,0)', 'rgba(255,255,255,1)'], ['rgba(241,243,245,0)', 'rgba(241,243,245,1)']];
28const transparencyMapArray: number[] = [0.15, 0.15, 0.4, 0.6, 0.8];
29const RECTANGLE_OUTSIDE_OFFSET_ONE = 1;
30const COLOR_RATIO_THIRTY_PERCENT = 0.3;
31const COLOR_RATIO_FIFTY_PERCENT = 0.5;
32const COLOR_RATIO_SEVENTY_PERCENT = 0.7;
33const COLOR_RATIO_FORTY_PERCENT = 0.4;
34const COLOR_RATIO_SIXTY_PERCENT = 0.6;
35const COLOR_RATIO_ONE_FIFTY_PERCENT = 1.5;
36const COORDINATE_NEGATIVE_ONE = -1;
37const BLUR_CONSTANT = 500;
38
39/**
40 * 三种标题栏相关数据
41 */
42const BREAK_POINT_VP_SM = 600;
43const BREAK_POINT_VP_MD = 840;
44const BREAK_POINT_SM = 'sm';
45const BREAK_POINT_MD = 'md';
46const BREAK_POINT_LG = 'lg';
47const SIDE_BAR_EMBED_MIN_WIDTH = 240;
48const SIDE_BAR_OVERLAY_WIDTH = 304;
49const SIDE_BAR_COMMON_WIDTH = 360;
50const CONTENT_MIN_WIDTH = 600;
51/**
52 * menubar避让区域宽度计算修正时用到的数据
53 */
54const MENUBAR_X_FIRST_THRESHOLD: number = 200;
55const MENUBAR_X_SECOND_THRESHOLD: number = 40;
56const MENUBAR_CORRECTION_OFFSET_VALUE: number = 92;
57/**
58 * 标题栏统一样式配置数据
59 */
60const TITLE_MAX_LINES: number = 2;
61const TITLE_MIN_FONT_SIZE: number = 14;
62const TITLE_MAX_FONT_SIZE: number = 26;
63const DEFAULT_TITLE_HEIGHT: number = 36;
64const TITLE_LAYOUT_WEIGHT: number = 1;
65const DEFAULT_PADDING_START_DISTANCE: number = 32;
66const DEFAULT_MARGIN_TOP_DISTANCE: number = 26;
67const TITLE_FONT_COLOR: ResourceStr = $r('sys.color.font_primary');
68
69/**
70 * 背景颜色的不透明度的枚举类型
71 *
72 * @enum { number }.
73 */
74export enum GradientAlpha {
75  /**
76   * 不透明度为0.2
77   *
78   */
79  OPACITY_20 = 1,
80  /**
81   * 不透明度为0.6
82   *
83   */
84  OPACITY_60 = 2,
85  /**
86   * 不透明度为0.8
87   */
88  OPACITY_80 = 3,
89  /**
90   * 不透明度为1.0
91   */
92  OPACITY_100 = 4
93}
94
95/**
96 * 背景颜色融合方式
97 *
98 * @enum { number }.
99 */
100export enum MixMode {
101  /**
102   * 两种颜色所占比例相同
103   */
104  AVERAGE = 1,
105  /**
106   * 一种颜色穿过另一种颜色
107   */
108  CROSS = 2,
109  /**
110   * 一种颜色渐渐转变为另一种颜色
111   */
112  TOWARDS = 3
113}
114
115/**
116 * 背景底色
117 *
118 * @enum { number }.
119 */
120export enum BackgroundTheme {
121  /**
122   * 黑色
123   */
124  DARK = 1,
125  /**
126   * 白色
127   */
128  LIGHT = 2,
129  /**
130   * 颜色值 #F1F3F5
131   */
132  DEFAULT = 3
133}
134
135@Component
136export struct AtomicServiceNavigation {
137  @State navPathStack?: NavPathStack = new NavPathStack();
138  @BuilderParam navigationContent?: Callback<void>;
139  @Prop title?: ResourceStr;
140  @Prop titleOptions?: TitleOptions = { isBlurEnabled: true };
141  @Prop gradientBackground?: GradientBackground;
142  @Prop hideTitleBar?: boolean;
143  @Prop navBarWidth?: Length;
144  @Prop mode?: NavigationMode;
145  @BuilderParam navDestinationBuilder?: NavDestinationBuilder = this.defaultNavDestinationBuilder;
146  @Prop navBarWidthRange?: [Dimension, Dimension];
147  @Prop minContentWidth?: Dimension;
148  @Prop sideBarOptions?: SideBarOptions;
149  @BuilderParam sideBarContent?: Callback<void>;
150  @State private showMaskLayer: boolean = false;
151  @State private marginWindowLeft: number = 16;
152  @State private currentBreakPoint: string = BREAK_POINT_SM;
153  @State private sideBarAttribute: sideBarAttributeSet = new sideBarAttributeSet();
154  @State private controlButtonVisible: boolean = true;
155  @State private titleBuilderPaddingEndWidth: number = 0;
156  @BuilderParam menus?: CustomBuilder | Array<NavigationMenuItem>;
157  stateChangeCallback?: Callback<boolean>;
158  modeChangeCallback?: Callback<NavigationMode>;
159
160  private settings: RenderingContextSettings = new RenderingContextSettings(true);
161  private context: CanvasRenderingContext2D = new CanvasRenderingContext2D(this.settings);
162  @State private navigationWidth: number = 0;
163  @State private navigationHeight: number = 0;
164
165  private mainWindow?: window.Window;
166  private onWindowSizeChangeCallback?: Callback<Size>;
167  private onAvoidSafeAreaChangeCallback?: Callback<window.AvoidAreaOptions>;
168  private sideBarHelper?: SideBarHelper;
169  private atomicServiceIcon?: PixelMap;
170  private navigationBarVisibility: boolean = true;
171
172  @Builder
173  defaultNavDestinationBuilder(name: string, param?: Object) {
174  }
175
176  @Builder
177  BackgroundBuilder(gradientBackground: GradientBackground) {
178    Canvas(this.context)
179      .opacity(transparencyMapArray[(gradientBackground.alpha === undefined) ? GradientAlpha.OPACITY_20 :
180      gradientBackground.alpha])
181      .backdropBlur(BLUR_CONSTANT)
182      .height(this.navigationHeight)
183      .backgroundColor(gradientBackground.backgroundTheme === undefined ? backGroundColor[2] :
184      backGroundColor[gradientBackground.backgroundTheme - 1])
185      .onReady(() => {
186        if (gradientBackground.secondaryColor === undefined) {
187          //单色渐变
188          this.drawSingleGradient(this.context, gradientBackground.primaryColor,
189            gradientBackground.backgroundTheme === undefined ? backGroundColor[2] :
190            backGroundColor[gradientBackground.backgroundTheme - 1]);
191        } else {
192          if (gradientBackground.mixMode === MixMode.AVERAGE) {
193            //双色渐变五五分
194            this.drawGradientCanvasHalf(this.context, gradientBackground.primaryColor,
195              gradientBackground.secondaryColor);
196          } else if (gradientBackground.mixMode === MixMode.CROSS) {
197            //第一种双色渐变三七分
198            this.drawGradientCanvasCross(this.context, gradientBackground.primaryColor,
199              gradientBackground.secondaryColor);
200          } else {
201            //第二种双色渐变三七分
202            this.drawGradientCanvasTowards(this.context, gradientBackground.primaryColor,
203              gradientBackground.secondaryColor);
204          }
205          this.drawTransparentGradient(this.context,
206            gradientBackground.backgroundTheme === undefined ? BackgroundTheme.DEFAULT :
207            gradientBackground.backgroundTheme);
208        }
209      }).expandSafeArea([SafeAreaType.SYSTEM], [SafeAreaEdge.TOP, SafeAreaEdge.BOTTOM])
210  }
211
212  aboutToAppear(): void {
213    this.initWindow();
214    this.initIcon();
215    this.initSideBarAttr();
216  }
217
218  /**
219   * 初始化侧边栏相关信息
220   */
221  private initSideBarAttr(): void {
222    if (this.titleOptions?.titleBarType !== TitleBarType.DRAWER) {
223      return;
224    }
225    this.sideBarAttribute = new sideBarAttributeSet();
226    this.sideBarHelper = new SideBarHelper();
227    let sideBarStatusListener = (show: boolean) => {
228      this.sideBarAttribute.showSideBar = show;
229      this.updateControlButtonVisibility();
230      if (this.sideBarOptions?.onChange) {
231        this.sideBarOptions.onChange(show);
232      }
233    };
234    this.sideBarHelper.registerListener(sideBarStatusListener);
235  }
236
237  /**
238   * 窗口初始化或者尺寸发生变化时,让menubar避让宽度更新
239   */
240  private freshMenubarAvoidAreaWidth(mainWindow: window.Window): void {
241    setTimeout(() => {
242      const atomicServiceBar: Nullable<AtomicServiceBar> = this.getUIContext().getAtomicServiceBar();
243      if (!atomicServiceBar) {
244        this.titleBuilderPaddingEndWidth = 0;
245        return;
246      }
247      let menubarX: number = atomicServiceBar.getBarRect().x;
248      let corretionWidth: number = 0;
249      if (menubarX > MENUBAR_X_FIRST_THRESHOLD) {
250        const mainWindowWidth: number = px2vp(mainWindow.getWindowProperties()?.windowRect?.width) - menubarX;
251        corretionWidth = mainWindowWidth > MENUBAR_X_FIRST_THRESHOLD ? 0 : mainWindowWidth;
252      } else if (menubarX < MENUBAR_X_SECOND_THRESHOLD) {
253        corretionWidth = menubarX + MENUBAR_CORRECTION_OFFSET_VALUE;
254      }
255      let currentWidth: number = atomicServiceBar.getBarRect().width;
256      this.titleBuilderPaddingEndWidth = corretionWidth > currentWidth ? corretionWidth : currentWidth;
257    }, 100);
258  }
259
260  /**
261   * 初始化window,并设置windowSizeChange监听,更新断点信息
262   */
263  private initWindow(): void {
264    let context = getContext(this) as common.UIAbilityContext;
265    context?.windowStage?.getMainWindow().then(mainWindow => {
266      if (!mainWindow) {
267        return;
268      }
269      this.mainWindow = mainWindow;
270      if (this.titleOptions?.titleBarType === TitleBarType.DRAWER) {
271        this.sideBarHelper?.updateLayout(this.currentBreakPoint, this.sideBarAttribute);
272      }
273      this.updateBreakPoint(mainWindow.getWindowProperties()?.windowRect?.width);
274      this.freshMenubarAvoidAreaWidth(mainWindow);
275      this.onWindowSizeChangeCallback = ((windowSize) => {
276        this.updateBreakPoint(windowSize?.width);
277        this.freshMenubarAvoidAreaWidth(mainWindow);
278      });
279      mainWindow.on('windowSizeChange', this.onWindowSizeChangeCallback);
280    }).catch((err: Error) => {
281      console.error(`AtomicServiceNavigation get main window failed, message is ${err?.message}`);
282    });
283  }
284
285  /**
286   * 初始化icon,用户如果设置了则用自定义的icon,没有设置则用元服务图标
287   */
288  private initIcon(): void {
289    if ((this.titleOptions?.titleBarType !== TitleBarType.ROUND_ICON &&
290      this.titleOptions?.titleBarType !== TitleBarType.SQUARED_ICON) || this.titleOptions?.titleIcon) {
291      return;
292    }
293    let bundleFlags: number = bundleManager.BundleFlag.GET_BUNDLE_INFO_WITH_APPLICATION;
294    let bundleInfo: bundleManager.BundleInfo = bundleManager.getBundleInfoForSelfSync(bundleFlags);
295    let iconRes: Resource = bundleInfo?.appInfo?.iconResource;
296    this.atomicServiceIcon = getContext(this)?.resourceManager?.getDrawableDescriptor(iconRes)?.getPixelMap();
297  }
298
299  /**
300   * 更新断点信息
301   */
302  private updateBreakPoint(windowWidth: number): void {
303    if (!windowWidth || windowWidth <= 0) {
304      return;
305    }
306    let widthVp = px2vp(windowWidth);
307    let newBreakPoint: string = '';
308    if (widthVp < BREAK_POINT_VP_SM) {
309      newBreakPoint = BREAK_POINT_SM;
310    } else if (widthVp < BREAK_POINT_VP_MD) {
311      newBreakPoint = BREAK_POINT_MD;
312    } else {
313      newBreakPoint = BREAK_POINT_LG;
314    }
315    if (this.currentBreakPoint !== newBreakPoint) {
316      this.currentBreakPoint = newBreakPoint;
317      this.updateMargin();
318      if (this.titleOptions?.titleBarType === TitleBarType.DRAWER) {
319        this.sideBarHelper?.updateLayout(this.currentBreakPoint, this.sideBarAttribute);
320      }
321    }
322  }
323
324  /**
325   * 更新边距
326   */
327  private updateMargin(): void {
328    switch (this.currentBreakPoint) {
329      case BREAK_POINT_MD:
330        this.marginWindowLeft = 24;
331        break;
332      case BREAK_POINT_LG:
333        this.marginWindowLeft = 32;
334        break;
335      case BREAK_POINT_SM:
336      default:
337        this.marginWindowLeft = 16;
338        break;
339    }
340  }
341
342  aboutToDisappear(): void {
343    if (this.onWindowSizeChangeCallback) {
344      this.mainWindow?.off('windowSizeChange', this.onWindowSizeChangeCallback);
345    }
346  }
347
348  /**
349   * 更新control button的可见性
350   */
351  updateControlButtonVisibility(): void {
352    if (this.titleOptions?.titleBarType !== TitleBarType.DRAWER) {
353      return;
354    }
355
356    // 如果侧边栏显示,那么控制图标一定需要显示
357    if (this.sideBarAttribute.showSideBar && this.controlButtonVisible) {
358      return;
359    }
360
361    if (this.currentBreakPoint === BREAK_POINT_LG) {
362      if (this.sideBarAttribute.showSideBar) {
363        if (!this.controlButtonVisible) {
364          this.controlButtonVisible = true;
365        }
366        return;
367      }
368
369      if (!this.navigationBarVisibility && !this.sideBarAttribute.showSideBar) {
370        if (this.controlButtonVisible) {
371          this.controlButtonVisible = false;
372        }
373      } else {
374        if (!this.controlButtonVisible) {
375          this.controlButtonVisible = true;
376        }
377      }
378    } else {
379      if (this.controlButtonVisible !== this.navigationBarVisibility) {
380        this.controlButtonVisible = this.navigationBarVisibility;
381      }
382    }
383  }
384
385  /**
386   * 获取当前系统是否为镜像模式(文本排布从右往左)
387   *
388   * @returns 返回true表示是镜像模式,反之返回false
389   */
390  private isRTL(): boolean {
391    return i18n.isRTL(i18n.System.getSystemLocale());
392  }
393
394  @Builder
395  drawerTitleBuilder(): void {
396    if (this.titleOptions?.titleBarType === TitleBarType.DRAWER && this.title) {
397      Row() {
398        Text(this.title)
399          .maxLines(TITLE_MAX_LINES)
400          .minFontSize(TITLE_MIN_FONT_SIZE)
401          .maxFontSize(TITLE_MAX_FONT_SIZE)
402          .height(DEFAULT_TITLE_HEIGHT)
403          .fontColor(TITLE_FONT_COLOR)
404          .textOverflow({ overflow: TextOverflow.Ellipsis })
405          .fontWeight(FontWeight.Bold)
406          .width(0)
407          .layoutWeight(TITLE_LAYOUT_WEIGHT)
408          .clip(true)
409          .textAlign(this.isRTL() ? TextAlign.End : TextAlign.Start)
410      }
411      .padding({
412        start: LengthMetrics.vp(this.currentBreakPoint !== BREAK_POINT_LG ? this.marginWindowLeft + 36 + 8 :
413          (this.sideBarAttribute.showSideBar ? 8 : this.marginWindowLeft + 36 + 8)),
414        top: LengthMetrics.vp(10),
415        bottom: LengthMetrics.vp(10),
416        // 在Stack模式,或者非分栏模式下右侧需要有一定padding值,避免超长文本时不能避让menuBar
417        end: ((this.currentBreakPoint === BREAK_POINT_SM &&
418          (this.mode === NavigationMode.Auto || !this.mode)) || this.mode === NavigationMode.Stack) ?
419        LengthMetrics.vp(this.titleBuilderPaddingEndWidth) : LengthMetrics.vp(0)
420      })
421      .width('100%')
422    }
423  }
424
425  @Builder
426  maskLayer() {
427    Column() {
428    }
429    .expandSafeArea([SafeAreaType.SYSTEM], [SafeAreaEdge.TOP, SafeAreaEdge.BOTTOM])
430    .backgroundColor($r('sys.color.ohos_id_color_mask_thin'))
431    .transition(TransitionEffect.opacity(0).animation({ duration: 500, curve: Curve.Linear }))
432    .width('100%')
433    .height('100%')
434    .accessibilityLevel('no')
435    .onClick(() => {
436      this.changeSideBarWithAnimation(false);
437    })
438  }
439
440  @Styles
441  controlButtonStyle() {
442    .backgroundColor($r('sys.color.ohos_id_color_button_normal'))
443    .width(36)
444    .height(36)
445    .borderRadius(18)
446    .margin({
447      start: LengthMetrics.vp(this.marginWindowLeft),
448    })
449    .zIndex(2)
450    .draggable(false)
451    .onClick(() => {
452      const isShowSideBar = !this.sideBarHelper?.isShowSideBar();
453      this.changeSideBarWithAnimation(isShowSideBar);
454    })
455    .position({
456      start: LengthMetrics.vp(0),
457      top: LengthMetrics.vp(8)
458    })
459    .visibility(this.controlButtonVisible ? Visibility.Visible : Visibility.None)
460  }
461
462  @Builder
463  controlButton() {
464    if (this.sideBarAttribute.showSideBar) {
465      if (!this.sideBarOptions?.sideBarIcon) {
466        Button({ type: ButtonType.Circle }) {
467          SymbolGlyph($r('sys.symbol.open_sidebar'))
468            .fontWeight(FontWeight.Normal)
469            .fontSize($r('sys.float.ohos_id_text_size_headline7'))
470            .fontColor([$r('sys.color.ohos_id_color_titlebar_icon')])
471        }
472        .controlButtonStyle()
473      } else if (this.sideBarOptions?.sideBarIcon instanceof SymbolGlyphModifier) {
474        Button({ type: ButtonType.Circle }) {
475          SymbolGlyph()
476            .attributeModifier(this.sideBarOptions?.sideBarIcon)
477            .fontSize($r('sys.float.ohos_id_text_size_headline7'))
478            .size({
479              width: $r('sys.float.ohos_id_text_size_headline7'),
480              height: $r('sys.float.ohos_id_text_size_headline7')
481            })
482        }
483        .controlButtonStyle()
484      } else {
485        Image(this.sideBarOptions?.sideBarIcon)
486          .controlButtonStyle()
487      }
488    } else {
489      if (!this.titleOptions?.titleIcon) {
490        Button({ type: ButtonType.Circle }) {
491          SymbolGlyph($r('sys.symbol.close_sidebar'))
492            .fontWeight(FontWeight.Normal)
493            .fontSize($r('sys.float.ohos_id_text_size_headline7'))
494            .fontColor([$r('sys.color.ohos_id_color_titlebar_icon')])
495        }
496        .controlButtonStyle()
497      } else if (this.titleOptions?.titleIcon instanceof SymbolGlyphModifier) {
498        Button({ type: ButtonType.Circle }) {
499          SymbolGlyph()
500            .attributeModifier(this.titleOptions?.titleIcon)
501            .fontSize($r('sys.float.ohos_id_text_size_headline7'))
502            .size({
503              width: $r('sys.float.ohos_id_text_size_headline7'),
504              height: $r('sys.float.ohos_id_text_size_headline7')
505            })
506        }
507        .controlButtonStyle()
508      } else {
509        Image(this.titleOptions?.titleIcon)
510          .controlButtonStyle()
511      }
512    }
513  }
514
515  @Builder
516  sideBar() {
517    Row() {
518      if (this.sideBarContent) {
519        this.sideBarContent();
520      }
521    }
522    .border({ width: {
523      left: 0,
524      right: '1px',
525      top: 0,
526      bottom: 0
527    },
528      color: $r('sys.color.comp_divider'),
529      style: {
530        right: BorderStyle.Solid
531      }
532    })
533    .alignItems(VerticalAlign.Top)
534    .width('100%')
535    .height('100%')
536    .padding({ top: 56 })
537    .focusable(true)
538    .defaultFocus(true)
539    .backgroundColor(this.sideBarOptions?.sideBarBackground ?? $r('sys.color.ohos_id_color_sub_background'))
540    .expandSafeArea([SafeAreaType.SYSTEM], [SafeAreaEdge.TOP, SafeAreaEdge.BOTTOM])
541  }
542
543  @Styles
544  roundIconStyle() {
545    .backgroundColor('rgba(0, 0, 0, 0)')
546    .width(36)
547    .height(36)
548    .borderRadius(18)
549    .margin({
550      start: LengthMetrics.vp(this.marginWindowLeft),
551      end: LengthMetrics.vp(8),
552      top: LengthMetrics.vp(10),
553      bottom: LengthMetrics.vp(10)
554    })
555    .zIndex(2)
556    .draggable(false)
557    .position({
558      start: LengthMetrics.vp(0),
559      top: LengthMetrics.vp(0)
560    })
561  }
562
563  @Builder
564  roundIconBuilder() {
565    if (this.titleOptions?.titleIcon instanceof SymbolGlyphModifier) {
566      Button({ type: ButtonType.Circle }) {
567        SymbolGlyph()
568          .attributeModifier(this.titleOptions?.titleIcon)
569          .fontSize($r('sys.float.ohos_id_text_size_headline7'))
570          .size({
571            width: $r('sys.float.ohos_id_text_size_headline7'),
572            height: $r('sys.float.ohos_id_text_size_headline7')
573          })
574      }
575      .stateEffect(false)
576      .roundIconStyle()
577    } else {
578      Image(this.titleOptions?.titleIcon ?? this.atomicServiceIcon)
579        .roundIconStyle()
580    }
581  }
582
583  @Styles
584  longIconStyles() {
585    .height(36)
586    .width(36)
587    .margin({
588      start: LengthMetrics.vp(this.marginWindowLeft),
589      end: LengthMetrics.vp(-12),
590      top: LengthMetrics.vp(10),
591      bottom: LengthMetrics.vp(10)
592    })
593    .draggable(false)
594    .position({
595      start: LengthMetrics.vp(0),
596      top: LengthMetrics.vp(0)
597    })
598    .backgroundColor('rgba(0, 0, 0, 0)')
599  }
600
601  @Builder
602  longIconBuilder() {
603    if (this.titleOptions?.titleIcon instanceof SymbolGlyphModifier) {
604      Button() {
605        SymbolGlyph()
606          .attributeModifier(this.titleOptions?.titleIcon)
607          .fontSize($r('sys.float.ohos_id_text_size_headline7'))
608          .size({
609            width: $r('sys.float.ohos_id_text_size_headline7'),
610            height: $r('sys.float.ohos_id_text_size_headline7')
611          })
612      }
613      .longIconStyles()
614      .stateEffect(false)
615      .type(ButtonType.Normal)
616    } else {
617      Image(this.titleOptions?.titleIcon ?? this.atomicServiceIcon)
618        .longIconStyles()
619        .objectFit(ImageFit.Auto)
620    }
621  }
622
623  @Builder
624  titleBuilder() {
625    if (this.titleOptions?.titleBarType === TitleBarType.SQUARED_ICON) {
626      this.longIconBuilder();
627    } else {
628      this.roundIconBuilder();
629    }
630  }
631
632  @Builder
633  private defaultTitleBuilder(): void {
634    Text(this.title)
635      .maxLines(TITLE_MAX_LINES)
636      .minFontSize(TITLE_MIN_FONT_SIZE)
637      .maxFontSize(TITLE_MAX_FONT_SIZE)
638      .height(DEFAULT_TITLE_HEIGHT)
639      .fontColor(TITLE_FONT_COLOR)
640      .textOverflow({ overflow: TextOverflow.Ellipsis })
641      .fontWeight(FontWeight.Bold)
642      .layoutWeight(TITLE_LAYOUT_WEIGHT)
643      .clip(true)
644      .margin({ top: LengthMetrics.px(DEFAULT_MARGIN_TOP_DISTANCE) })
645      .padding({ start: LengthMetrics.px(DEFAULT_PADDING_START_DISTANCE) })
646      .textAlign(this.isRTL() ? TextAlign.End : TextAlign.Start)
647  }
648
649  /**
650   * 根据当前屏幕尺寸判断是否要显示用户插入的布局
651   */
652  private isShowMenus(): boolean {
653    return this.mode === NavigationMode.Stack && this.currentBreakPoint !== BREAK_POINT_SM;
654  }
655
656  /**
657   * 根据用户传入的类型和当前屏幕尺寸判断是否要显示NavigationMenuItem列表
658   */
659  private getMenuItemArray(): undefined | Array<NavigationMenuItem> {
660    return this.isShowMenus() && this.menus instanceof Array ? this.menus : undefined;
661  }
662
663  build() {
664    Stack() {
665      Column() {
666        if (this.gradientBackground !== undefined) {
667          this.BackgroundBuilder({
668            primaryColor: this.gradientBackground.primaryColor,
669            secondaryColor: this.gradientBackground.secondaryColor,
670            backgroundTheme: this.gradientBackground.backgroundTheme,
671            mixMode: this.gradientBackground.mixMode,
672            alpha: this.gradientBackground.alpha
673          })
674        }
675      }
676      if (this.titleOptions?.titleBarType === TitleBarType.DRAWER) {
677        this.controlButton();
678
679        // 断点为LG时,侧边栏嵌入到组件内;断点为MD或SM时,侧边栏悬浮在B栏上
680        SideBarContainer(this.currentBreakPoint === BREAK_POINT_LG ? SideBarContainerType.Embed :
681        SideBarContainerType.Overlay) {
682          this.sideBar()
683
684          Stack() {
685            Navigation(this.navPathStack) {
686              if (this.navigationContent) {
687                this.navigationContent();
688              }
689            }
690            .expandSafeArea([SafeAreaType.SYSTEM], [SafeAreaEdge.TOP, SafeAreaEdge.BOTTOM])
691            .title(this.drawerTitleBuilder(), this.getTitleOption())
692            .menus(this.isShowMenus() && this.menus instanceof Function ? this.menus() : this.getMenuItemArray())
693            .titleMode(NavigationTitleMode.Mini)
694            .hideBackButton(true)
695            .hideTitleBar(this.hideTitleBar)
696            .navBarWidth(this.navBarWidth)
697            .navBarPosition(NavBarPosition.Start)
698            .mode(this.mode)
699            .navDestination(this.navDestinationBuilder)
700            .navBarWidthRange(this.navBarWidthRange)
701            .minContentWidth(this.minContentWidth)
702            .onNavigationModeChange(this.modeChangeCallback)
703            .onNavBarStateChange((visible: boolean) => {
704              this.navigationBarVisibility = visible;
705              this.updateControlButtonVisibility();
706              if (this.stateChangeCallback) {
707                this.stateChangeCallback(visible);
708              }
709            })
710
711            if (this.titleOptions?.titleBarType === TitleBarType.DRAWER && this.sideBarAttribute.showSideBar &&
712              this.currentBreakPoint !== BREAK_POINT_LG && this.showMaskLayer) {
713              this.maskLayer();
714            }
715          }
716          .expandSafeArea([SafeAreaType.SYSTEM], [SafeAreaEdge.TOP, SafeAreaEdge.BOTTOM])
717        }
718        .expandSafeArea([SafeAreaType.SYSTEM], [SafeAreaEdge.TOP, SafeAreaEdge.BOTTOM])
719        .sideBarWidth(this.sideBarAttribute.sideBarWidth)
720        .minContentWidth(this.sideBarAttribute.minContentWidthOfSideBar)
721        .minSideBarWidth(this.sideBarAttribute.minSideBarWidth)
722        .maxSideBarWidth(this.sideBarAttribute.maxSideBarWidth)
723        .showControlButton(false)
724        .showSideBar(this.sideBarAttribute.showSideBar)
725        .onChange((showSideBar: boolean) => {
726          if (this.sideBarHelper?.isShowSideBar() !== showSideBar) {
727            this.changeSideBarWithAnimation(showSideBar);
728          }
729        })
730      } else {
731        Navigation(this.navPathStack) {
732          if (this.navigationContent) {
733            this.navigationContent();
734          }
735        }
736        .title((this.titleOptions?.titleIcon || (this.titleOptions?.titleBarType && !this.title)) ?
737          this.titleBuilder() : this.defaultTitleBuilder(), this.getTitleOption())
738        .titleMode(NavigationTitleMode.Mini)
739        .menus(this.isShowMenus() && this.menus instanceof Function ? this.menus() : this.getMenuItemArray())
740        .hideBackButton(true)
741        .hideTitleBar(this.hideTitleBar)
742        .navBarWidth(this.navBarWidth)
743        .navBarPosition(NavBarPosition.Start)
744        .mode(this.mode)
745        .navDestination(this.navDestinationBuilder)
746        .navBarWidthRange(this.navBarWidthRange)
747        .minContentWidth(this.minContentWidth)
748        .onNavBarStateChange(this.stateChangeCallback)
749        .onNavigationModeChange(this.modeChangeCallback)
750      }
751    }
752    .align(Alignment.TopStart)
753    .width('100%')
754    .height('100%')
755    .onSizeChange((oldValue: SizeOptions, newValue: SizeOptions) => {
756      this.navigationWidth = newValue.width as number;
757      this.navigationHeight = newValue.height as number;
758    })
759    .expandSafeArea([SafeAreaType.SYSTEM], [SafeAreaEdge.TOP, SafeAreaEdge.BOTTOM])
760  }
761
762  /**
763   * 双色渐变下两种颜色各占50%的实现,把整个画布区域分为两个一样的矩形在绘制
764   * @param context 画布上下文
765   * @param primaryColor 第一种颜色
766   * @param secondaryColor 第二种颜色
767   */
768  drawGradientCanvasHalf(context: CanvasRenderingContext2D, primaryColor: ResourceColor,
769    secondaryColor: ResourceColor): void {
770    let height = this.navigationHeight * COLOR_RATIO_THIRTY_PERCENT;
771    let grad1 =
772      context.createLinearGradient(COORDINATE_NEGATIVE_ONE * this.navigationWidth * COLOR_RATIO_FIFTY_PERCENT,
773        height, this.navigationWidth * COLOR_RATIO_FIFTY_PERCENT, 0);
774    let grad2 = context.createLinearGradient(this.navigationWidth * COLOR_RATIO_ONE_FIFTY_PERCENT, height,
775      this.navigationWidth * COLOR_RATIO_FIFTY_PERCENT, 0);
776    grad1.addColorStop(0, this.resourceColorToString(primaryColor));
777    grad1.addColorStop(COLOR_RATIO_FIFTY_PERCENT, this.resourceColorToString(primaryColor));
778    grad1.addColorStop(1, this.resourceColorToString(secondaryColor));
779    grad2.addColorStop(0, this.resourceColorToString(primaryColor));
780    grad2.addColorStop(COLOR_RATIO_FIFTY_PERCENT, this.resourceColorToString(primaryColor));
781    grad2.addColorStop(1, this.resourceColorToString(secondaryColor));
782    context.fillStyle = grad1;
783    context.fillRect(0, 0, this.navigationWidth * COLOR_RATIO_FIFTY_PERCENT, height);
784    context.fillStyle = grad2;
785    context.fillRect(this.navigationWidth * COLOR_RATIO_FIFTY_PERCENT, 0, this.navigationWidth, height);
786  }
787
788  /**
789   * 双色渐变的一种实现,把画布先分为两个大矩形,再把其中一个矩形分为两个小矩形
790   * @param context 画布上下文
791   * @param primaryColor 第一种颜色
792   * @param secondaryColor 第二种颜色
793   */
794  drawGradientCanvasCross(context: CanvasRenderingContext2D, primaryColor: ResourceColor,
795    secondaryColor: ResourceColor): void {
796    let height = this.navigationHeight * COLOR_RATIO_THIRTY_PERCENT;
797    let grad1 = context.createLinearGradient(0, 0, COLOR_RATIO_SEVENTY_PERCENT * this.navigationWidth, 0);
798    grad1.addColorStop(0, this.resourceColorToString(primaryColor));
799    grad1.addColorStop(COLOR_RATIO_FIFTY_PERCENT, this.resourceColorToString(primaryColor));
800    grad1.addColorStop(1, this.resourceColorToString(secondaryColor));
801    context.fillStyle = grad1;
802    context.fillRect(0, 0, COLOR_RATIO_SEVENTY_PERCENT * this.navigationWidth, height);
803    let y1 = (COLOR_RATIO_FIFTY_PERCENT * height - COLOR_RATIO_THIRTY_PERCENT * this.navigationWidth) > 0 ?
804      COLOR_RATIO_FIFTY_PERCENT * height - COLOR_RATIO_THIRTY_PERCENT * this.navigationWidth : 0;
805    let grad2 =
806      context.createLinearGradient(COLOR_RATIO_SEVENTY_PERCENT * this.navigationWidth, y1, this.navigationWidth,
807        height * COLOR_RATIO_FIFTY_PERCENT);
808    grad2.addColorStop(0, this.resourceColorToString(secondaryColor));
809    grad2.addColorStop(COLOR_RATIO_FORTY_PERCENT, this.resourceColorToString(secondaryColor));
810    grad2.addColorStop(1, this.resourceColorToString(primaryColor));
811    context.fillStyle = grad2;
812    context.fillRect(COLOR_RATIO_SEVENTY_PERCENT * this.navigationWidth - RECTANGLE_OUTSIDE_OFFSET_ONE, 0,
813      this.navigationWidth * COLOR_RATIO_THIRTY_PERCENT + RECTANGLE_OUTSIDE_OFFSET_ONE,
814      height * COLOR_RATIO_FIFTY_PERCENT + RECTANGLE_OUTSIDE_OFFSET_ONE);
815    let y2 = (COLOR_RATIO_FIFTY_PERCENT * height - COLOR_RATIO_THIRTY_PERCENT * this.navigationWidth) > 0 ?
816      COLOR_RATIO_FIFTY_PERCENT * height + COLOR_RATIO_THIRTY_PERCENT * this.navigationWidth : height;
817    let grad3 =
818      context.createLinearGradient(COLOR_RATIO_SEVENTY_PERCENT * this.navigationWidth, y2, this.navigationWidth,
819        height * COLOR_RATIO_FIFTY_PERCENT);
820    grad3.addColorStop(0, this.resourceColorToString(secondaryColor));
821    grad3.addColorStop(COLOR_RATIO_FORTY_PERCENT, this.resourceColorToString(secondaryColor));
822    grad3.addColorStop(1, this.resourceColorToString(primaryColor));
823    context.fillStyle = grad3;
824    context.fillRect(COLOR_RATIO_SEVENTY_PERCENT * this.navigationWidth - RECTANGLE_OUTSIDE_OFFSET_ONE,
825      height * COLOR_RATIO_FIFTY_PERCENT,
826      COLOR_RATIO_THIRTY_PERCENT * this.navigationWidth + RECTANGLE_OUTSIDE_OFFSET_ONE,
827      height * COLOR_RATIO_FIFTY_PERCENT);
828  }
829
830  /**
831   * 双色渐变的一种实现,从矩形左上角颜色渐变到右下角
832   * @param context 画布上下文
833   * @param primaryColor 第一种颜色
834   * @param secondaryColor 第二种颜色
835   */
836  drawGradientCanvasTowards(context: CanvasRenderingContext2D, primaryColor: ResourceColor,
837    secondaryColor: ResourceColor): void {
838    let height = this.navigationHeight * COLOR_RATIO_THIRTY_PERCENT;
839    let grad = context.createLinearGradient(0, 0, this.navigationWidth, height);
840    grad.addColorStop(0, this.resourceColorToString(primaryColor));
841    grad.addColorStop(COLOR_RATIO_FORTY_PERCENT, this.resourceColorToString(primaryColor));
842    grad.addColorStop(1, this.resourceColorToString(secondaryColor));
843    context.fillStyle = grad;
844    context.fillRect(0, 0, this.navigationWidth, height);
845  }
846
847  /**
848   * 双色渐变下透明效果的实现
849   * @param context 画布上下文
850   * @param backgroundTheme 背景色底色
851   */
852  drawTransparentGradient(context: CanvasRenderingContext2D, backgroundTheme: BackgroundTheme): void {
853    let height = this.navigationHeight * COLOR_RATIO_THIRTY_PERCENT;
854    let grad = context.createLinearGradient(0, 0, 0, height);
855    grad.addColorStop(0, backGroundTransparentGradientColor[backgroundTheme - 1][0]);
856    grad.addColorStop(1, backGroundTransparentGradientColor[backgroundTheme - 1][1]);
857    context.fillStyle = grad;
858    context.fillRect(0, 0, this.navigationWidth + RECTANGLE_OUTSIDE_OFFSET_ONE, height +
859      RECTANGLE_OUTSIDE_OFFSET_ONE);
860    if (backgroundTheme === BackgroundTheme.DARK) {
861      context.fillStyle = Color.Black;
862      context.fillRect(0, height, this.navigationWidth, this.navigationHeight - height);
863    }
864  }
865
866  /**
867   * 单色渐变:
868   * @param context 画布上下文
869   * @param primaryColor createLinearGradient初始颜色为primaryColor,结束颜色为底色
870   * @param backgroundColor 颜色线性渐变的结束颜色
871   */
872  drawSingleGradient(context: CanvasRenderingContext2D,
873    primaryColor: ResourceColor, backgroundColor: string): void {
874    let height = this.navigationHeight * COLOR_RATIO_SIXTY_PERCENT;
875    let grad1 = context.createLinearGradient(0, 0, 0, height);
876    grad1.addColorStop(0, this.resourceColorToString(primaryColor));
877    grad1.addColorStop(1, backgroundColor);
878    context.fillStyle = grad1;
879    context.fillRect(0, 0, this.navigationWidth, height);
880    //当背景为黑色的时候需要特殊处理下
881    if (backgroundColor === backGroundColor[0]) {
882      context.fillStyle = Color.Black
883      context.fillRect(0, height, this.navigationWidth, this.navigationHeight * (1 - COLOR_RATIO_SIXTY_PERCENT));
884    }
885  }
886
887  /**
888   * ResourceColor转化为能作为addColorStop使用的字符串
889   * @param resource ResourceColor = Color | number | string | Resource,对于Resource转化为直接使用的字符串需要特殊处理
890   * @returns
891   */
892  resourceColorToString(resource: ResourceColor): string {
893    if (typeof resource === 'object') {
894      try {
895        return getContext(this).resourceManager.getStringSync(resource)
896      } catch (error) {
897        let code = (error as BusinessError).code;
898        let message = (error as BusinessError).message;
899        hilog.error(0x0000, 'AtomicServiceNavigation',
900          `resourceColorToString - systemResourceManager getStringValue failed, error code: ${code}, message: ${message}.`);
901      }
902      return '';
903    } else {
904      return resource.toString();
905    }
906  }
907
908  /**
909   * 获取NavigationTitleOptions
910   */
911  private getTitleOption(): NavigationTitleOptions {
912    return {
913      backgroundColor: this.titleOptions?.backgroundColor,
914      backgroundBlurStyle: this.titleOptions?.isBlurEnabled ? BlurStyle.COMPONENT_THICK : BlurStyle.NONE,
915      barStyle: this.titleOptions?.barStyle
916    };
917  }
918
919  /**
920   * 更新control button的可见性,并运行动画效果
921   */
922  private changeSideBarWithAnimation(isShowSidebar: boolean): void {
923    animateTo({
924      duration: 500,
925      curve: curves.cubicBezierCurve(0.2, 0.2, 0.1, 1),
926      onFinish: () => {
927        this.showMaskLayer = isShowSidebar;
928      }
929    }, () => {
930      if (this.sideBarHelper) {
931        this.sideBarHelper.setShowSideBar(isShowSidebar);
932      }
933      this.showMaskLayer = true;
934    })
935  }
936}
937
938/**
939 * 侧边栏相关参数
940 */
941@Observed
942class sideBarAttributeSet {
943  /**
944   * 侧边栏宽度
945   */
946  public sideBarWidth: number = SIDE_BAR_OVERLAY_WIDTH;
947  /**
948   * 侧边栏最小宽度
949   */
950  public minSideBarWidth = SIDE_BAR_OVERLAY_WIDTH;
951  /**
952   * 侧边栏最大宽度
953   */
954  public maxSideBarWidth = SIDE_BAR_OVERLAY_WIDTH;
955  /**
956   * 侧边栏内容最小宽度
957   */
958  public minContentWidthOfSideBar: Dimension = SIDE_BAR_COMMON_WIDTH;
959  /**
960   * 侧边栏显示隐藏状态
961   */
962  public showSideBar: boolean = false;
963}
964
965/**
966 * 侧边栏对外暴露相关参数
967 */
968export interface SideBarOptions {
969  /**
970   * 侧边栏背景
971   */
972  sideBarBackground?: ResourceColor;
973
974  /**
975   * 侧边栏显示隐藏状态变化监听
976   */
977  onChange?: (show: boolean) => void;
978
979  /**
980   * 侧边栏icon
981   */
982  sideBarIcon?: Resource | SymbolGlyphModifier;
983}
984
985/**
986 * 标题相关参数
987 */
988export interface TitleOptions {
989  /**
990   * 背景色
991   */
992  backgroundColor?: ResourceColor,
993
994  /**
995   * 是否启动模糊效果
996   */
997  isBlurEnabled?: boolean,
998
999  /**
1000   * 标题栏风格
1001   */
1002  barStyle?: BarStyle,
1003
1004  /**
1005   * 标题栏类型
1006   */
1007  titleBarType?: TitleBarType,
1008
1009  /**
1010   * 标题栏icon
1011   */
1012  titleIcon?: Resource | SymbolGlyphModifier;
1013}
1014
1015export type NavDestinationBuilder = (name: string, param?: Object) => void;
1016
1017export interface GradientBackground {
1018  primaryColor: ResourceColor,
1019  secondaryColor?: ResourceColor,
1020  backgroundTheme?: BackgroundTheme,
1021  mixMode?: MixMode,
1022  alpha?: GradientAlpha
1023}
1024
1025/**
1026 * 侧边栏辅助管理类
1027 */
1028class SideBarHelper {
1029  private isExpandSideBar: boolean = false;
1030  private listener?: Function;
1031
1032  /**
1033   * 注册侧边栏显隐状态变化监听
1034   *
1035   * @param listener 监听器对象
1036   */
1037  public registerListener(listener: Function): void {
1038    this.listener = listener;
1039  }
1040
1041  /**
1042   * 取消注册监听
1043   */
1044  public unregisterListener(): void {
1045    this.listener = undefined;
1046  }
1047
1048  /**
1049   * 获取侧边栏显示隐藏状态
1050   *
1051   * @returns 侧边栏是否显示
1052   */
1053  public isShowSideBar(): boolean {
1054    return this.isExpandSideBar;
1055  }
1056
1057  /**
1058   * 设置侧边栏显示隐藏状态
1059   *
1060   * @param value 显示或隐藏状态
1061   */
1062  public setShowSideBar(value: boolean): void {
1063    this.isExpandSideBar = value;
1064    if (this.listener) {
1065      this.listener(value);
1066    }
1067  }
1068
1069  /**
1070   * 更新侧边栏布局
1071   *
1072   * @param breakPoint 当前断点
1073   * @param layout 布局参数
1074   */
1075  public updateLayout(breakPoint: string, layout: sideBarAttributeSet): void {
1076    if (breakPoint === BREAK_POINT_LG) {
1077      this.updateLGLayout(layout);
1078    } else {
1079      this.updateCommonLayout(layout);
1080    }
1081  }
1082
1083  /**
1084   * 更新除LG外窗口模式的布局
1085   */
1086  private updateCommonLayout(layout: sideBarAttributeSet): void {
1087    layout.sideBarWidth = SIDE_BAR_OVERLAY_WIDTH;
1088    layout.minContentWidthOfSideBar = '100%';
1089  }
1090
1091  /**
1092   * 更新LG窗口模式布局
1093   */
1094  private updateLGLayout(layout: sideBarAttributeSet): void {
1095    layout.sideBarWidth = SIDE_BAR_EMBED_MIN_WIDTH;
1096    layout.minContentWidthOfSideBar = CONTENT_MIN_WIDTH;
1097  }
1098}
1099
1100/**
1101 * 标题栏类型
1102 */
1103export enum TitleBarType {
1104  /**
1105   * 长图标类型标题栏
1106   */
1107  SQUARED_ICON = 1,
1108
1109  /**
1110   * 圆形图标类型标题栏
1111   */
1112  ROUND_ICON,
1113
1114  /**
1115   * 抽屉类型标题栏
1116   */
1117  DRAWER
1118}