自学内容网 自学内容网

【HarmonyOS——最新一多开发布局实践】

大家好,我是小z,今天给大家介绍一下一多开发常见的几种布局能力。

前言

一多开发是指通过统一的代码实现多种设备的适配。基于三种关键布局能力介绍,其中重点介绍的是响应式布局能力的前两种方式:断点媒体查询,除此之外还有一个栅格布局
示例图片

多设备断点开发

  • 通过横向断点和纵向断点,让开发者可以结合窗口宽度与高度两个维度去适配HarmonyOS的1+8设备,解决多设备UX布局问题

为什么需要横向和纵向断点:

  • 因为当前在实际开发过程中,仅仅使用横向断点无法区分多设备场景,例如手机横屏和折叠屏展开态窗口宽度都在600-840vp,即横向断点都是’md’范围。为实现一多布局能力,需要引入纵向断点区分多设备类型。

横纵向断点的设计原理

横向断点以应用窗口宽度为基准,按照320vp、600vp、840vp、1440vp四个阈值将断点分为了5个值:

窗口宽度横向断点
<320vpxs
320-600vpsm
600-840vpmd
840-1440vplg
>1440vpxl

分析当前所有设备高宽比,可以将设备按照0.8以及1.2两个阈值分成3个区间:

高宽比纵向断点
<0.8sm
0.8-1.2md
>1.2lg

有两种方法来获取横纵向断点

1. 系统接口获取

  • API13,getWindowWidthBreakpoint()和getWindowHeightBreakpoint(),可以直接获取横向和纵向断点值
// 根据窗口宽度更新横向断点
onWindowStageCreate(windowStage: window.WindowStage): void {
  // Main window is created, set main page for this ability
  hilog.info(0x0000, 'testTag', '%{public}s', 'Ability onWindowStageCreate');

  windowStage.loadContent('pages/Index', (err) => {
    if (err.code) {
      hilog.error(0x0000, 'testTag', 'Failed to load the content. Cause: %{public}s', JSON.stringify(err) ?? '');
      return;
    }
    hilog.info(0x0000, 'testTag', 'Succeeded in loading the content.');
    windowStage.getMainWindow().then((data: window.Window) => {
      this.uiContext = data.getUIContext();
      let widthBp: WidthBreakpoint = this.uiContext.getWindowWidthBreakpoint();
      let heightBp: HeightBreakpoint = this.uiContext.getWindowHeightBreakpoint();
      AppStorage.setOrCreate('widthBreakpoint', widthBp);
      AppStorage.setOrCreate('heightBreakpoint', heightBp);
      data.on('windowSizeChange', (windowSize: window.Size) => {
        let widthBp: WidthBreakpoint = this.uiContext!.getWindowWidthBreakpoint();
        AppStorage.setOrCreate('widthBreakpoint', widthBp);
        let heightBp: HeightBreakpoint = this.uiContext!.getWindowHeightBreakpoint();
        AppStorage.setOrCreate('heightBreakpoint', heightBp);
      })
    }).catch((err: BusinessError) => {
      console.error(`Failed to obtain the main window. Cause code: ${err.code}, message: ${err.message}`);
    });
  });
}

2. 自定义实现横纵向断点

updateWidthBp()方法按照应用窗口宽度的四个阈值分为了五个断点:xs、sm、md、lg和xl,并将断点存在全局中。

通过窗口对象获取启动时的应用窗口宽度并注册回调函数监听窗口尺寸变化。将窗口尺寸的长度单位由px换算为vp后,即可基于前文中介绍的规则得到当前断点值,此时可以使用状态变量记录当前的断点值方便后续使用

// products/phone/src/main/ets/entryability/EntryAbility.ets
private updateWidthBp(): void {
  if (this.windowObj === undefined) {
    return;
  }
  let mainWindow: window.WindowProperties = this.windowObj.getWindowProperties();
  let windowWidth: number = mainWindow.windowRect.width;
  let windowWidthVp = windowWidth / display.getDefaultDisplaySync().densityPixels;
  let widthBp: string = '';
  let videoGridColumn: string = CommonConstants.VIDEO_GRID_COLUMNS[0];
  if (windowWidthVp < 320) {
    widthBp = 'xs';
    videoGridColumn = CommonConstants.VIDEO_GRID_COLUMNS[0];
  } else if (windowWidthVp >= 320 && windowWidthVp < 600) {
    widthBp = 'sm';
    videoGridColumn = CommonConstants.VIDEO_GRID_COLUMNS[0];
  } else if (windowWidthVp >= 600 && windowWidthVp < 840) {
    widthBp = 'md';
    videoGridColumn = CommonConstants.VIDEO_GRID_COLUMNS[1];
  } else if (windowWidthVp >= 840 && windowWidthVp < 1440) {
    widthBp = 'lg';
    videoGridColumn = CommonConstants.VIDEO_GRID_COLUMNS[2];
  } else {
    widthBp = 'xl';
    videoGridColumn = CommonConstants.VIDEO_GRID_COLUMNS[2];
  }
  AppStorage.setOrCreate('currentWidthBreakpoint', widthBp);
  AppStorage.setOrCreate('videoGridColumn', videoGridColumn);
}

updateHeightBp()方法按照高宽比的两个阈值分成了三个断点:sm、md和lg,并将断点存在全局中。

// products/phone/src/main/ets/entryability/EntryAbility.ets
private updateHeightBp(): void {
  if (this.windowObj === undefined) {
    return;
  }
  let mainWindow: window.WindowProperties = this.windowObj.getWindowProperties();
  let windowHeight: number = mainWindow.windowRect.height;
  let windowWidth: number = mainWindow.windowRect.width;
  let windowWidthVp = windowWidth / display.getDefaultDisplaySync().densityPixels;
  let windowHeightVp = windowHeight / display.getDefaultDisplaySync().densityPixels;
  let heightBp: string = '';
  let aspectRatio: number = windowHeightVp / windowWidthVp;
  if (aspectRatio < 0.8) {
    heightBp = 'sm';
  } else if (aspectRatio >= 0.8 && aspectRatio < 1.2) {
    heightBp = 'md';
  } else {
    heightBp = 'lg';
  }
  AppStorage.setOrCreate('currentHeightBreakpoint', heightBp);
}
开发步骤
  • 在EntryAbility的onWindowStageCreate()生命周期中增加对宽度和“高宽比”的监听。在获取到主窗口后调用updateWidthBp()和updateHeightBp()方法初始设置一次横纵断点,之后在窗口大小变化时设置,当windowSize改变的时候就会触发。
// products/phone/src/main/ets/entryability/EntryAbility.ets
windowStage.getMainWindow().then((data: window.Window) => {
  this.windowObj = data;
  this.updateWidthBp();
  this.updateHeightBp();
  this.windowObj.on('windowSizeChange', (windowSize: window.Size) => {
    this.updateWidthBp();
    this.updateHeightBp();
  })
})
  • 在全屏播放页中的aboutToAppear()基于不同的设备设置窗口方向。先来看下全屏播放页未使用断点时的窗口设置逻辑:有以下几种情况需要将窗口设置为AUTO_ROTATION_LANDSCAPE属性:手机、折叠屏折叠态与折叠屏半折态。

多设备适配指导

image-20241209003159116

媒体查询

在实际应用开发过程中,开发者常常需要针对不同类型设备或同一类型设备的不同状态来修改应用的样式。媒体查询提供了丰富的媒体特征监听能力,可以监听应用显示区域变化、横竖屏、深浅色、设备类型等等,因此在应用开发过程中使用的非常广泛。

步骤

1. 封装

对通过媒体查询监听断点的功能做简单的封装,方便后续使用

import mediaQuery from '@ohos.mediaquery';
import { BreakpointConstants } from '../constants/BreakpointConstants';

declare interface BreakPointTypeOption<T> {
  sm?: T
  md?: T
  lg?: T
  xl?: T
  xxl?: T
}

export class BreakPointType<T> {
  options: BreakPointTypeOption<T>

  constructor(option: BreakPointTypeOption<T>) {
    this.options = option
  }

  getValue(currentBreakPoint: string): T {
    // return this.options[currentBreakPoint] as T
    return '' as T
  }
}

export class BreakpointSystem {
  private currentBreakpoint: string = '';
  private smListener?: mediaQuery.MediaQueryListener;
  private mdListener?: mediaQuery.MediaQueryListener;
  private lgListener?: mediaQuery.MediaQueryListener;

  private updateCurrentBreakpoint(breakpoint: string) {
    if (this.currentBreakpoint !== breakpoint) {
      this.currentBreakpoint = breakpoint;
      AppStorage.set<string>(BreakpointConstants.CURRENT_BREAKPOINT, this.currentBreakpoint);
    }
  }

  private isBreakpointSM = (mediaQueryResult: mediaQuery.MediaQueryResult) => {
    if (mediaQueryResult.matches) {
      this.updateCurrentBreakpoint(BreakpointConstants.BREAKPOINT_SM);
    }
  }
  private isBreakpointMD = (mediaQueryResult: mediaQuery.MediaQueryResult) => {
    if (mediaQueryResult.matches) {
      this.updateCurrentBreakpoint(BreakpointConstants.BREAKPOINT_MD);
    }
  }
  private isBreakpointLG = (mediaQueryResult: mediaQuery.MediaQueryResult) => {
    if (mediaQueryResult.matches) {
      this.updateCurrentBreakpoint(BreakpointConstants.BREAKPOINT_LG);
    }
  }

  public register() {
    this.smListener = mediaQuery.matchMediaSync(BreakpointConstants.RANGE_SM);
    this.smListener.on('change', this.isBreakpointSM);
    this.mdListener = mediaQuery.matchMediaSync(BreakpointConstants.RANGE_MD);
    this.mdListener.on('change', this.isBreakpointMD);
    this.lgListener = mediaQuery.matchMediaSync(BreakpointConstants.RANGE_LG);
    this.lgListener.on('change', this.isBreakpointLG);
  }

  public unregister() {
    this.smListener?.off('change', this.isBreakpointSM);
    this.mdListener?.off('change', this.isBreakpointMD);
    this.lgListener?.off('change', this.isBreakpointLG);
  }
}

2. 页面中调用

在页面中,通过媒体查询,监听应用窗口宽度变化,获取当前应用所处的断点值

 @Entry
 @Component
 struct MainPage {
   @StorageProp('currentBreakpoint') currentBreakpoint: string = 'sm';
   //...

   aboutToDisappear() {
     this.breakpointSystem.unregister();
   }
 
   aboutToAppear() {
     this.breakpointSystem.register();
   }
     
   build(){
       //...
       Tabs({barPosition: this.currentBreakpoint === BreakpointConstants.BREAKPOINT_LG ? BarPosition.Start : BarPosition.End,
             index: this.currentPageIndex
           })
       //...
   }
 ```

原文地址:https://blog.csdn.net/m0_66825548/article/details/144335539

免责声明:本站文章内容转载自网络资源,如本站内容侵犯了原著者的合法权益,可联系本站删除。更多内容请关注自学内容网(zxcms.com)!