1.窗口管理
1.1窗口概述
1.1.1窗口模块的作用
- 展示应用和操作系统的UI界面
- 组织不同窗口之间的显示关系,即维护不同窗口之间的叠加层次和位置属性
- 提供窗口装饰,即窗口标题栏和窗口边框
- 提供窗口切换或者显隐的动效
- 协助系统进行事件分发(触摸事件需要根据窗口的位置)
1.1.2窗口类型
- 系统窗口:指的是完成系统特定功能的窗口。如音量条、壁纸、通知栏等
- 应用窗口
- 应用主窗口:显示应用界面,会在任务管理界面出现
- 应用子窗口:用于显示应用的弹窗、悬浮窗等辅助窗口,生命周期跟随应用主窗口
1.1.3主窗口的生命周期
1.1.3.1生命周期概述
在Stage模型中,一个UIAbility对应一个WindowStage、一个WindowStage对应一个应用主窗口;

由UIAbility通过WindowStage来管理主窗口并维护其生命周期,onWindowStageStage和onWindowStageDestroy即为主窗口的创建和销毁回调
每个UIAbility实例都会与一个WindowStage类实例相绑定,WindowStage类的作用主要是管理应用进程内的窗口,这个类包含一个主窗口。也就是说UIAbility实例通过WindowStage持有了一个主窗口,该主窗口为ArkUI提供了绘制区域
1.1.3.2主窗口的生命周期状态
窗口在进入前台、前后台切换以及退至后台时,会触发窗口相应的生命周期状态变化
- shown:应用从后台切换至前台时触发
- resumed:窗口进入可交互状态,窗口进入前台、分屏调整完毕都会触发
- paused:窗口进入不可交互状态。窗口在前台但是不可交互时触发,例如正在调整分屏的范围时
- hidden:窗口从前台切换至后台时触发
1.1.3.3监听生命周期变化
可以在onWindowStageCreate回调中使用on方法来监听生命周期变化
1.2管理应用窗口
1.2.1使用场景
- 设置应用主窗口属性以及目标页面
- 设置应用子窗口属性以及目标页面
- 设置窗口沉浸式
- 设置悬浮窗
- 监听窗口不可交互和可交互事件
1.2.2接口说明
- getMainWindow:获取WindowStage实例下的主窗口
- loadContent:为当前WindowStage的主窗口加载具体的页面
- createSubWindow:创建子窗口
- on:监听WindowStage生命周期的变化
- createWindow:创建子窗口或者系统窗口
- ...
1.2.3配置应用主窗口
- 获取应用主窗口
typescript
windowStage.getMainWindow((err: BusinessError, data) => {})
- 设置主窗口属性
- 通过loadContent接口加载主窗口的目标页面
1.2.4配置应用子窗口
- 通过createSubWindow创建应用子窗口
typescript
windowStage_.createSubWindow('mySubWindow',(err: BusinessError, data)=>{
if(err.code) {
console.log('[test] 创建子窗口错误 ' + err.message+ err.code)
return;
}
//返回的data即为Window对象
}
- 设置子窗口属性
- 子窗口创建成功之后,可以改变其位置、大小,设置窗口背景色、亮度等
typescript
sub_windowClass = data;
//移动窗口位置
sub_windowClass.moveWindowTo(300,300);
//调整窗口大小
sub_windowClass.resize(500,500)
- 通过setUIContent和showWindow接口加载显示子窗口的具体内容
typescript
sub_windowClass.setUIContent('pages/subWindow',(err: BusinessError) =>{
if(err.code){
console.log('[test]子窗口加载目标页面错误 '+ err.code)
return
}
if(!sub_windowClass){
return;
}
//显示子窗口
sub_windowClass.showWindow((err:BusinessError) =>{
if(err.code){
console.log('[test]子窗口显示错误')
return;
}
})
})
需要注意的是这里需要以err对象的错误码作为判断依据而不能使用err对象本身
- 不再需要子窗口时,通过destoryWindow接口销毁子窗口
- 运行结果如下
1.2.5设置窗口沉浸式
在看视频,玩游戏时,可以通过设置窗口的沉浸式能力,即隐藏状态栏、导航栏等不必要的系统窗口来提高用户体验
- 通过getMainWindow获取主窗口
- 实现沉浸式有以下两种方式
- 方式一:调用setWindowSystemBarEnable接口,设置导航栏、状态栏不显示来达到沉浸式效果
- 方式二:调用setWindowLayoutFullScreen接口,设置应用主窗口为全屏布局;随后调用setWindowSystemBarPropertiew接口来设置导航栏、状态栏的透明度、背景/文字颜色等属性,使其与主窗口显示一致来达到沉浸式效果
- 通过loadContent接口来加载窗口的内容
1.2.6设置悬浮窗(受限开放)
- 通过createWindow接口创建悬浮窗类型的窗口
- 设置悬浮窗的位置、大小等属性
- 通过setUIContent和showWindow接口显示悬浮窗的具体内容
- 不再需要悬浮窗时,使用destroyWindow接口销毁悬浮窗
1.2.7监听窗口不可交互与可交互事件
在创建WindowStage对象后可通过监听'windowStageEvent'事件类型,来监听到窗口的生命周期变化
typescript
windowStage.on('windowStageEvent', (data) => {
// 根据事件状态类型选择进行相应的处理
if (data === window.WindowStageEventType.SHOWN) {
console.info('current window stage event is SHOWN');
// 应用进入前台,默认为可交互状态
// ...
} else if (data === window.WindowStageEventType.HIDDEN) {
console.info('current window stage event is HIDDEN');
// 应用进入后台,默认为不可交互状态
// ...
} else if (data === window.WindowStageEventType.PAUSED) {
console.info('current window stage event is PAUSED');
// 前台应用进入多任务,转为不可交互状态
// ...
} else if (data === window.WindowStageEventType.RESUMED) {
console.info('current window stage event is RESUMED');
// 进入多任务后又继续返回前台时,恢复可交互状态
// ...
}
// ...
});
1.3module.json5的metadata标签
1.3.1概述
metadata标签用于标识HAP的自定义信息,包含name、value、resoures三个子标签
- name:标识数据项的名称
- value:标识数据项的值
- resource:标识用户自定义数据的资源索引
2.3.2使用示例
- 使用metadata标签配置主窗口的默认大小和位置。其中name取值及其对应含义如下
- name为ohos.ability.window.height表示主窗口的默认高度
- name为ohos.ability.window.width表示主窗口的默认宽度
- ...
- 使用metadata标签配置是否移除应用启动页
- name为ohos.remove.starting.window,value取值为true表示移除启动页,取值为false表示不移除,默认为false
- 配置主窗口启动时是否以最大化状态显示(仅在PC/2in1设备上生效)
- name为ohos.ability.window.isMaximize,value取值为true表示最大化启动、取值为false表示不以最大化状态启动
1.4在应用中使用画中画功能
1.4.1概述
1.4.1.1接口说明
-
isPiPEnable:判断当前系统是否支持画中画功能
-
create:创建画中画控制器(使用XComponent)
typescriptcreate(config: PiPConfiguration): Promise<PiPController>
- config:创建画中画控制器的参数
- context:一个BaseContext类型的对象
- componentController:表示原始的XComponent控制器
- navigationId:当前页面的导航id,当使用Navigation管理页面路由时,需要设置Navigation的id属性,并将该id设置到该字段,确保在还原场景时能够从画中画场景恢复到原窗口
- templateType:模版类型,用于区分视频播放、视频通话或视频会议,默认为视频播放
- controlGroups:画中画的可选控件列表
- config:创建画中画控制器的参数
-
create:创建画中画控制器(使用typeNode)
typescriptcreate(config: PiPConfiguration, contentNode: typeNode.XComponent): Promise<PiPController>
- config:画中画控制器参数
- contentNode:用于渲染画中画窗口中的内容,是一个XComponent类型的FrameNode节点
-
startPiP:启动画中画
-
stopPiP:停止画中画
-
setAutoStartEnable:设置是否在应用退至后台时自动启动画中画
-
...
1.4.1.2画中画的交互方式
- 单击画中画窗口:若控制层未显示则显示,三秒后隐藏;若已显示则隐藏
- 双击画中画窗口:放大或缩小画中画窗口
- 拖动画中画窗口
- 拖拽缩放画中画窗口大小
- 拖动删除画中画窗口
1.4.1.3配置画中画控制层可选控件
使用create创建画中画时,可通过在PiPConfiguration中新增PiPControlGroup类型的数组配置当前画中画窗口中的控件
- 视频播放场景通过配置VideoPlayControlGroup类
- 视频通话场景通过配置VideoPlayControlGroup类
- ...
1.4.1.4在画中画上方展示自定义UI
在使用create创建画中画时,可通过在PiPConfiguration中传入customUIController来显示自定义UI
1.4.1.5实现方式
- 使用XComponent实现画中画功能:适用于应用通过Navigation管理页面或者Ability中只有一个页面的情况,这种实现方式无需应用管理页面
- 使用typeNode实现画中画:使用于所有场景,灵活度较高,但是需要应用自行管理页面
- 使用NDK实现画中画:适用依赖NDK接口开发的应用,需要应用自行管理页面 NDK也就是Native Development Kit 原生开发工具包,即使用C/C++编写的高性能工具库
在HarmonyOS开发中还有响应的SDK 即Software Development Kit软件开发工具包
1.4.2使用XComponent实现画中画
- 创建画中画控制器,并注册相应监听事件
typescript
startPip(){
if(PiPWindow.isPiPEnabled() == false){
return;
}
let config: PiPWindow.PiPConfiguration = {
//表示上下文环境 BaseContext类型
context: this.getUIContext().getHostContext() as Context,
//原始XComponent控制器
componentController: this.mXComponentController,
//当前page导航id,单页面时无需设置
navigationId:'',
//模版类型,默认我视频播放
templateType: PiPWindow.PiPTemplateType.VIDEO_PLAY,
//原始内容宽度px,用于确定画中画窗口比例,使用typeNode方式创建时默认为1920,否则默认为XComponent组件宽度
contentWidth: 1920,
//原始内容高度
contentHeight:1080,
//画中画控制面板的可选控件组列表
controlGroups: [PiPWindow.VideoPlayControlGroup.VIDEO_PREVIOUS_NEXT],
//画中画内容上方的自定义组件
customUIController: undefined
}
PiPWindow.create(config)
.then((controller: PiPWindow.PiPController) => {
//初始化画中画控制器
this.pipController = controller
this.initPipController()
})
}
initPipController(){
if(this.pipController == undefined){
return
}
//设置是否需要在应用返回桌面时自动启动画中画,默认为false
this.pipController.setAutoStartEnabled(true)
//注册生命周期事件回调和控制事件回调,为便于演示此处不给出具体实现
this.pipController.on('stateChange',(state: PiPWindow.PiPState, reason: string) => {
})
this.pipController.on('controlPanelActionEvent',(event: PiPWindow.PiPActionEventType,status?: number) => {
})
}
- 通过startPiP接口启动画中画
typescript
this.pipController.startPiP()
.then(()=>{
console.log('[test]pip启动成功')
})
.catch(()=>{
console.log('[test]pip启动失败')
})
- 画中画媒体源更新后(切换视频),通过控制器的updateContentSize接口,根据新媒体源的尺寸信息来调整画中画的窗口比例
- 不需要时通过控制器的stopPiP接口关闭画中画
1.4.3使用typeNode实现画中画
1.4.3.1使用自由节点实现画中画
自由节点也就是不将节点添加到组件中
- 创建控制器,注册生命周期回调以及控制事件回调
- 通过主窗口UIContext类创建typeNode节点
typescript
// 创建typeNode节点
makeTypeNode(ctx: UIContext) {
if (this.xComponent === null || this.xComponent === undefined) {
// 创建XComponent类型的typeNode
this.xComponent = typeNode.createNode(ctx, "XComponent", {
type: XComponentType.SURFACE, // 类型设置为SURFACE
controller: PipManager.getInstance().getXComponentController(), // 设置XComponentController
});
}
}
windowStage.getMainWindow().then((window) => {
let ctx = window.getUIContext();
AppStorage.setOrCreate('UIContext', ctx);
// 通过主窗口UIContext,调用make方法,创建typeNode节点
PipManager.getInstance().makeTypeNode(ctx);
})
- 通过相对应的create接口来创建控制器实例
- ...
- 通过startPiP接口启动画中画
- 根据媒体源变化调整窗口大小
- 不需要时关闭画中画
1.4.3.2使用Navigation导航时实现画中画
- 与使用自由节点不同的是需要创建一个自定义NodeController类
- 通过startPiP启动画中画,并且在画中画的aboutToStart生命周期中将typeNode节点从布局中移除
也可以在aboutToStart中调用路由栈的pop方法返回至上级页面,需要注意如果进行了pop操作,还需要在aboutToRestore生命周期中重新跳转至目标页面 - 根据媒体源变化调整窗口大小
- 不需要时关闭画中画,同时在aboutToStop生命周期中将typeNode节点重新添加至布局中