为有牺牲多壮志,敢教日月换新天。

HarmonyOS:一次开发,多端部署(1)界面级一多开发

★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★
➤博客园地址:为敢技术(https://www.cnblogs.com/strengthen/ 
➤GitHub地址:https://github.com/strengthen
➤原文地址:https://www.cnblogs.com/strengthen/p/18511635
➤如果链接不是为敢技术的博客园地址,则可能是爬取作者的文章。
➤原文已修改更新!强烈建议点击原文地址阅读!支持作者!支持原创!
★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★

  

一、布局能力

1、自适应布局(Adaptive Layout):元素可以根据相对关系自动变化以适应外部容器变化的布局能力。当前开发框架提炼了七种自适应布局能力,这些布局可以独立使用,也可多种布局叠加使用。

2、响应式布局(Responsive Layout):元素可以根据特定的特征(如窗口宽度、屏幕方向等)触发变化以适应外部容器变化的布局能力。响应式布局基于断点、媒体查询、栅格等能力实现。

 3、自适应布局七种常见能力。

(1)、拉伸能力:是指容器组件尺寸发生变化时,增加或减小的空间全部分配给容器组件内指定区域。本例中,页面由中间的内容区(包含一张图片)以及两侧的留白区组成。拉伸能力可以使用flex组件的fLexGrow和FLexShrink属性。代码解读:左边空白是150个VP,Image是400个VP,右边的留白也是150个VP,加起来是700个VP,当这个父容器的尺寸小于700VP的时候,左右两侧的留白区域,就会按照1:1的比例进行收缩,当这个父容器的尺寸大于700VP的时候,父容器中的多余的部分就全部留给了中间的一幅图片,最后呈现的形式就是,这张图片进行拉伸,这700个VP是400+150+150=700。

Row() {
      // 通过fLexGrow和FLexShrink属性,将多余的空间全部分配给图片,将不足的控件全部分配给两侧空白区域。
      Row().width(150)
        .flexGrow(0).flexShrink(1)
      Image($r("app.media.illustrator")).width(400)
        .flexGrow(1).flexShrink(0)
      Row().width(150)
        .flexGrow(0).flexShrink(1)
}

(2)、均分能力:是指容器组件尺寸发生变化时,增加或减小的空间均匀分配给容器组件内所有空白区域。本例中,父容器尺寸变化过程中,图标及文字的尺寸不变,图标间的间距及图标离左右边缘的距离同时均等改变。均分能力主要使用Row、Column和Flex组件的justifyContent属性进行设置。代码解读:将Row设置了justifyContent,然后设置的是FlexAlign.SpaceEvenly属性,本例中使用了Row组件的属性,随着父容器的缩放,图标和文字的比例尺寸,始终是不变的,只是左右和中间的这样一个留白,它是按等比例的进行均分的。

    Column() {
      Row() {
        ForEach(this.list, (item: number) => {...})
      }.width('100%')
      // 均匀分配父容器主轴方向的剩余空间
      .justifyContent(FlexAlign.SpaceEvenly)
      //同上ROw
      Row() {...}
    }
    •width(this.rate * 100 + '%')

(3)、占比能力:是指子组件的宽高按照预设的比例,随父容器组件发生变化。例如本例中,简单的播放控制栏,其中“上一首”、“播放/暂停”、“下一首”的layoutWeight属性都设置为1,因此它们按照“1:1:1”的比例均分父容器主轴方向的空间。当然,可以根据不同的场景,把权重比例设置成1:2:1,或者其他的一些属性参数,也会按照其他的这样一个比例,进行布局和划分。

  Row() {
    Column() {...}
    .layoutweight(1) // 设置子组件在父容器主轴方向的布局权重
    Column() {...}
    .layoutweight(1) // 设置子组件在父容器主轴方向的布局权重
    Column() {...}
    .layoutWeight(1) // 设置子组件在父容器主轴方向的布局权重
  }
  .width(this.rate * 100 + '%')

(4)、缩放能力:是指子组件的宽高按照预设的比例,随容器组件发生变化,且变化过程中子组件的宽高比不变。例如本例中,Column组件随着其Flex父组件尺寸变化而缩放的过程中,始终保持预设的宽高比,其中的图片也始终正常显示。代码解读:下图是一张图片,给图片设置aspectRatio属性值为1,其始终保持着这张图片的这个比例是1,等比例的进行缩放。

  Column() {
    Column() {
      Image($r("app.media.illustrator"))
        .width('100%').height('100%')
    }
    .aspectRatio(1) // 固定宽高比
   }
   .height(this.sliderHeight)
   .width(this.sliderWidth)

(5)、延伸能力:是指容器组件内的子组件,按照其在列表中的先后顺序,随容器组件尺寸变化显示或隐藏。它可以根据显示区域的尺寸,显示不同数量的元素。例如本例中,当父容器的尺寸发生改变时,页面中显示的图标数量随之发生改变。本例使用的是List组件,把List的组件方向设置为横向布局,实现这个屏幕尺寸在进行变化的时候,有隐藏或显示的效果。

  Row({ space: 10 }) {
  // 通过ist组件实现隐藏能力
  List({ space: 10}){...}
  .listDirection(Axis.Horizontal)
  .width('100%')
  }
  .width(this.rate * 100 + '%')

(6)、隐藏能力: 指容器组件内的子组件,按照其预设的显示优先级,随容器组件尺寸变化显示或隐藏,其中相同显示优先级的子组件同时显示或隐藏。例如本例中,下面5个按键设置了不同的优先级,父组件宽度变化后,优先显示高优先级的元素。给不同的图片设置不同的优先级,通过优先级进行组件的显示或隐藏。

  Row() {
    Image($r("app.media.favorite"))
      .displayPriority(1) // 布局优先级
    Image($r("app.media.down"))
    .displayPriority(2) // 布局优先级
    Image($r ("app.media.pause"))
      .displayPriority(3) // 布局优先级
    Image($r("app.media.next"))
      .displayPriority(2) // 布局优先级
    Image($r("app.media.list"))
    .displayPriority(1) // 布局优先级
  }
  .width(this.rate * 100 + '%')

(7)、折行能力:是指容器组件尺寸发生变化,当布局方向尺寸不足以显示完整内容时自动换行。它常用于横竖屏适配或默认设备向平板切换的场景。例如本例中,当父容器尺寸发生变化,其中的内容做自适应换行。折行能力使用的是Flex组件的Wrap属性,当父容器进行缩放的时候,其中的内容就会自适应的换行,

  Column () {
    // 通过FLex组件warp参数实现自适应折行
    Flex ({
      wrap: FlexWrap.Wrap, direction: FlexDirection.Row
      }){
      ForEach(this imageList, (item:Resource)=> {
        Image(item).width(183).height(138)
      })
    }
    .width(this.rate * 100 + '%')
  }

3、自适应布局应用实例。源码下载:MusicHome.zip

 4、响应式布局三种基础能力。

(1)、断点和媒体查询,它们是一起结合使用的。

断点:将窗口宽度划分为不同的范围(即断点),监听窗口尺寸变化,当断点改变时同步调整页面布局。注意,断点支持自定义,取值范围可修改扩展,下表是常用的4个断点范围。

可以设想一下,一个屏幕,实际上就是从0到无穷大的一个区间段。把屏幕划分为如下四个区间段:xs超小、sm小、md中等、lg就是大。每一个区间段的临界点称为断点。更重要的是让代码如何查询到断点,这个用到的就是媒体查询这个能力。

媒体查询:提供了丰富的媒体特征监听能力,可以监听应用显示区域变化、横竖屏、深浅色、设备类型等等,本文仅介绍媒体查询跟断点的结合,即如何借助媒体查询能力,监听断点的变化。

工具类:

import { mediaquery } from '@kit.ArkUI';
import { BreakpointConstants } from '@ohos/constantsCommon';

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

export class BreakpointType<T> {
  options: BreakPointTypeOption<T>;

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

  getValue(currentPoint: string): T {
    if (currentPoint === 'sm') {
      return this.options.sm as T;
    }
    else if (currentPoint === 'md') {
      return this.options.md as T;
    } else {
      return this.options.lg as T;
    }
  }
}

export class BreakpointSystem {
  // 定义了几种不同屏幕尺寸的监听,
  private currentBreakpoint: string = BreakpointConstants.BREAKPOINT_SM;
  private smListener: mediaquery.MediaQueryListener = mediaquery.matchMediaSync(BreakpointConstants.RANGE_SM);
  private mdListener: mediaquery.MediaQueryListener = mediaquery.matchMediaSync(BreakpointConstants.RANGE_MD);
  private lgListener: mediaquery.MediaQueryListener = mediaquery.matchMediaSync(BreakpointConstants.RANGE_LG);


  // 在AppStorage里面,去存储了key-value标识位。
  private updateCurrentBreakpoint(breakpoint: string): void {
    if (this.currentBreakpoint !== breakpoint) {
      this.currentBreakpoint = breakpoint;
      AppStorage.setOrCreate<string>(BreakpointConstants.CURRENT_BREAKPOINT, this.currentBreakpoint);
    }
  }

  // 在屏幕尺寸变化的时候,需要有个标记位去记录屏幕尺寸的变化。
  private isBreakpointSM = (mediaQueryResult: mediaquery.MediaQueryResult): void => {
    if (mediaQueryResult.matches) {
      this.updateCurrentBreakpoint(BreakpointConstants.BREAKPOINT_SM);
    }
  }
  private isBreakpointMD = (mediaQueryResult: mediaquery.MediaQueryResult): void => {
    if (mediaQueryResult.matches) {
      this.updateCurrentBreakpoint(BreakpointConstants.BREAKPOINT_MD);
    }
  }
  private isBreakpointLG = (mediaQueryResult: mediaquery.MediaQueryResult): void => {
    if (mediaQueryResult.matches) {
      this.updateCurrentBreakpoint(BreakpointConstants.BREAKPOINT_LG);
    }
  }

  // register实现的就是这几种不同屏幕尺寸的监听。
  public register(): void {
    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(): void {
    this.smListener.off('change', this.isBreakpointSM);
    this.mdListener.off('change', this.isBreakpointMD);
    this.lgListener.off('change', this.isBreakpointLG);
  }
}

如何使用工具类:通过import方式引入BreakpointSystem工具类。

import { BreakpointConstants, RouterUrlConstants, StyleConstants } from '@ohos/constantsCommon';

给BreakpointSystem工具类进行实例化:

@StorageProp ('currentBreakpoint') currentBreakpoint: string = 'sm'

在页面生命周期的时机进行注册监听和取消监听。

  aboutToAppear(){
    this.breakpointSystem.register()
  }

  aboutToDisappear(){
    this.breakpointSystem.unregister()
  }

(2)、栅格布局:根据设备的水平宽度,将不同的屏幕尺寸划分为不同数量的栅格,来实现屏幕的自适应。栅格布局逻辑跟断点和媒体查询是一样的,把一个设备,从0到无穷大,把它画成不同的栅格。比如手表是一个超小尺寸,给手表画2个栅格,手机竖屏4个栅格,手机横屏8个栅格。可以根据设备的水平宽度,将屏幕画成不同的栅格来实现屏幕的自适应。

(3)、栅格布局案例:

一般来说推荐开发者安卓4:8:12的比例,进行不同栅格划分,栅格和栅格之间有一定的留白,留白的区域设置的是12VP,有这样的留白以后,才能清楚的把这个栅格给数出来,如果留白是0,则不好区分。

如何占栅格:初始化都是占比一个栅格。

  • 可以调节布局占栅格的数量(设置参数span)、偏移量(设置参数offset),来实现栅格的适配。
  • 可以修改断点的取值范围,支持启用最多6个断点(设置breakpoints的value参数)。

栅格布局后:

栅格布局代码实现:

5、栅格组件应用场景(1)缩进布局:一是通过设置GridCol的span属性分配组件所占栅格列数;二是通过设置GridCol的offset、GridRow的gutter等属性改变间距,实现最佳效果。当sm:0时可以进行省略。

栅格组件应用场景(2)挪移布局:通过设置GridCol的span属性分配组件所占栅格列数。原来本来是一个上下布局,通过挪移布局变成了左右布局。操作原理:实际上栅格的row和column可以进行一个嵌套,比如把四个栅格占满了,则进行向下的1个换行的布局。

栅格组件应用场景(3)重复布局:通过设置GridCol的span属性分配组件所占栅格列数。

响应式布局应用实例,播放器案例:12的约数为:1、2、3、4、6,所以选择1:4:12

二、视觉风格

1、视觉风格(1):分层参数:为了保证各组件有相同风格的默认样式,或者为了保证HarmonyOS系统应用有统一的风格。UX定义了一套系统资源,预置在系统中,开发者可以直接使用,称为分层参数。使用了分层参数以后,不同设备都是系统去调用一个headline等于6这样一个属性,在手机和TV上,手机的屏幕小一点,TV的屏幕大一点,可以实现字体是不一样的,由此就可以去解决多设备适配的问题。

2、使用了分层参数后,当系统切换深色模式时,字体和背景也可以自适应。下方示例调用的是system.color、system.float这样的一些限定,这样就在深色模式和正常模式下进行了自适应的切换。

3、开发者可以在resources目录中通过限定词目录来定义不同设备状态的资源,资源可以按照“key-Value”的形式自定义。应用在运行态选择使用某资源时,系统会根据设备状态优先从相匹配的目录中寻找资源。

自定义资源:

三、交互归一

对于不同类型的智能设备,用户可能有不同的交互方式,如通过触摸屏、鼠标、触控板等。针对不同来自不同输入设备的相同输入,通过交互归一提供给开发者统一的API。交互归一后开发者无需关注当前设备和输入设备类型,只需在交互归一事件接口中做逻辑响应即可。

1、交互归一:缩放,以缩放交互为例,通过多指触控的张合来完成缩放动作,在多设备场景下,缩放交互会出现多种不同的操作输入方式,如表所示。在开发接口上,这些缩放操作都统一为PinchGesture的API事件。

2、交互归一:组件归一响应,当应用部署在不同设备上供用户使用时,需要支持多种1/0设备,界面呈现出相应的状态为用户提供正确的视觉引导。例如触摸时显示按压状态,鼠标特有的悬停状态,键盘走焦状态。

四、多设备预览

 

 

posted @ 2024-10-28 21:00  为敢技术  阅读(21)  评论(0编辑  收藏  举报