应用窗口管理
帝心:全网首发HarmonyOS4.0教程(哔哩哔哩)创作者。致力于推广鸿蒙教程开发。
API9 版本,应对金砖赛
设置应用主窗口
Stage模型下,应用主窗口由UIAbility创建并维护生命周期。在UIAbility的onWindowStageCreate回调中,通过WindowStage获取应用主窗口,即可对其进行属性设置等操作。还可以在应用配置文件中设置应用主窗口的属性,如最大窗口宽度maxWindowWidth等,详见module.json5配置文件。
开发步骤
- 获取应用主窗口。
通过getMainWindow接口获取应用主窗口。
- 设置主窗口属性。
可设置主窗口的背景色、亮度值、是否可触等多个属性,开发者可根据需要选择对应的接口。也可设置沉浸式效果,调用setWindowSystemBarEnable接口,设置导航栏、状态栏不显示,从而达到沉浸式效果。
- 为主窗口加载对应的目标页面。
通过loadContent接口加载主窗口的目标页面。
onWindowStageCreate(windowStage: window.WindowStage): void {
// 1. 获取应用主窗口对象
let windowClass:window.Window = windowStage.getMainWindowSync()
// 2.设置主窗口属性。
// 2.1 设置 是否可触摸 属性
let isTouchable = true;
windowClass.setWindowTouchable(isTouchable)
// 2.2 设置沉浸式 实现沉浸式效果:设置导航栏、状态栏不显示。
let names = []
windowClass.setWindowSystemBarEnable(names)
// 2.3 设置全屏效果
windowClass.setWindowLayoutFullScreen(true)
// 3.为主窗口加载对应的目标页面。
windowStage.loadContent('pages/Index', (err, data) => {
if (err.code) {
hilog.error(0x0000, 'testTag', 'Failed to load the content. Cause: %{public}s', JSON.stringify(err) ?? '');
return;
}
hilog.info(0x0000, 'testTag', 'Succeeded in loading the content. Data: %{public}s', JSON.stringify(data) ?? '');
});
}
设置应用子窗口
开发者可以按需创建应用子窗口,如弹窗等,并对其进行属性设置等操作。
开发步骤
- 创建应用子窗口。
通过createSubWindow接口创建应用子窗口。
- 设置子窗口属性。
子窗口创建成功后,可以改变其大小、位置等,还可以根据应用需要设置窗口背景色、亮度等属性。
- 加载显示子窗口的具体内容。
通过setUIContent和showWindow接口加载显示子窗口的具体内容。
- 销毁子窗口。
当不再需要某些子窗口时,可根据具体实现逻辑,使用destroyWindow接口销毁子窗口。
通过应用入口程序创建子窗口
- 全局声明
WindowStage对象和子窗口对象
该方法中需要使用window.WindowStage对象调用createSubWindow来创建子窗口。
自定义方法中没有window.WindowStage,可用,所以记得在全局src/main/ets/entryability/EntryAbility.ts 声明该对象,在生命周期方法onWindowStageCreate进行初始化。
在该方法中创建的子窗口,需要在指定时机进行销毁,例如在生命周期方法destroySubWindow方法中调用子窗口的destroyWindow方法进行销毁。同理,需要全局声明。
// 声明一个全局的 window.WindowStage 暂时未null 用到的时候有值即可
let windowStage_: window.WindowStage = null;
let sub_windowClass = null;
- 设计显示窗口的方法
该方法加载pages/Dxin 页面,自行创建。
// 显示子窗口的方法
async showSubWindow() {
// 1.创建应用子窗口。 使用 window.WindowStage 对象调用createSubWindow 但是当前方法没有 window.WindowStage 所以需要顶部声明全局对象 并在 onWindowStageCreate时初始化
// 创建一个名称为 Dxin 的子窗口 异步任务
let sub_windowClass = await windowStage_.createSubWindow('Dxin')
// 2.子窗口创建成功后,设置子窗口的位置、大小及相关属性等。
sub_windowClass.moveWindowTo(300, 30)
sub_windowClass.resize(500, 500)
// 3.为子窗口加载对应的目标页面。
sub_windowClass.setUIContent('pages/Dxin')
sub_windowClass.showWindow()
}
- 在生命周期方法
onWindowStageCreate中调用窗口方法
onWindowStageCreate(windowStage: window.WindowStage): void {
// 初始化全局对象 windowStage_
windowStage_ = windowStage
// 开发者可以在适当的时机,如主窗口上按钮点击事件等,创建子窗口。并不一定需要在onWindowStageCreate调用,这里仅作展示
this.showSubWindow()
}
- 设计销毁子窗口方法
// 4.销毁子窗口。当不再需要子窗口时,可根据具体实现逻辑,使用destroy对其进行销毁。
destroySubWindow() {
sub_windowClass.destroyWindow((err) => {
if (err.code) {
console.error('Dxin Failed to destroy the window. Cause: ' + JSON.stringify(err));
return;
}
console.info('Dxin Succeeded in destroying the window.');
});
}
- 在生命周期方法
onWindowStageDestroy中销毁窗口
onWindowStageDestroy(): void {
// 开发者可以在适当的时机,如子窗口上点击关闭按钮等,销毁子窗口。并不一定需要在onWindowStageDestroy调用,这里仅作展示
this.destroySubWindow();
}
通过页面创建子窗口
关键点。封装一个可以暴露使用的工具类。
- 具备显示窗口的方法
- 销毁窗口的方法
困难点:如何在该工具类中拿到创建小窗的那个舞台对象window.WindowStage
- 该工具类设计一个属性,类似于前文的全局对象。
let windowStage_: window.WindowStage = null;
-
在入口文件中导入该工具类并初始化工具类中的属性
-
入口文件中合适时机(生命周期方法
onWindowStageCreate)初始化工具类的属性。 -
封装工具类
该工具类需要在入口类进行调用并初始化其属性值。所以要设计成ts文件。因为ets文件无法在入口文件中进行导入
import window from '@ohos.window'
class MyWindow{
// 关键的舞台对象属性。入口文件给我初始化。 所以在入口之后,到处可用
windowStage:window.WindowStage = null
subWindow:window.Window = null
async show(){
// 1.创建应用子窗口。 使用 window.WindowStage 对象调用createSubWindow 但是当前方法没有 window.WindowStage 所以需要顶部声明全局对象 并在 onWindowStageCreate时初始化
// 创建一个名称为 Dxin 的子窗口 异步任务
this.subWindow = await this.windowStage.createSubWindow('Dxin')
// 2.子窗口创建成功后,设置子窗口的位置、大小及相关属性等。
this.subWindow.moveWindowTo(300, 30)
this.subWindow.resize(500, 500)
// 3.为子窗口加载对应的目标页面。
this.subWindow.setUIContent('pages/Dxin')
this.subWindow.showWindow()
}
destroy(){
// 销毁子窗口
this.subWindow.destroyWindow((err) => {
if (err.code) {
console.error('Dxin => 销毁小窗口时候失败了')
return
}
console.info('Dxin => 成功销毁小窗')
})
}
}
export default new MyWindow()
- 在入口类中导入并初始化该工具类相关属性
如下代码入口页面为Index.ets页面。我在index.ets跳转到了本案例页面ShowSmallWindow.ets。你也可以直接显示案例页面,自行设计。
// 导入显示小窗的工具类
import myWindow from '../common/util/MyWindow'
....
onWindowStageCreate(windowStage: window.WindowStage): void {
// 演示页面创建小窗效果
myWindow.windowStage = windowStage
// 3.为主窗口加载对应的目标页面。
windowStage.loadContent('pages/Index', (err, data) => {
if (err.code) {
hilog.error(0x0000, 'testTag', 'Failed to load the content. Cause: %{public}s', JSON.stringify(err) ?? '');
return;
}
hilog.info(0x0000, 'testTag', 'Succeeded in loading the content. Data: %{public}s', JSON.stringify(data) ?? '');
});
}
- 在案例页面中调用工具类对应方法显示和关闭小窗
// 导入显示小窗的工具类
import myWindow from '../common/util/MyWindow'
@Entry
@Component
struct ShowSmallWindow {
@State message: string = 'Hello World'
build() {
Column({ space: 30 }) {
Button('点击出现小窗')
.onClick(() => {
myWindow.show()
})
Button('点击关闭小窗')
.onClick(() => {
myWindow.destroy()
})
}
.width('100%')
.height('100%')
.backgroundColor($r('app.color.theme_color'))
.justifyContent(FlexAlign.Center)
}
}
- 也可以在小窗页面设计关闭按钮,调用工具类关闭自己
// 用来作为窗口呈现的页面
// 导入显示小窗的工具类
import myWindow from '../common/util/MyWindow'
@Entry
@Component
struct Dxin {
@State message: string = '帝心'
build() {
Column({ space: 30 }) {
Button('X')
.fontSize(50)
.fontColor('green')
.onClick(() => {
myWindow.destroy()
})
Text(this.message)
.fontSize(30)
.fontColor('green')
}
.width('100%')
.height('100%')
.backgroundColor('red')
.justifyContent(FlexAlign.Center)
}
}
设置悬浮窗
悬浮窗可以在已有的任务基础上,创建一个始终在前台显示的窗口。即使创建悬浮窗的任务退至后台,悬浮窗仍然可以在前台显示。通常悬浮窗位于所有应用窗口之上;开发者可以创建悬浮窗,并对悬浮窗进行属性设置等操作。
开发步骤
**前提条件:**创建WindowType.TYPE_FLOAT即悬浮窗类型的窗口,需要申请ohos.permission.SYSTEM_FLOAT_WINDOW权限,配置方式请参见配置文件权限声明。
开发悬浮窗使用的ohos.permission.SYSTEM_FLOAT_WINDOW权限,要求ACL使能,需要向官方发送邮箱申请。遂,本示例无法演示成功。g。
- 创建悬浮窗。
通过window.createWindow接口创建悬浮窗类型的窗口。
- 对悬浮窗进行属性设置等操作。
悬浮窗窗口创建成功后,可以改变其大小、位置等,还可以根据应用需要设置悬浮窗背景色、亮度等属性。
- 加载显示悬浮窗的具体内容。
通过setUIContent和showWindow接口加载显示悬浮窗的具体内容。
- 销毁悬浮窗。
当不再需要悬浮窗时,可根据具体实现逻辑,使用destroyWindow接口销毁悬浮窗。
示例
ⅰ. 申请浮窗权限
"requestPermissions": [
{"name": "ohos.permission.SYSTEM_FLOAT_WINDOW"}
]
ⅱ. 编写工具类
重点在于:上下文对象。
// 创建悬浮窗口的工具类
import common from '@ohos.app.ability.common'
import window from '@ohos.window'
// 关键点:1 需要上下文对象 2 需要申请权限 ohos.permission.SYSTEM_FLOAT_WINDOW
class MyFloatWindow {
// 上下文对象
context: common.UIAbilityContext = null
subWindow: window.Window = null
// 显示 悬浮窗 的方法
async show() {
console.error('Dxin => show 111')
// 1:创建一个子窗口 是悬浮类型
this.subWindow = await window.createWindow({
name: "DxinFloat",
windowType: window.WindowType.TYPE_FLOAT,
ctx: this.context
})
console.error('Dxin => show 222')
// 2:移动位置 设置大小
this.subWindow.moveWindowTo(300, 300)
this.subWindow.resize(600, 600)
// 3.为子窗口加载对应的目标页面。
await this.subWindow.setUIContent('pages/DxinFloat')
this.subWindow.showWindow()
}
// 销毁子窗口
destroy() {
this.subWindow.destroyWindow((err) => {
if (err.code) {
console.error('Dxin => 销毁 悬浮 窗口时候失败了')
return
}
console.info('Dxin => 成功销毁 悬浮 小窗')
})
}
}
export default new MyFloatWindow()
ⅲ. 在入口类初始化工具类中上下文对象
onWindowStageCreate(windowStage: window.WindowStage): void {
// 演示悬浮窗 效果 需要初始化上下文对象
MyFloatWindow.context = this.context
// 3.为主窗口加载对应的目标页面。
windowStage.loadContent('pages/Index', (err, data) => {
if (err.code) {
hilog.error(0x0000, 'testTag', 'Failed to load the content. Cause: %{public}s', JSON.stringify(err) ?? '');
return;
}
hilog.info(0x0000, 'testTag', 'Succeeded in loading the content. Data: %{public}s', JSON.stringify(data) ?? '');
});
}
ⅳ. 编写测试页面,调用工具类拉起浮窗
// 导入显示 悬浮 窗口的工具类
import MyFloatWindow from '../common/util/MyFloatWindow'
@Entry
@Component
struct ShowFloatWindow {
build() {
Column({ space: 30 }) {
Text('ShowFloatWindow')
.fontSize(30)
Button('点击出现 悬浮 窗口')
.onClick(() => {
MyFloatWindow.show()
})
Button('点击关闭小窗')
.onClick(() => {
MyFloatWindow.destroy()
})
}
.width('100%')
.height('100%')
.backgroundColor($r('app.color.theme_color'))
.justifyContent(FlexAlign.Center)
}
}
ⅴ. 浮窗页面编写
// 导入显示 悬浮 窗口的工具类
import MyFloatWindow from '../common/util/MyFloatWindow'
@Entry
@Component
struct DxinFloat {
@State message: string = '帝心悬浮窗口'
build() {
Column({ space: 30 }) {
Button('X')
.fontSize(50)
.fontColor('green')
.onClick(() => {
MyFloatWindow.destroy()
})
Text(this.message)
.fontSize(30)
.fontColor('green')
}
.width('100%')
.height('100%')
.backgroundColor('red')
.justifyContent(FlexAlign.Center)
}
}
ⅵ. 测试效果
因为ACL使能问题,需要发送邮箱开通白名单。遂无法测试效果。
NEXT版本-应用窗口管理
1. 设置应用页面沉浸式效果
async onWindowStageCreate(windowStage: window.WindowStage): Promise<void> {
//1 获取主窗口
let windowClass = await windowStage.getMainWindow()
// 2. 沉浸式设置
let names: Array<'status' | 'navigation'> = [];
windowClass.setWindowSystemBarEnable(names)
windowStage.loadContent('pages/Index', (err) => {
// 创建指定页面后可以干嘛
});
}
2. 设置应用页面小窗
官网文档使用在入口类中启动小窗
本案例封装成工具类,在所需页面处通过事件启动小窗,更加灵活.
a. 工具类
import { window } from '@kit.ArkUI';
class MySubWindow{
windowStage_: window.WindowStage | null = null;
// 小窗口对象
sub_windowClass: window.Window | null = null;
// 创建小窗的方法
async showSubWindow() {
// 1.创建应用子窗口
if (this.windowStage_ == null) {
console.error('dxin => Failed to create the subwindow. Cause: windowStage_ is null');
} else {
this.sub_windowClass = await this.windowStage_.createSubWindow("Dxin")
// 2 设置小窗 位置
this.sub_windowClass.moveWindowTo(440, 550)
this.sub_windowClass.resize(800, 1500)
// 窗口里面的有内容
this.sub_windowClass.setUIContent("pages/layout/SwiperLayout", () => {
// 显示出来
(this.sub_windowClass as window.Window).showWindow()
})
}
}
destroySubWindow() {
// 4.销毁子窗口。当不再需要子窗口时,可根据具体实现逻辑,使用destroy对其进行销毁。
(this.sub_windowClass as window.Window).destroyWindow();
}
}
export default new MySubWindow()
b. 在入口类中初始化工具类的windowStage_
async onWindowStageCreate(windowStage: window.WindowStage): Promise<void> {
//1 获取主窗口
let windowClass = await windowStage.getMainWindow()
// 2. 沉浸式设置
let names: Array<'status' | 'navigation'> = [];
windowClass.setWindowSystemBarEnable(names)
// 工具类中的那个主舞台对象 要被真正的主舞台(入口类的)对象赋值
mySubWindow.windowStage_ = windowStage
windowStage.loadContent('pages/Index', (err) => {
// 创建指定页面后可以干嘛
});
}
c. 在页面中调用工具类开启小窗
// 导入小窗工具类
import mySubWindow from "../../common/MySubWindow"
...
Button("点我显示小窗")
.onClick(() => {
mySubWindow.showSubWindow()
})
3. 工具类优化方案
// 创建小窗的方法
async showSubWindow() {
// 1.创建应用子窗口
if (this.windowStage_ == null) {
console.error('dxin => Failed to create the subwindow. Cause: windowStage_ is null');
} else {
this.sub_windowClass = await this.windowStage_.createSubWindow("Dxin")
// 2 设置小窗 位置
this.sub_windowClass.moveWindowTo(440, 550)
this.sub_windowClass.resize(800, 1500)
// 窗口里面的有内容
this.sub_windowClass.setUIContent("pages/layout/SwiperLayout", () => {
// 显示出来
(this.sub_windowClass as window.Window).showWindow()
})
}
如上方法可以开启一个固定尺寸(800,1500)的小窗.
该小窗的位置固定在(440,550)坐标处.
该小窗的内容固定为指定页面pages/layout/SwiperLayout
我们的小窗工具类应满足灵活创建小窗的能力.所以该方法应设计为通过参数传递来优化固定值.
- 可以通过传递多个参数的方式,在调用处按照参数列表进行带参调用
- 也可以设计为一个对象类型的参数,该对象携带能够灵活配置小窗效果的数据
- 如果看到这里你还在等我把优化后的代码贴出来,说明你并听不懂上面两句话,先去打基础吧