HarmonyOS NEXT-应用窗口管理

249 阅读8分钟

应用窗口管理

帝心:全网首发HarmonyOS4.0教程(哔哩哔哩)创作者。致力于推广鸿蒙教程开发。

  1. 个人网站-鸿蒙学院
  2. 语雀笔记-编程视界
  3. 微信公众号:不多讲故事

API9 版本,应对金砖赛

设置应用主窗口

Stage模型下,应用主窗口由UIAbility创建并维护生命周期。在UIAbilityonWindowStageCreate回调中,通过WindowStage获取应用主窗口,即可对其进行属性设置等操作。还可以在应用配置文件中设置应用主窗口的属性,如最大窗口宽度maxWindowWidth等,详见module.json5配置文件

开发步骤

  1. 获取应用主窗口。

通过getMainWindow接口获取应用主窗口。

  1. 设置主窗口属性。

可设置主窗口的背景色、亮度值、是否可触等多个属性,开发者可根据需要选择对应的接口。也可设置沉浸式效果,调用setWindowSystemBarEnable接口,设置导航栏、状态栏不显示,从而达到沉浸式效果。

  1. 为主窗口加载对应的目标页面。

通过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) ?? '');
    });
  }


设置应用子窗口

开发者可以按需创建应用子窗口,如弹窗等,并对其进行属性设置等操作。

开发步骤

  1. 创建应用子窗口。

通过createSubWindow接口创建应用子窗口。

  1. 设置子窗口属性。

子窗口创建成功后,可以改变其大小、位置等,还可以根据应用需要设置窗口背景色、亮度等属性。

  1. 加载显示子窗口的具体内容。

通过setUIContentshowWindow接口加载显示子窗口的具体内容。

  1. 销毁子窗口。

当不再需要某些子窗口时,可根据具体实现逻辑,使用destroyWindow接口销毁子窗口。


通过应用入口程序创建子窗口

  1. 全局声明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;
  1. 设计显示窗口的方法

该方法加载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()
  }
  1. 在生命周期方法onWindowStageCreate中调用窗口方法
  onWindowStageCreate(windowStage: window.WindowStage): void {
    // 初始化全局对象 windowStage_
    windowStage_ = windowStage
    // 开发者可以在适当的时机,如主窗口上按钮点击事件等,创建子窗口。并不一定需要在onWindowStageCreate调用,这里仅作展示
    this.showSubWindow()
  }
  1. 设计销毁子窗口方法
  // 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.');
    });
  }
  1. 在生命周期方法onWindowStageDestroy中销毁窗口
  onWindowStageDestroy(): void {
    // 开发者可以在适当的时机,如子窗口上点击关闭按钮等,销毁子窗口。并不一定需要在onWindowStageDestroy调用,这里仅作展示
    this.destroySubWindow();
  }

通过页面创建子窗口

关键点。封装一个可以暴露使用的工具类。

  1. 具备显示窗口的方法
  2. 销毁窗口的方法

困难点:如何在该工具类中拿到创建小窗的那个舞台对象window.WindowStage

  1. 该工具类设计一个属性,类似于前文的全局对象。
let windowStage_: window.WindowStage = null;
  1. 在入口文件中导入该工具类并初始化工具类中的属性

  2. 入口文件中合适时机(生命周期方法onWindowStageCreate)初始化工具类的属性。

  3. 封装工具类

该工具类需要在入口类进行调用并初始化其属性值。所以要设计成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()
  1. 在入口类中导入并初始化该工具类相关属性

如下代码入口页面为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) ?? '');
    });
  }
  1. 在案例页面中调用工具类对应方法显示和关闭小窗
// 导入显示小窗的工具类
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)
  }
}
  1. 也可以在小窗页面设计关闭按钮,调用工具类关闭自己
// 用来作为窗口呈现的页面

// 导入显示小窗的工具类
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。

  1. 创建悬浮窗。

通过window.createWindow接口创建悬浮窗类型的窗口。

  1. 对悬浮窗进行属性设置等操作。

悬浮窗窗口创建成功后,可以改变其大小、位置等,还可以根据应用需要设置悬浮窗背景色、亮度等属性。

  1. 加载显示悬浮窗的具体内容。

通过setUIContentshowWindow接口加载显示悬浮窗的具体内容。

  1. 销毁悬浮窗。

当不再需要悬浮窗时,可根据具体实现逻辑,使用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

我们的小窗工具类应满足灵活创建小窗的能力.所以该方法应设计为通过参数传递来优化固定值.

  • 可以通过传递多个参数的方式,在调用处按照参数列表进行带参调用
  • 也可以设计为一个对象类型的参数,该对象携带能够灵活配置小窗效果的数据
  • 如果看到这里你还在等我把优化后的代码贴出来,说明你并听不懂上面两句话,先去打基础吧