Zzhaojunxiaadd userguid
ea8d3dae创建于 2024年5月27日历史提交

#开发指导 本文主要介绍一下横竖屏、DFX、国际化的开发方法。

横竖屏实现方法

横竖屏屏幕尺寸不一致,容纳元素也不一样。为了应对这种屏幕尺寸不确定性,应用开发可以采用,一次开发,多端部署的能力实现,其方案可以基于布局能力实现,也可以基于组件的属性能力实现,开发者根据不同场景选用不同方案,下面介绍实现方法。

布局实现横竖屏的不同呈现方式

弹性布局 (Flex)

弹性布局(Flex)提供更加有效的方式对容器中的子元素进行排列、对齐和分配剩余空间。常用于页面头部导航栏的均匀分布、页面框架的搭建、多行数据的排列等。 在弹性布局中,容器的子元素可以按照任意方向排列。通过设置参数direction,可以决定主轴的方向,从而控制子元素的排列方向,默认值FlexDirection.Row,主轴为水平方向。

	  Flex({ direction: FlexDirection.Row, alignItems: ItemAlign.Center, justifyContent: FlexAlign.SpaceBetween }) {
	    Button('OK', { type: ButtonType.Normal, stateEffect: true })
	      .borderRadius(8)
	      .backgroundColor(0x317aff)
	      .width(90)
	      .onClick(() => {
	        console.log('ButtonType.Normal')
	      })
	    Button({ type: ButtonType.Normal, stateEffect: true }) {
	      Row() {
	        LoadingProgress().width(20).height(20).margin({ left: 12 }).color(0xFFFFFF)
	        Text('loading').fontSize(12).fontColor(0xffffff).margin({ left: 5, right: 12 })
	      }.alignItems(VerticalAlign.Center)
	    }.borderRadius(8).backgroundColor(0x317aff).width(90).height(40)
	
	    Button('Disable', { type: ButtonType.Normal, stateEffect: false }).opacity(0.4)
	      .borderRadius(8).backgroundColor(0x317aff).width(90)
	  }

可以通过Flex组件的alignItems参数设置子元素在交叉轴的对齐方式,默认ItemAlign.Start,交叉轴方向首部对齐。

栅格布局 (GridRow/GridCol)

GridRow为栅格容器组件,需与栅格子组件GridCol在栅格布局场景中联合使用。

栅格系统断点 栅格系统以设备的水平宽度(屏幕密度像素值,单位vp)作为断点依据,定义设备的宽度类型,形成了一套断点规则。开发者可根据需求在不同的断点区间实现不同的页面布局效果。

  • 栅格系统断点:栅格系统以设备的水平宽度(屏幕密度像素值,单位vp)作为断点依据,定义设备的宽度类型,形成了一套断点规则。开发者可根据需求在不同的断点区间实现不同的页面布局效果。

在GridRow栅格组件中,允许开发者使用breakpoints自定义修改断点的取值范围,最多支持6个断点,除了默认的四个断点外,还可以启用xl,xxl两个断点,支持六种不同尺寸(xs, sm, md, lg, xl, xxl)设备的布局设置。

栅格子组件GridCol,必须作为栅格容器组件(GridRow)的子组件使用。

使用栅格的默认列数12列,即在未设置columns时,任何断点下,栅格布局被分成12列。通过断点设置将应用宽度分成六个区间,在各区间中,每个栅格子元素占用的列数均不同。

	@State bgColors: Color[] = [Color.Red, Color.Orange, Color.Yellow, Color.Green, Color.Pink, Color.Grey, Color.Blue, Color.Brown];
	...
	GridRow({
	  breakpoints: {
	    value: ['200vp', '300vp', '400vp', '500vp', '600vp'],
	    reference: BreakpointsReference.WindowSize
	  }
	}) {
	   ForEach(this.bgColors, (color:Color, index?:number|undefined) => {
	     GridCol({
	       span: {
	         xs: 2, // 在最小宽度类型设备上,栅格子组件占据的栅格容器2列。
	         sm: 3, // 在小宽度类型设备上,栅格子组件占据的栅格容器3列。
	         md: 4, // 在中等宽度类型设备上,栅格子组件占据的栅格容器4列。
	         lg: 6, // 在大宽度类型设备上,栅格子组件占据的栅格容器6列。
	         xl: 8, // 在特大宽度类型设备上,栅格子组件占据的栅格容器8列。
	         xxl: 12 // 在超大宽度类型设备上,栅格子组件占据的栅格容器12列。
	       }
	     }) {
	       Row() {
	         Text(`${index}`)
	       }.width("100%").height('50vp')
	     }.backgroundColor(color)
	   })
	}                                                                    

媒体查询 (@ohos.mediaquery)

媒体查询可根据不同设备类型或同设备不同状态修改应用的样式。当屏幕发生动态改变时(比如分屏、横竖屏切换),同步更新应用的页面布局时,可以使用媒体查询。 Stage模型下的示例:使用媒体查询,实现屏幕横竖屏切换时,给页面文本应用添加不同的内容和样式。

import mediaquery from '@ohos.mediaquery';
import window from '@ohos.window';
import common from '@ohos.app.ability.common';

@Entry
@Component
struct MediaQueryExample {
  @State color: string = '#DB7093';
  @State text: string = 'Portrait';
  @State portraitFunc:mediaquery.MediaQueryResult|void|null = null;
  // 当设备横屏时条件成立
  listener:mediaquery.MediaQueryListener = mediaquery.matchMediaSync('(orientation: landscape)');

  // 当满足媒体查询条件时,触发回调
  onPortrait(mediaQueryResult:mediaquery.MediaQueryResult) {
    if (mediaQueryResult.matches as boolean) { // 若设备为横屏状态,更改相应的页面布局
      this.color = '#FFD700';
      this.text = 'Landscape';
    } else {
      this.color = '#DB7093';
      this.text = 'Portrait';
    }
  }

  aboutToAppear() {
    // 绑定当前应用实例
    // 绑定回调函数
    this.listener.on('change', (mediaQueryResult:mediaquery.MediaQueryResult) => { this.onPortrait(mediaQueryResult) });
  }

  // 改变设备横竖屏状态函数
  private changeOrientation(isLandscape: boolean) {
    // 获取UIAbility实例的上下文信息
    let context:common.UIAbilityContext = getContext(this) as common.UIAbilityContext;
    // 调用该接口手动改变设备横竖屏状态
    window.getLastWindow(context).then((lastWindow) => {
      lastWindow.setPreferredOrientation(isLandscape ? window.Orientation.LANDSCAPE : window.Orientation.PORTRAIT)
    });
  }

  build() {
    Column({ space: 50 }) {
      Text(this.text).fontSize(50).fontColor(this.color)
      Text('Landscape').fontSize(50).fontColor(this.color).backgroundColor(Color.Orange)
        .onClick(() => {
          this.changeOrientation(true);
        })
      Text('Portrait').fontSize(50).fontColor(this.color).backgroundColor(Color.Orange)
        .onClick(() => {
          this.changeOrientation(false);
        })
    }
    .width('100%').height('100%')
  }
}

组件属性实现横竖屏的不同呈现方式

justifyContent属性

justifyContent属性可以设置Row组件 、 Column组件 或 Flex组件中子元素在父容器主轴方向的布局。其中justifyContent属性设置为FlexAlign.SpaceEvenly可以实现子元素在父容器主轴方向等间距布局,相邻元素之间的间距、第一个元素与行首的间距、最后一个元素到行尾的间距都完全一样。 SpaceEvenly均分的示例代码如下:

@Entry
@Component
struct EquipartitionCapabilitySample {
  readonly list: number [] = [0, 1, 2, 3]
  @State rate: number = 0.6

  // 底部滑块,可以通过拖拽滑块改变容器尺寸
  @Builder slider() {
    Slider({ value: this.rate * 100, min: 30, max: 60, style: SliderStyle.OutSet })
      .blockColor(Color.White)
      .width('60%')
      .onChange((value: number) => {
        this.rate = value / 100
      })
      .position({ x: '20%', y: '80%' })
  }

  build() {
    Column() {
      Column() {
        // 均匀分配父容器主轴方向的剩余空间
        Row() {
          ForEach(this.list, (item:number) => {
            Column() {
              Image($r("app.media.icon")).width(48).height(48).margin({ top: 8 })
              Text('App name')
                .width(64)
                .height(30)
                .lineHeight(15)
                .fontSize(12)
                .textAlign(TextAlign.Center)
                .margin({ top: 8 })
                .padding({ bottom: 15 })
            }
            .width(80)
            .height(102)
            .flexShrink(1)
          })
        }
        .width('100%')
        .justifyContent(FlexAlign.SpaceEvenly)
        // 均匀分配父容器主轴方向的剩余空间
        Row() {
          ForEach(this.list, (item:number) => {
            Column() {
              Image($r("app.media.icon")).width(48).height(48).margin({ top: 8 })
              Text('App name')
                .width(64)
                .height(30)
                .lineHeight(15)
                .fontSize(12)
                .textAlign(TextAlign.Center)
                .margin({ top: 8 })
                .padding({ bottom: 15 })
            }
            .width(80)
            .height(102)
            .flexShrink(1)
          })
        }
        .width('100%')
        .justifyContent(FlexAlign.SpaceEvenly)
      }
      .width(this.rate * 100 + '%')
      .height(222)
      .padding({ top: 16 })
      .backgroundColor('#FFFFFF')
      .borderRadius(16)

      this.slider()
    }
    .width('100%')
    .height('100%')
    .backgroundColor('#F1F3F5')
    .justifyContent(FlexAlign.Center)
    .alignItems(HorizontalAlign.Center)
  }
}

另外,ohos-connect\product\phone\src\main\ets\pages\CategoryPage.ets中Column设置了justifyContent属性为FlexAlign.Center,示例代码如下:

  TabBottom(item: TabItem, index: number) {
    Column() {
      Image(this.categoryTabIndex === index ? item.imageActivated : item.imageOriginal)
        .height($r('app.float.tab_image_size'))
        .width($r('app.float.tab_image_size'))
        .margin({
          top: $r('app.float.tab_margin_top'),
          bottom: $r('app.float.tab_margin_bottom')
        })

      Text(item.title)
        .width(CommonConstants.FULL_WIDTH_PERCENT)
        .height($r('app.float.tab_text_height'))
        .fontSize($r('app.float.tab_text_font_size'))
        .fontWeight(CommonConstants.TAB_ITEM_FONT_WEIGHT)
        .fontColor(this.categoryTabIndex === index ?
        $r('app.color.tab_text_activated') : $r('app.color.tab_text_normal'))
        .textAlign(TextAlign.Center)
        .margin({
          bottom: $r('app.float.tab_text_margin_bottom')
        })
    }
    .justifyContent(FlexAlign.Center)
    .height(CommonConstants.FULL_HEIGHT_PERCENT)
    .width(CommonConstants.FULL_WIDTH_PERCENT)
  }

wrap属性

wrap属性可以实现Flex折行布局,当横向布局尺寸不足以完整显示内容元素时,通过折行的方式,将元素显示在下方。 通过Flex组件warp参数实现自适应折行。

@Entry
@Component
struct WrapCapabilitySample {
  @State rate: number = 0.7
  readonly imageList: Resource [] = [
    $r('app.media.flexWrap1'),
    $r('app.media.flexWrap2'),
    $r('app.media.flexWrap3'),
    $r('app.media.flexWrap4'),
    $r('app.media.flexWrap5'),
    $r('app.media.flexWrap6')
  ]

  // 底部滑块,可以通过拖拽滑块改变容器尺寸
  @Builder slider() {
    Slider({ value: this.rate * 100, min: 50, max: 70, style: SliderStyle.OutSet })
      .blockColor(Color.White)
      .width('60%')
      .onChange((value: number) => {
        this.rate = value / 100
      })
      .position({ x: '20%', y: '87%' })
  }

  build() {
    Flex({ justifyContent: FlexAlign.Center, direction: FlexDirection.Column }) {
      Column() {
        // 通过Flex组件warp参数实现自适应折行
        Flex({
          direction: FlexDirection.Row,
          alignItems: ItemAlign.Center,
          justifyContent: FlexAlign.Center,
          wrap: FlexWrap.Wrap
        }) {
          ForEach(this.imageList, (item:Resource) => {
            Image(item).width(183).height(138).padding(10)
          })
        }
        .backgroundColor('#FFFFFF')
        .padding(20)
        .width(this.rate * 100 + '%')
        .borderRadius(16)
      }
      .width('100%')

      this.slider()
    }.width('100%')
    .height('100%')
    .backgroundColor('#F1F3F5')
  }
}

displayPriority属性

displayPriority属性可以设置布局优先级,来控制显隐,当布局主轴方向剩余尺寸不足以满足全部元素时,按照布局优先级大小,从小到大依次隐藏,直到容器能够完整显示剩余元素。具有相同布局优先级的元素将同时显示或者隐藏。

父容器尺寸发生变化时,其子元素按照预设的优先级显示或隐藏。

@Entry
@Component
struct HiddenCapabilitySample {
  @State rate: number = 0.45

  // 底部滑块,可以通过拖拽滑块改变容器尺寸
  @Builder slider() {
    Slider({ value: this.rate * 100, min: 10, max: 45, style: SliderStyle.OutSet })
      .blockColor(Color.White)
      .width('60%')
      .height(50)
      .onChange((value: number) => {
        this.rate = value / 100
      })
      .position({ x: '20%', y: '80%' })
  }

  build() {
    Column() {
      Row() {
        Image($r("app.media.favorite"))
          .width(48)
          .height(48)
          .objectFit(ImageFit.Contain)
          .margin({ left: 12, right: 12 })
          .displayPriority(1)  // 布局优先级

        Image($r("app.media.down"))
          .width(48)
          .height(48)
          .objectFit(ImageFit.Contain)
          .margin({ left: 12, right: 12 })
          .displayPriority(2)  // 布局优先级

        Image($r("app.media.pause"))
          .width(48)
          .height(48)
          .objectFit(ImageFit.Contain)
          .margin({ left: 12, right: 12 })
          .displayPriority(3)  // 布局优先级

        Image($r("app.media.next"))
          .width(48)
          .height(48)
          .objectFit(ImageFit.Contain)
          .margin({ left: 12, right: 12 })
          .displayPriority(2)  // 布局优先级

        Image($r("app.media.list"))
          .width(48)
          .height(48)
          .objectFit(ImageFit.Contain)
          .margin({ left: 12, right: 12 })
          .displayPriority(1)  // 布局优先级
      }
      .width(this.rate * 100 + '%')
      .height(96)
      .borderRadius(16)
      .backgroundColor('#FFFFFF')
      .justifyContent(FlexAlign.Center)
      .alignItems(VerticalAlign.Center)

      this.slider()
    }
    .width('100%')
    .height('100%')
    .backgroundColor('#F1F3F5')
    .justifyContent(FlexAlign.Center)
    .alignItems(HorizontalAlign.Center)
  }
}

不同场景下,横竖屏实现的方案不一样,属性方式可以实现,布局方式也可以实现。布局可能存在二次分配布局的问题,此情况下属性方式性能较佳。

国际化实现方法

以应用工程ohos-connect中的TopComponent.ets页面(ohos-connect\product\phone\src\main\ets\view\TopComponent.ets)中的 “Text($r('app.string.sub_title'))”资源引用为例,介绍国际化实现方法。

定义中英文含义

  • 定义字符:在ohos-connect\product\phone\src\main\resources\base\element\string.json中定义需要国际化的sub_title资源,定义其name、value。

      {
        "name": "grid_item_text",
        "value": "Subclass"
      },
      {
        "name": "sub_title",
        "value": "Sub title"
      },
      {
        "name": "title",
        "value": "Category"
      },
    
  • 定义资源对应的英文描述:在\ohos-connect\product\phone\src\main\resources\en_US\element\string.json中定义sub_title资源的英文描述,对应其value的内容。

      {
        "name": "grid_item_text",
        "value": "Subclass"
      },
      {
        "name": "sub_title",
        "value": "Sub title"
      },
      {
        "name": "title",
        "value": "Category"
      },
    
  • 定义资源对应的中文描述:在\ohos-connect\product\phone\src\main\resources\zh_CN\element\string.json中定义sub_title资源的中文描述,对应其value的内容。

      {
        "name": "grid_item_text",
        "value": "子类"
      },
      {
        "name": "sub_title",
        "value": "子标题"
      },
      {
        "name": "title",
        "value": "分类"
      },
    

引用已国际化的字符

使用时,使用“$r('app.string.sub_title')”这种方式引用已国际化的资源,即可实现不同语言系统不同显示的国际化需求。

  Text($r('app.string.sub_title'))
    .height($r('app.float.subtitle_height'))
    .fontColor($r('app.color.sub_text'))
    .fontSize($r('app.float.subtitle_font_size'))
    .fontWeight(CommonConstants.SUBTITLE_FONT_WEIGHT)
    .textAlign(TextAlign.Start)
}      

DFX实现方法

自定义log格式

\ohos-connect\common\src\main\ets\utils\Logger.ets文件中已定义,代码如下:

import hilog from '@ohos.hilog';

/**
 * Common log for all features.
 *
 * @param {string} prefix Identifies the log tag.
 */


let domain: number = 0xFF00;
let prefix: string = 'OhosConnectLogger';
let format: string = `%{public}s, %{public}s`;

export class Logger {
  static debug(...args: string[]) {
    hilog.debug(domain, prefix, format, args);
  }

  static info(...args: string[]) {
    hilog.info(domain, prefix, format, args);
  }

  static warn(...args: string[]) {
    hilog.warn(domain, prefix, format, args);
  }

  static error(...args: string[]) {
    hilog.error(domain, prefix, format, args);
  }

  static fatal(...args: string[]) {
    hilog.fatal(domain, prefix, format, args);
  }

  static isLoggable(level: LogLevel) {
    hilog.isLoggable(domain, prefix, level);
  }
}

/**
 * Log level define
 *
 * @syscap SystemCapability.HiviewDFX.HiLog
 */
enum LogLevel {
  DEBUG = 3,
  INFO = 4,
  WARN = 5,
  ERROR = 6,
  FATAL = 7
}

使用已定义的Logger进行打印

示例代码如下:

import { CommonConstants, Logger } from '@ohos/common'
   ......

    AddScenePage({
      addClick: (type: number) => {
        router.pushUrl({
          url: "pages/ScenePage",
          params: {type: 3, category: type}
        }).catch((error: Error) => {
          Logger.error('LauncherPage pushUrl error ' + JSON.stringify(error));
        });