ArkUI页面简化支持RC组件库开发指导
概述
当前HarmonyOS应用开发的过程中,断点需要开发者自己定义布局边界,由于HarmonyOS设备种类众多,因此断点适配需要设计多种布局,给开发者带来不便。
考虑到部分应用的布局形态仅考虑小屏(如直板机)与大屏(如平板)两种状态,本文将介绍一种对窗口布局适配精细度要求较低,且对特定组件有独立布局要求的布局方案:
- 在应用窗口级别,定义横纵方向的布局分别为R (Regular,宽松) /C (Compact,紧凑) 两种,即 2x2 的布局方案。
- 在应用容器级别,每个容器可定义独立于窗口级RC布局的容器级RC布局。
- 针对每种RC布局方案,配置相应的资源后可根据布局自动获取相应资源。
效果展示
注意:本文中的测试与效果演示设备为Mate 60,系统版本为5.0.0.123,使用其他设备可能由于设备大小或版本差异导致布局与文中效果不一致,需开发者根据实际情况进行调整。
横向紧凑,纵向不限(Mate 60竖屏) |
横向宽松,纵向不限(Mate 60横屏) |
---|---|
|
|
原理介绍
监听回调
窗口级RC布局通过媒体查询的方式监听窗口大小,并根据大小变化的回调更新布局状态。由于媒体查询依赖UI上下文,因此需要保证监听注册处UIContext可获取,参见UIContext说明。
容器级RC目前通过绑定组件尺寸变化事件实现监听效果,在尺寸变化事件回调中更新布局状态。
横纵断点
窗口级RC布局本质上是一套简化的断点机制,将HarmonyOS 4 * 3 的断点简化为 2 * 2 的规格。
窗口RC分界值 |
|
---|---|
横向 |
可根据实际需求自行配置,范围:[0, 2560],默认600,单位为vp |
纵向 |
可根据实际需求自行配置,范围:[0, 2560],默认800,单位为vp |
资源获取
根据文件名以及RC布局状态匹配对应的目录并获取相应资源,需要开发者按需自行创建相应目录并配置资源。
└── entry // 工程入口文件夹
└── src
└── main
└── resources
├── rawfile // rawfile文件夹,需要创建
│ └── size_media // size_media文件夹,需要创建
│ ├── base // 通用布局资源文件夹,适用于未定义的布局状态,建议配置以避免特定布局下资源访问异常
│ │ ├── image1.png // 对应布局下的资源
│ │ ├── image2.png // 对应布局下的资源
│ │ └── ...
│ ├── ca // 横向紧凑,纵向不限,按需创建
│ │ ├── image1.png // 对应布局下的资源
│ │ ├── image2.png // 对应布局下的资源
│ │ └── ...
│ ├── cc // 横向紧凑,纵向紧凑,按需创建
│ ├── cr // 横向紧凑,纵向宽松,按需创建
│ ├── ra // 横向宽松,纵向不限,按需创建
│ │ ├── image1.png // 对应布局下的资源
│ │ ├── image2.png // 对应布局下的资源
│ │ └── ...
│ ├── rc // 横向宽松,纵向紧凑,按需创建
│ ├── rr // 横向宽松,纵向宽松,按需创建
│ ├── ac // 横向不限,纵向紧凑,按需创建
│ └── ar // 横向不限,纵向宽松,按需创建
├── base // 默认base资源文件夹
├── en_US // 默认en_US资源文件夹
└── zh_CN // 默认zh_CN资源文件夹
依赖安装
通过ohpm下载并安装依赖,OpenHarmony ohpm环境配置等更多内容,请参考如何安装 OpenHarmony ohpm 包。
ohpm install @hadss/size_class_layout
使用说明
窗口级RC布局
- 适配说明
- 选择监听使用方式:
- 在EntryAbility.ets中定义SizeClassManager对象,在窗口的初始化与销毁回调中分别设置窗口监听与解绑逻辑,此时需要注意监听注册处UIContext可获取。例如在onWindowStageCreate内调用loadContent或getMainWindow,之后在其回调函数内注册监听,之后再onWindowStageWillDestory内进行解绑。
- 在页面声明式UI内定义SizeClassManager对象,在aboutToAppear与aboutToDisappear中分别设置窗口监听与解绑逻辑。
- 在UI页面文件中,按需定义@StorageLink修饰的横纵向布局状态变量。
- 在上述变量定义的组件中,定义SizeClassState对象,使用不同状态下的配置值对其进行初始化,后续使用getValue方法根据当前状态获取配置值。
- 场景案例
默认布局(横向紧凑,纵向不限布局)下,文本所在Column组件缩放倍率为1;横向宽松且纵向不限布局下,文本所在Column组件缩放倍率为2。通过旋转设备切换窗口的布局状态,可以直接观察到文本大小的变化。
横向紧凑,纵向不限(Mate 60) |
横向宽松,纵向不限(Mate 60) |
---|---|
|
|
关键代码片段:
// EntryAbility.ets
// 导入依赖对象
import { SizeClassManager } from '@hadss/size_class_layout';
export default class EntryAbility extends UIAbility {
private sizeClassManager: SizeClassManager | undefined = undefined;
// ...
onWindowStageCreate(windowStage: window.WindowStage): void {
// ...
windowStage.getMainWindow((err, windowClass) => {
// 步骤一:定义窗口监听对象,默认横向布局边界为600vp,纵向为800vp。此处在getMainWindow回调函数内注册保证监听成功。
this.sizeClassManager = new SizeClassManager();
// 步骤一:注册窗口监听逻辑定义
this.sizeClassManager.register();
})
}
onWindowStageWillDestroy(): void {
// 步骤一:解除窗口监听逻辑定义
this.sizeClassManager?.unregister();
}
}
// Index.ets
// 导入依赖对象
import { SizeClassState, SizeClassType, Constants } from '@hadss/size_class_layout';
import { window } from '@kit.ArkUI';
@Entry
@Component
struct Index {
// 步骤二:定义横向布局状态
@StorageLink(Constants.HORIZONTAL_PROPNAME) wSizeType: SizeClassType = SizeClassType.ANY;
// 当前窗口宽度
@State windowWidth: number = 0;
private windowStage: window.Window | undefined = undefined;
async aboutToAppear(): Promise<void> {
// 注册窗口宽度监听
this.windowStage = await window.getLastWindow(getContext(this));
this.windowStage.on('windowSizeChange', (windowSize: window.Size) => {
this.windowWidth = px2vp(windowSize.width);
});
// 获取窗口宽度初始值
this.windowWidth = px2vp(this.windowStage.getWindowProperties().windowRect.width);
}
aboutToDisappear(): void {
// 取消监听
this.windowStage?.off('windowSizeChange');
}
build() {
Column() {
Column() {
Text(`Window widthSizeType: ${this.wSizeType}`)
// 实时展示当前窗口宽度与布局边界值
Text(`Window width: ${this.windowWidth.toFixed(0)}vp, horizontal border: ${Constants.DEFAULT_HORIZONTAL_BORDER}vp`)
}
// 步骤三:定义不同状态下的配置值并使用,根据窗口布局状态改变Column组件显示缩放倍率
.scale(new SizeClassState<ScaleOptions>({
// any为默认布局,处理无匹配布局状态的情况
any: {
x: 1,
y: 1
},
wCAndHAny: {
x: 1,
y: 1
},
wRAndHAny: {
x: 2,
y: 2
}
}).getValue({widthSizeType: this.wSizeType}))
}
.justifyContent(FlexAlign.Center)
.width('100%')
.height('100%')
}
}
容器级RC布局
- 适配说明
- 定义SizeClassRule对象,使用容器RC的布局边界进行初始化。
- 在UI页面文件中,按需定义@State修饰的横纵向布局状态变量。
- 在RC布局作用的容器对象中,在其onSizeChange方法中使用新的布局边界更新SizeClassRule内部的RC状态,并更新至上一步骤中定义的状态变量。
- 在自定义布局状态变量的作用范围内,定义SizeClassState对象,使用不同状态下的配置值对其进行初始化,后续使用getValue方法传入当前状态获取配置值。
- 场景案例
默认布局(横向紧凑,纵向宽松布局)下,Column组件为黄色;横向宽松且纵向紧凑布局下,Column组件为灰色。通过旋转设备切换Column容器的布局状态,可直接观察到颜色的变化。
横向紧凑,纵向宽松(Mate 60) |
横向宽松,纵向紧凑(Mate 60) |
---|---|
|
|
关键代码片段:
// Index.ets
// 导入依赖对象
import { SizeClassBorder, SizeClassRules, SizeClassState, SizeClassType } from '@hadss/size_class_layout';
// 定义横向边界为200vp,纵向边界为200vp
const rulesBorder: SizeClassBorder = {
horizontalBorder: 200,
verticalBorder: 200
}
@Entry
@Component
struct Index {
// 步骤一:定义容器布局边界,使用上述布局进行初始化
private containerRule: SizeClassRules = new SizeClassRules(rulesBorder);
// 步骤二:定义容器横向布局状态
@State wSizeType: SizeClassType = SizeClassType.ANY;
// 步骤二:定义容器纵向布局状态
@State hSizeType: SizeClassType = SizeClassType.ANY;
@State columnWidth: number = 0;
@State columnHeight: number = 0;
build() {
Column() {
// 展示Column组件宽高度信息、布局边界值以及RC状态
Text(`Column width: ${this.columnWidth.toFixed(0)}vp, border: ${rulesBorder.horizontalBorder}vp`)
Text(`Column height: ${this.columnHeight.toFixed(0)}vp,, border: ${rulesBorder.verticalBorder}vp`)
Text(`Column wSizeType: ${this.wSizeType}`)
Text(`Column hSizeType: ${this.hSizeType}`)
Column() {
}
.width('50%')
.height('50%')
// 步骤四:定义不同状态下的配置值并使用,根据Column组件布局状态改变其颜色
.backgroundColor(new SizeClassState<Color>({
// any为默认布局,处理无匹配布局状态的情况
any: Color.Yellow,
wCAndHR: Color.Yellow,
wRAndHC: Color.Gray,
}).getValue({ widthSizeType: this.wSizeType, heightSizeType: this.hSizeType }))
.onSizeChange((oldValue: SizeOptions, newValue: SizeOptions) => {
if (typeof newValue.width === 'number' && typeof newValue.height === 'number') {
// 步骤三:根据容器变化后的布局边界更新RC状态
this.containerRule.updateSizeClassType({ width: newValue.width ?? 0, height: newValue.height ?? 0 });
// 步骤三:获取更新后的横向容器RC状态
this.wSizeType = this.containerRule.getHorizontalSizeClass();
this.hSizeType = this.containerRule.getVerticalSizeClass();
// 更新Column组件宽高度
this.columnWidth = newValue.width;
this.columnHeight = newValue.height;
}
})
}
.justifyContent(FlexAlign.Center)
.width('100%')
.height('100%')
}
}
RC资源适配
- 适配说明
- 在resources下新建rawfile/size_media,内部可配置相应布局下的资源。
- 在EntryAbility或UI页面文件内应用窗口级RC或者容器级RC。
- 在UI页面文件内,根据上一步骤中选择的RC类型定义相应的横纵向布局状态变量。
- 在上述布局状态变量的作用范围内,调用SizeClassResUtils的静态getRes方法,通过传入资源名与布局状态来获取相应资源。
- 场景案例
下图样例使用默认布局边界值(横向600vp,纵向800vp)。在横向紧凑布局下,为默认图片资源;横向宽松布局下,图片资源发生变化。通过旋转设备切换窗口的布局状态,可以直接观察到图片的变化。
横向紧凑,纵向不限(Mate 60) |
横向宽松,纵向不限(Mate 60) |
---|---|
|
|
关键代码片段:
// 步骤一:创建资源文件夹,创建后的工程目录结构如下
/*
* └── entry // 工程入口文件夹
* └── src
* └── main
* └── resources
* ├── rawfile // rawfile文件夹,需要创建
* │ └── size_media // size_media文件夹,需要创建
* │ ├── base // 通用布局,在未匹配到相应布局时适用,此处配置以避免特定布局资源访问异常
* │ │ └── shopHeaderImage.png // 对应布局下的资源
* │ ├── ca // 横向紧凑,纵向不限(wCAndHAny)
* │ │ └── shopHeaderImage.png // 对应布局下的资源
* │ └── ra // 横向宽松,纵向不限(wRAndHAny)
* │ └── shopHeaderImage.png // 对应布局下的资源
* ├── base // 默认base资源文件夹
* ├── en_US // 默认en_US资源文件夹
* └── zh_CN // 默认zh_CN资源文件夹
*/
// EntryAbility.ets
// 步骤二:应用窗口级RC,代码与窗口级RC布局一致,此处省略
// Index.ets
// 导入依赖对象
import { SizeClassType, SizeClassResUtils, Constants } from '@hadss/size_class_layout';
import { window } from '@kit.ArkUI';
@Entry
@Component
struct Index {
// 步骤三:定义横向布局状态
@StorageLink(Constants.HORIZONTAL_PROPNAME) wSizeType: SizeClassType = SizeClassType.ANY;
// 当前窗口宽度
@State windowWidth: number = 0;
private windowStage: window.Window | undefined = undefined;
async aboutToAppear(): Promise<void> {
// 注册窗口宽度监听
this.windowStage = await window.getLastWindow(getContext(this));
this.windowStage.on('windowSizeChange', (windowSize: window.Size) => {
this.windowWidth = px2vp(windowSize.width);
});
// 获取窗口宽度初始值
this.windowWidth = px2vp(this.windowStage.getWindowProperties().windowRect.width);
}
aboutToDisappear(): void {
// 取消监听
this.windowStage?.off('windowSizeChange');
}
build() {
Column() {
Column() {
Text(`Window widthSizeType: ${this.wSizeType}`)
.backgroundColor(Color.Yellow)
// 步骤四:根据图片名以及横向布局状态获取相应图片资源
Image(SizeClassResUtils.getRes('shopHeaderImage.png', { widthSizeType: this.wSizeType }))
.width('80%')
.height('80%')
// 实时展示当前窗口宽度与布局边界值
Text(`Window width: ${this.windowWidth.toFixed(0)}vp, horizontal border: ${Constants.DEFAULT_HORIZONTAL_BORDER}vp`)
.backgroundColor(Color.Yellow)
}
}
.justifyContent(FlexAlign.Center)
.width('100%')
.height('100%')
}
}
注意事项
- 在EntryAbility中注册监听时,建议在onWindowStageCreate内调用loadContent或getMainWindow,之后在其回调函数内注册监听,保证注册监听处媒体查询生效。可参考本文的“原理介绍”一节。
- 建议在onWindowStageWillDestory/onWindowStageDestory方法调用unregister取消窗口监听,避免内存泄漏。
- 当应用对某一方向布局不敏感时,可以只定义单一布局状态进一步减少复杂度,常见于直板机/横向折叠/平板间的布局适配,一般仅需考虑横向状态,此时纵向布局状态默认为ANY。
- 使用布局配置时,如果已定义的布局类型覆盖不全面,可能存在未匹配到部分布局的情况,例如仅定义了cr与rc的布局,导致rr无法匹配,使用出现异常。 这种情况建议额外配置通用布局,即使用SizeClassState时定义any下的配置,使用SizeClassResUtils前配置base目录下的资源。
示例代码
更多推荐
所有评论(0)