鸿蒙5.0开发实战案例:智慧多窗开发全攻略
本文全面介绍了鸿蒙系统的智慧多窗功能,包含悬浮窗和分屏两种多任务模式。悬浮窗支持竖向和横向两种类型,适用于临时任务处理;分屏则适合长时间并行使用应用。文章详细说明了两种模式的触发方式、比例参数和配置方法,并强调了应用布局适配的重要性。开发者可通过修改module.json5配置文件声明支持智慧多窗功能,需特别注意不同设备(手机/折叠屏)的比例差异。
📑往期推文全新看点(文中附带最新·鸿蒙全栈学习笔记)
✒️ 鸿蒙应用开发与鸿蒙系统开发哪个更有前景?
✒️ 嵌入式开发适不适合做鸿蒙南向开发?看完这篇你就了解了~
✒️ 对于大前端开发来说,转鸿蒙开发究竟是福还是祸?
✒️ 鸿蒙岗位需求突增!移动端、PC端、IoT到底该怎么选?
✒️ 记录一场鸿蒙开发岗位面试经历~
✒️ 持续更新中……
智慧多窗是一种多任务处理解决方案,它允许用户在同一时间、同一屏幕上以悬浮窗或分屏的方式同时运行多个应用窗口。在智慧多窗的显示模式下,用户可以根据自己的需求,合理安排应用窗口的位置和大小。
悬浮窗
悬浮窗是一种在设备屏幕上悬浮的、非全屏的应用窗口。一般用于在已有全屏任务运行的基础上,临时处理另一个任务,或短时间多任务并行使用。如浏览网页的同时回复消息。
针对手机,一个屏幕内最多支持显示一个悬浮窗;在折叠屏手机展开态、平板类设备上,一个屏幕内最多支持显示两个悬浮窗。在超出悬浮窗显示最大个数限制时,打开新的悬浮窗会替换最近久未操作的悬浮窗。
悬浮窗的类型
悬浮窗的常见类型主要分为如下两种:
- 竖向悬浮窗:一般用于新闻资讯、社交以及购物类应用等场景。
- 横向悬浮窗:主要用于横向游戏和视频全屏播放的场景。
悬浮窗的触发及恢复方式
悬浮窗的触发方式有以下两种:
- 手势触发:应用全屏时从屏幕底部向上滑至右上方热区,松手后可开启悬浮窗模式。
- 通知消息下拉触发:在系统接收到通知消息未收起时,可直接下拉此通知消息开启悬浮窗模式。
悬浮窗的恢复方式主要有以下两种:
- 多任务中心中恢复:对于已开启悬浮窗模式的应用,在进入多任务中心时,悬浮窗应用同全屏应用一起显示在多任务中心,用户选择点击悬浮窗应用卡片时可恢复悬浮窗模式。
- 侧边条恢复:对于已开启悬浮窗模式的应用,其最小化后会暂存在屏幕上的侧边条中,点击或者长按侧边条可展开任务选择界面,选择点击侧边条中悬浮窗应用卡片时可恢复悬浮窗模式。
分屏
分屏一般用于两个应用长时间并行使用的场景。例如边看购物攻略、边浏览商品;边看视频、边玩游戏;看学习类视频的同时做笔记等。
分屏的触发方式
分屏通过手势触发:应用全屏时,从屏幕底部向上滑至左上方热区,进入待分屏状态,点击桌面另一个支持分屏的应用图标或卡片,可形成分屏。
应用声明支持智慧多窗
当应用需要智慧多窗的能力时,可以通过在 module.json5配置文件 中对应标签添加相关字段声明支持。
声明支持悬浮窗
开发者可以通过在module.json5配置文件中 abilities标签 下的supportWindowMode属性增加“floating”字段或使用默认值以声明应用支持悬浮窗。
supportWindowMode属性主要标识当前UIAbility所支持的窗口模式,支持的字段及含义如下表所示。
字段 | 说明 |
---|---|
fullscreen | 窗口支持全屏显示。 |
split | 窗口支持分屏显示。 |
floating | 支持悬浮窗显示。 |
在应用声明支持智慧多窗后,还可根据业务场景的需要配置是否支持横向悬浮窗或上下分屏模式。
当应用需要支持横向悬浮窗时,开发者可以通过在module.json5配置文件中abilities标签下的preferMultiWindowOrientation属性增加“landscape”或者“landscape_auto”配合API以声明应用支持横向悬浮窗或上下分屏模式。
preferMultiWindowOrientation属性主要标识当前UIAbility组件多窗布局方向,支持的字段及含义如下表所示。
配置值 | 说明 | 效果 |
---|---|---|
portrait | 多窗布局方向为竖向。建议竖向游戏类应用配置。 | 手机 手势触发悬浮窗:竖向悬浮窗 手势触发分屏:不支持 分屏样式切换:不涉及 折叠屏手机展开态 手势触发悬浮窗:竖向悬浮窗 手势触发分屏:形成左右分屏 分屏样式切换:不支持样式切换 |
landscape | 多窗布局方向为横向,配置后支持横向悬浮窗和上下分屏。建议横向游戏类应用配置。 | 手机 手势触发悬浮窗:横向悬浮窗 手势触发分屏:不支持 分屏样式切换:不涉及 折叠屏手机展开态 手势触发悬浮窗:横向悬浮窗 手势触发分屏:形成上下分屏 分屏样式切换:不支持样式切换 |
landscape_auto | 多窗布局动态可变为横向,需要配合API( enableLandscapeMultiWindow / disableLandscapeMultiWindow )使用。建议视频类应用配置。 | 系统识别应用为横向全屏播放: 手机 手势触发悬浮窗:横向悬浮窗 手势触发分屏:形成上下分屏 分屏样式切换:不涉及 折叠屏手机展开态 手势触发悬浮窗:横向悬浮窗 手势触发分屏:形成上下分屏 分屏样式切换:支持样式切换 系统识别应用为非横向全屏播放: 同配置为default |
default | 缺省值,参数不配置时默认为default。 建议其他应用类配置。 | 折叠屏手机折叠态 & 手机 手势触发悬浮窗:竖向悬浮窗 手势触发分屏: 形成上下分屏 分屏样式切换:不涉及 折叠屏手机展开态 手势触发悬浮窗:竖向悬浮窗 手势触发分屏:形成左右分屏 分屏样式切换:支持样式切换 |
声明支持分屏
开发者可以通过在module.json5配置文件中 abilities标签 下的supportWindowMode属性增加“split”字段或使用默认值以声明应用支持分屏。
supportWindowMode属性主要标识当前UIAbility所支持的窗口模式,支持的字段及含义如下表所示。
字段 | 说明 |
---|---|
fullscreen | 窗口支持全屏显示。 |
split | 窗口支持分屏显示。 |
floating | 支持悬浮窗显示。 |
应用布局适配智慧多窗的意义
由于应用从全屏进入智慧多窗(悬浮窗/分屏)模式后,窗口尺寸、宽高比例会发生变化,所以需要开发者适配应用窗口在不同尺寸、不同比例下的自适应布局,以确保应用窗口在各种形态下都能呈现出最佳的视觉效果,提供更好的用户体验。
悬浮窗的比例
不同设备支持悬浮窗的比例如下所示:
设备 | 竖向悬浮窗宽高比 | 横向悬浮窗宽高比 |
---|---|---|
手机 | 3:4.575 | 16:9 |
折叠屏手机展开态 | 9:16 | 16:9 |
说明
- 手机:悬浮窗模式下,应用窗口真实宽度为屏幕宽度。竖向时,高度根据宽高比3 : 4.575动态调整;横向时,高度根据宽高比16 : 9动态调整。
- 折叠屏手机展开态:悬浮窗模式下,应用窗口真实宽度为折叠屏手机折叠态时的屏幕宽度。竖向时,高度根据宽高比9 : 16动态调整;横向时,高度根据宽高比16 : 9动态调整。
分屏的比例
目前支持两种分屏样式:“上下分屏”和“左右分屏”。
分屏比例指的是分屏下两应用间尺寸的比例,调整分屏比例会调整应用窗口的大小。
默认形成分屏后分屏比例为1:1,拖动中间的分屏条可以改变分屏比例档位。手机“上下分屏”可调节档位1:2、1:1、2:1,“左右分屏”可调节档位为1:1。手机折叠屏展开态可调节档位只有1:1。
设备 | 默认分屏比例 | 分屏可调节档位 |
---|---|---|
手机 | 1:1 | “上下分屏”: 1:1, 1:2, 2:1 “左右分屏”: 1:1 |
手机折叠屏展开态 | 1:1 | “上下分屏”和 “左右分屏”: 1:1 |
应用布局可以通过自适应布局和响应式布局来更新自身布局,避免出现截断、挤压、堆叠等现象:
- 自适应布局
- 响应式布局
应用布局适配智慧多窗的方案
无论是悬浮窗还是分屏,当应用进入智慧多窗模式时,应用的窗口尺寸发生变化,所以应用需要根据不同的窗口尺寸调整自身布局。
主要可以通过窗口的 on(‘windowSizeChange’) 方法实现对窗口尺寸大小变化的监听。再根据窗口的尺寸变化,更新调整自身应用布局以实现适配。
主要步骤和示例如下:
- 在onWindowStageCreate方法中,获取Window对象。
- 通过getWindowProperties方法返回值中的windowRect获取窗口尺寸,写入AppStorage中用于UI侧窗口尺寸的首次初始化赋值。
- 使用on('windowSizeChange)注册窗口尺寸变化时的监听,并写入AppStorage中供UI侧布局使用。
- UI侧通过@StorageLink绑定窗口尺寸后,AppStorage中属性key值对应的数据一旦改变,UI侧会同步修改。
- @StorageLink装饰的数据本身是状态变量,所以窗口尺寸发生变化时,会引起组件的重新渲染,开发者可以根据最新的窗口尺寸动态调整应用布局。
// EntryAbility.ets
import { UIAbility } from '@kit.AbilityKit';
import { window } from '@kit.ArkUI';
export default class EntryAbility extends UIAbility {
onWindowStageCreate(windowStage: window.WindowStage): void {
console.info('Ability onWindowStageCreate.');
windowStage.getMainWindow().then((windowClass) => {
// 获取窗口尺寸,存入AppStorage
AppStorage.setOrCreate('winWidth', windowClass.getWindowProperties().windowRect.width);
AppStorage.setOrCreate('winHeight', windowClass.getWindowProperties().windowRect.height);
// 监听窗口尺寸变化
windowClass.on('windowSizeChange', (windowSize) => {
AppStorage.setOrCreate('winWidth', windowSize.width);
AppStorage.setOrCreate('winHeight', windowSize.height);
});
});
windowStage.loadContent('pages/Index', (err) => {
if (err.code) {
console.error('Failed to load the content. Cause: ' + JSON.stringify(err));
return;
}
console.info('Succeeded in loading the content.');
});
}
}
// Index.ets
@Entry
@Component
struct Index {
// 初始化参数,这里会初始化为AppStorage中存储的值
@StorageLink('winWidth') winWidth: number = 1260;
@StorageLink('winHeight') winHeight: number = 2224;
aboutToAppear() {
console.info('Current window size. width: ' + this.winWidth + ', height: ' + this.winHeight);
}
build() {
Row() {
// 根据winWidth、winHeight动态调整应用布局
// ...
}
.size({
width: px2vp(this.winWidth),
height: px2vp(this.winHeight)
})
}
}
顶部窗口控制条避让适配智慧多窗
顶部窗口控制条是应用窗口处于智慧多窗模式下,应用顶部的操作横条
顶部窗口控制条示意图如下所示:
顶部横条的避让可通过以下两种方式适配:
- 使用窗口的避让能力:通过 setWindowLayoutFullScreen 设置窗口布局是否为沉浸式布局。
沉浸式布局是指应用布局不避让状态栏、导航栏以及智慧多窗顶部横条,这可能发生组件与顶部横条的重叠,导致文字遮挡、点击事件冲突等情况。非沉浸式布局是指布局避让状态栏、导航栏以及智慧多窗顶部横条,组件不会与其重叠。因此可设置isLayoutFullScreen值为false使窗口的布局为非沉浸式布局。
示例:
// Index.ets
import { BusinessError } from '@kit.BasicServicesKit';
import { window } from '@kit.ArkUI';
@Entry
@Component
struct Index {
@State message: string = '非沉浸式布局';
private windowClass: window.Window | undefined = undefined;
aboutToAppear(): void {
try {
window.getLastWindow(getContext(this), (err: BusinessError, data) => {
const errCode: number = err.code;
if (errCode) {
console.error('Failed to obtain the top window. Cause: ' + JSON.stringify(err));
return;
}
this.windowClass = data;
console.info('Succeeded in obtaining the top window. Data: ' + JSON.stringify(data));
});
} catch (exception) {
console.error('Failed to obtain the top window. Cause: ' + JSON.stringify(exception));
}
}
private setWindowLayoutFullScreen(isLayoutFullScreen: boolean) {
if (!this.windowClass) {
return;
}
try {
this.windowClass.setWindowLayoutFullScreen(isLayoutFullScreen, (err: BusinessError) => {
const errCode: number = err.code;
if (errCode) {
console.error('Failed to set the window layout to full-screen mode. Cause:' + JSON.stringify(err));
return;
}
console.info('Succeeded in setting the window layout to full-screen mode.');
});
} catch (exception) {
console.error('Failed to set the window layout to full-screen mode. Cause:' + JSON.stringify(exception));
}
}
build() {
Stack({ alignContent: Alignment.TopStart }) {
Column() {
Text(this.message)
.fontSize(25)
.fontWeight(FontWeight.Bold)
.margin({
top: '2%',
bottom: '40%'
})
Button() {
Text('设置窗口为沉浸式布局')
.fontSize(18)
.fontWeight(FontWeight.Normal)
}
.type(ButtonType.Normal)
.borderRadius(15)
.margin({ top: 20 })
.stateStyles({
normal: {
.backgroundColor('#ff6b89d4')
},
pressed: {
.backgroundColor('#ffc81f2a')
}
})
.width('60%')
.height('6%')
.onClick(() => {
this.setWindowLayoutFullScreen(true);
this.message = '沉浸式布局';
})
Button() {
Text('设置窗口为非沉浸式布局')
.fontSize(18)
.fontWeight(FontWeight.Normal)
}
.type(ButtonType.Normal)
.borderRadius(15)
.margin({ top: 20 })
.stateStyles({
normal: {
.backgroundColor('#ff6b89d4')
},
pressed: {
.backgroundColor('#ffc81f2a')
}
})
.width('60%')
.height('6%')
.onClick(() => {
this.setWindowLayoutFullScreen(false);
this.message = '非沉浸式布局';
})
}
.width('100%')
}
.backgroundColor('#fceaeaea')
.height('100%')
}
}
- 应用主动避让:应用不使用窗口避让能力(即设置窗口为沉浸式布局),还通过 getWindowAvoidArea 接口可获取屏幕顶部需要规避的矩阵区域topRect,获取到该值后应用可对应做布局避让。同时可通过 on(‘avoidAreaChange’) 监听系统规避区域变化以进行布局的动态调整。
// Index.ets
import { BusinessError } from '@kit.BasicServicesKit';
import { window } from '@kit.ArkUI';
@Entry
@Component
struct Index {
@State topSafeHeight: number = 0;
aboutToAppear(): void {
try {
let windowClass: window.Window | undefined = undefined;
window.getLastWindow(getContext(this), (err: BusinessError, data) => {
const errCode: number = err.code;
if (errCode) {
console.error('Failed to obtain the top window. Cause: ' + JSON.stringify(err));
return;
}
windowClass = data;
windowClass.setWindowLayoutFullScreen(true);
this.topSafeHeight = px2vp(windowClass.getWindowAvoidArea(window.AvoidAreaType.TYPE_SYSTEM).topRect.height);
windowClass.on('avoidAreaChange', (data) => {
if (data.type == window.AvoidAreaType.TYPE_SYSTEM) {
this.topSafeHeight = px2vp(data.area.topRect.height)
}
})
console.info('Succeeded in obtaining the top window. Data: ' + JSON.stringify(data));
});
} catch (exception) {
console.error('Failed to obtain the top window. Cause: ' + JSON.stringify(exception));
}
}
build() {
Stack({ alignContent: Alignment.TopStart }) {
// 顶部避让区域
Row() {
}
.height(this.topSafeHeight)
.width("100%")
// 根据topSafeHeight动态调整应用布局
// ...
}
.height('100%')
}
}
更多推荐
所有评论(0)