HarmonyOS:一次开发,多端部署(1)界面级一多开发
★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★
➤博客园地址:为敢技术(https://www.cnblogs.com/strengthen/ )
➤GitHub地址:https://github.com/strengthen
➤原文地址:https://www.cnblogs.com/strengthen/p/18511635
➤如果链接不是为敢技术的博客园地址,则可能是爬取作者的文章。
➤原文已修改更新!强烈建议点击原文地址阅读!支持作者!支持原创!
★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★
一、布局能力
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设备,界面呈现出相应的状态为用户提供正确的视觉引导。例如触摸时显示按压状态,鼠标特有的悬停状态,键盘走焦状态。
四、多设备预览