HarmonyOS:应用声明支持智慧多窗

50 阅读6分钟

一、前言

当应用需要智慧多窗的能力时,可以通过在module.json5配置文件中对应标签添加相关字段声明支持。

二、声明支持悬浮窗

开发者可以通过在module.json5配置文件中abilities标签下的supportWindowMode属性增加“floating”字段或使用缺省值以声明应用支持悬浮窗。
说明 supportWindowMode缺省值为["fullscreen", "split", "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缺省值为["fullscreen", "split", "floating"]。

supportWindowMode属性主要标识当前UIAbility所支持的窗口模式,支持的字段及含义如下表所示。

字段说明
fullscreen窗口支持全屏显示。
split窗口支持分屏显示。
floating窗口支持悬浮窗显示。

四、应用内分屏

应用内分屏功能允许声明支持分屏的应用在全屏显示模式下,通过调用startAbility方法启动UIAbility并形成分屏。该功能能够增强应用的多任务处理能力,提升用户的操作体验。
此处以点击按钮启动分屏为例,主要步骤和示例如下所示:

  1. 在应用中获取UIAbilityContext 对象,这是启动分屏所必需的上下文对象,用于后续调用startAbility接口。
let context = getContext(this) as common.UIAbilityContext;

2. 调用startAbility接口启动UIAbility,形成分屏。调用startAbility接口时,设置StartOptions对象,需要指定窗口模式windowMode(需设置为WINDOW_MODE_SPLIT_PRIMARY或者WINDOW_MODE_SPLIT_SECONDARY),并可根据需要设置其他StartOptions属性或startAbility参数,如Want对象。'

WindowMode

支持设备:Phone | PC/2in1 | Tablet | Wearable
启动Ability时的窗口模式,类型为枚举。可配合startAbility使用,指定启动Ability的窗口模式。

系统能力:SystemCapability.Ability.AbilityRuntime.Core

名称说明
WINDOW_MODE_FULLSCREEN1全屏模式。仅在2in1和tablet设备上生效。
WINDOW_MODE_SPLIT_PRIMARY100支持应用内拉起Ability时设置为分屏,左侧分屏。仅在折叠屏和tablet设备上生效
WINDOW_MODE_SPLIT_SECONDARY101支持应用内拉起Ability时设置为分屏,右侧分屏。仅在折叠屏和tablet设备上生效
// 创建StartOptions并设置为主窗口模式
let option: StartOptions = { windowMode: AbilityConstant.WindowMode.WINDOW_MODE_SPLIT_PRIMARY }; 
let want: Want = { bundleName: 'com.example.startsplitdemo', abilityName: 'EntryAbility1', moduleName: '' };
context.startAbility(want, option);

3. 若继续执行上述步骤,可继续启动其他UIAbility窗口,呈现左右分屏或替换一侧的分屏窗口。

五、示例

示例效果图
在这里插入图片描述

声明Ability支持分屏
在这里插入图片描述

TestWindowMode.ets示例代码

import { AbilityConstant, common, StartOptions, Want } from '@kit.AbilityKit';
import { hilog } from '@kit.PerformanceAnalysisKit';

@Entry
@Component
struct TestWindowMode {
  @State name: string = '';
  @State message: string = '应用声明支持智慧多窗';
  private appBundleName = ''
  private mContext: common.UIAbilityContext | undefined = undefined

  aboutToAppear(): void {
    this.mContext = getContext(this) as common.UIAbilityContext;
    this.name = this.mContext.abilityInfo.name
    this.appBundleName = this.mContext.abilityInfo.bundleName
    console.log("appBundleName: ", this.appBundleName)
  }

  build() {
    Column({ space: 10 }) {
      Text(this.message)
        .id('TestWindowModeHelloWorld')
        .fontSize(20)
        .fontWeight(FontWeight.Bold)
        .margin({ top: 20 })

      Button('启动应用内分屏')
        .fontColor(Color.Black)
        .fontWeight(FontWeight.Medium)
        .onClick(() => {
          // let context = getContext(this) as common.UIAbilityContext;
          let want: Want = { bundleName: this.appBundleName, abilityName: 'WindowMode1Ability', moduleName: '' };
          // 创建StartOptions并设置窗口模式为分屏模式,左侧分屏 , 目前 仅在折叠屏和tablet设备上生效
          let option: StartOptions = { windowMode: AbilityConstant.WindowMode.WINDOW_MODE_SPLIT_PRIMARY };
          try {
            this.mContext!!.startAbility(want, option, (error) => {
              if (error.code) {
                hilog.info(0x0000, 'testTag', '启动 WindowMode1Ability 分屏失败');
                return;
              }
              hilog.info(0x0000, 'testTag', '启动 WindowMode1Ability 分屏成功');
            });
          } catch (paramError) {
          }
        })

      Button('启动另一分屏窗口')
        .fontColor(Color.Black)
        .fontWeight(FontWeight.Medium)
        .onClick(() => {
          // let context = getContext(this) as common.UIAbilityContext;
          let want: Want = { bundleName: this.appBundleName, abilityName: 'WindowMode2Ability', moduleName: '' };
          // 指定启动 WindowMode2Ability 的窗口模式,右侧分屏 , 目前 仅在折叠屏和tablet设备上生效
          let option: StartOptions = { windowMode: AbilityConstant.WindowMode.WINDOW_MODE_SPLIT_SECONDARY };
          this.mContext!!.startAbility(want, option);
        })
    }
    .height('100%')
    .width('100%')
  }
}

TestWindowMode1.ets

@Entry
@Component
struct TestWindowMode1 {
  @State message: string = '智慧多窗1';

  build() {
    Column({ space: 10 }) {
      Text(this.message)
        .id('TestWindowMode1HelloWorld')
        .fontSize($r('app.float.page_text_font_20fp'))
        .fontWeight(FontWeight.Bold)
        .margin({ top: 20 })
    }
    .height('100%')
    .width('100%')
  }
}

WindowMode1Ability.ets代码

import { AbilityConstant, UIAbility, Want } from '@kit.AbilityKit';
import { hilog } from '@kit.PerformanceAnalysisKit';
import { window } from '@kit.ArkUI';

const DOMAIN = 0x0000;

export default class WindowMode1Ability extends UIAbility {
  onCreate(want: Want, launchParam: AbilityConstant.LaunchParam): void {
    hilog.info(DOMAIN, 'testTag', '%{public}s', 'Ability onCreate');
  }

  onDestroy(): void {
    hilog.info(DOMAIN, 'testTag', '%{public}s', 'Ability onDestroy');
  }

  onWindowStageCreate(windowStage: window.WindowStage): void {
    // Main window is created, set main page for this ability
    hilog.info(DOMAIN, 'testTag', '%{public}s', 'Ability onWindowStageCreate');

    windowStage.loadContent('pages/windowMode/TestWindowMode1', (err) => {
      if (err.code) {
        hilog.error(DOMAIN, 'testTag', 'Failed to load the content. Cause: %{public}s', JSON.stringify(err));
        return;
      }
      hilog.info(DOMAIN, 'testTag', 'Succeeded in loading the content.');
    });
  }

  onWindowStageDestroy(): void {
    // Main window is destroyed, release UI related resources
    hilog.info(DOMAIN, 'testTag', '%{public}s', 'Ability onWindowStageDestroy');
  }

  onForeground(): void {
    // Ability has brought to foreground
    hilog.info(DOMAIN, 'testTag', '%{public}s', 'Ability onForeground');
  }

  onBackground(): void {
    // Ability has back to background
    hilog.info(DOMAIN, 'testTag', '%{public}s', 'Ability onBackground');
  }
}

TestWindowMode2.ets代码

@Entry
@Component
struct TestWindowMode2 {
  @State message: string = '智慧多窗2';

  build() {
    Column({ space: 10 }) {
      Text(this.message)
        .id('TestWindowMode1HelloWorld')
        .fontSize($r('app.float.page_text_font_20fp'))
        .fontWeight(FontWeight.Bold)
        .margin({ top: 20 })
    }
    .height('100%')
    .width('100%')
  }
}

WindowMode2Ability.ets代码

import { AbilityConstant, UIAbility, Want } from '@kit.AbilityKit';
import { hilog } from '@kit.PerformanceAnalysisKit';
import { window } from '@kit.ArkUI';

const DOMAIN = 0x0000;

export default class WindowMode2Ability extends UIAbility {
  onCreate(want: Want, launchParam: AbilityConstant.LaunchParam): void {
    hilog.info(DOMAIN, 'testTag', '%{public}s', 'Ability onCreate');
  }

  onDestroy(): void {
    hilog.info(DOMAIN, 'testTag', '%{public}s', 'Ability onDestroy');
  }

  onWindowStageCreate(windowStage: window.WindowStage): void {
    // Main window is created, set main page for this ability
    hilog.info(DOMAIN, 'testTag', '%{public}s', 'Ability onWindowStageCreate');

    windowStage.loadContent('pages/windowMode/TestWindowMode2', (err) => {
      if (err.code) {
        hilog.error(DOMAIN, 'testTag', 'Failed to load the content. Cause: %{public}s', JSON.stringify(err));
        return;
      }
      hilog.info(DOMAIN, 'testTag', 'Succeeded in loading the content.');
    });
  }

  onWindowStageDestroy(): void {
    // Main window is destroyed, release UI related resources
    hilog.info(DOMAIN, 'testTag', '%{public}s', 'Ability onWindowStageDestroy');
  }

  onForeground(): void {
    // Ability has brought to foreground
    hilog.info(DOMAIN, 'testTag', '%{public}s', 'Ability onForeground');
  }

  onBackground(): void {
    // Ability has back to background
    hilog.info(DOMAIN, 'testTag', '%{public}s', 'Ability onBackground');
  }
}