前言

上篇我们玩了互动卡片和闪控窗,今天继续聊窗口——但这次是整个多窗口体系的全面实战。

A clean Notion-style diagram illustrating the Harm

HarmonyOS 7 的多窗口能力说实话让我眼前一亮。悬浮窗、侧边栏、分屏、平行视界,这四种形态组合在一起,基本覆盖了用户从手机到平板到折叠屏的所有使用场景。我们的智能生活助手要做到"哪里都能出现",就靠这套东西了。

多窗口体系全景

先把概念理清楚。HarmonyOS 7 的多窗口不是一个功能,是一整套窗口形态体系:

  • 悬浮窗:App 内容以小窗形式悬浮在其他应用之上,比如视频通话时切到聊天界面,视频画面缩小成浮窗继续
  • 侧边栏:窗口停靠在屏幕边缘,半收起状态,用户拉出来就能操作,类似系统的快捷面板
  • 分屏:两个应用左右或上下并排显示,各自独立操作
  • 平行视界:同一个应用在左右两侧显示不同层级的内容——左侧是列表,右侧是详情

这四种形态可以互相转换。闪控窗就是这个体系的核心枢纽——一个闪控球可以同时管理悬浮窗和侧边栏,用户一键切换。

EasyGo 新特性:应用内分屏比价

HarmonyOS 7 在平行视界基础上加了一个很实用的功能——应用内分屏比价。简单说就是同一个页面内,左右两边同时展示不同商品或不同渠道的信息,用户可以直观对比。

这个功能对我们的智能助手太合适了。用户在选商品的时候,左边看 A 平台的价格,右边看 B 平台的价格,不用再两个 App 来回切换。

EasyGo 的配置很轻量,核心就是在 module.json5 里声明支持平行视界,然后在代码里做分屏布局:

// module.json5
{
  "module": {
    "abilities": [
      {
        "name": "ShoppingAbility",
        "srcEntry": "./ets/entryability/ShoppingAbility.ets",
        "launchType": "multiton",
        "orientation": "auto_rotation",
        "multiWindowConfig": {
          "splitMode": "horizontal",
          "minWidth": 360,
          "minHeight": 480,
          "supportWindowModes": ["fullscreen", "split", "floating", "sidebar"]
        }
      }
    ]
  }
}

supportWindowModes 这个字段决定了你的应用支持哪些窗口形态。fullscreen 是默认的全屏,split 是分屏,floating 是悬浮窗,sidebar 是侧边栏。建议全勾上,让系统自动适配。

A Notion-style technical card visualizing the 'Eas

设备形态适配策略

这才是真正让人头疼的地方。手机、平板、折叠屏,三种设备形态下窗口的表现完全不同。

手机上主要用悬浮窗和侧边栏,分屏体验一般——屏幕太窄了,两个应用挤在一起很难用。平板和折叠屏就不一样了,分屏和平行视界才是主角,悬浮窗反而用得少。

我的做法是根据设备类型动态调整默认窗口策略:

// utils/WindowStrategy.ets
import { deviceInfo } from '@kit.BasicServicesKit';
import { window } from '@kit.ArkUI';

export type DeviceForm = 'phone' | 'tablet' | 'foldable';

export function getDeviceForm(): DeviceForm {
  const type = deviceInfo.deviceType;
  if (type === 'tablet') return 'tablet';
  if (type === '2in1' || type === 'foldable') return 'foldable';
  return 'phone';
}

export function getDefaultWindowMode(): window.WindowMode {
  const form = getDeviceForm();
  switch (form) {
    case 'tablet':
    case 'foldable':
      return window.WindowMode.WINDOW_MODE_SPLIT_PRIMARY;
    case 'phone':
    default:
      return window.WindowMode.WINDOW_MODE_FLOATING;
  }
}

这段代码通过 deviceInfo.deviceType 判断设备类型,然后返回推荐的默认窗口模式。平板和折叠屏默认走分屏,手机默认走悬浮窗。

实战:智能助手的分屏比价布局

A logical flowchart in Notion style explaining dev

接下来把比价功能跑通。核心思路是在平行视界模式下,左侧显示商品列表,右侧显示对比详情:

// pages/CompareShopping.ets
import { mediaquery } from '@kit.ArkUI';

@Entry
@Component
struct CompareShopping {
  @State leftProductId: string = '';
  @State rightProductId: string = '';
  @State isSplitMode: boolean = false;
  private smBreakpoint: mediaquery.SMBreakPoint = new mediaquery.SMBreakPoint();

  aboutToAppear() {
    this.smBreakpoint.register((sm: mediaquery.SMBreakpoint) => {
      // 当屏幕宽度大于 sm 阈值时,启用分屏模式
      this.isSplitMode = sm.currentBreakpoint === 'lg' || sm.currentBreakpoint === 'xl';
    });
  }

  aboutToDisappear() {
    this.smBreakpoint.unregister();
  }

  build() {
    if (this.isSplitMode) {
      // 分屏布局:左列表 + 右详情
      Row() {
        // 左侧:商品列表
        ProductListPanel({
          onProductSelect: (id: string) => {
            this.leftProductId = id;
          }
        })
          .width('40%')
          .height('100%')

        Divider().vertical(true)

        // 右侧:对比详情
        CompareDetailPanel({
          productId: this.leftProductId
        })
          .width('60%')
          .height('100%')
      }
      .width('100%')
      .height('100%')
    } else {
      // 手机模式:普通的列表→详情跳转
      ProductListPanel({
        onProductSelect: (id: string) => {
          router.pushUrl({
            url: 'pages/ProductDetail',
            params: { productId: id }
          });
        }
      })
    }
  }
}

这里用 mediaquery 的断点系统来判断当前窗口宽度。大屏幕下直接左右分栏,小屏幕下退化成传统的列表→详情跳转。同一套代码,不同设备自动适配。

悬浮窗场景:语音助手常驻

智能助手还有个很自然的悬浮窗场景——语音助手浮窗。用户在购物界面,悬浮窗显示语音助手按钮,点击就能语音搜索商品,不用离开当前页面:

// components/AssistantFloatingView.ets
@Entry
@Component
struct AssistantFloatingView {
  @State isExpanded: boolean = false;
  @State voiceText: string = '';

  build() {
    Column() {
      if (this.isExpanded) {
        // 展开态:显示语音识别结果和快捷操作
        Column({ space: 12 }) {
          Text(this.voiceText || '按住说话')
            .fontSize(14)
            .fontColor('#666')

          Row({ space: 16 }) {
            Button('搜商品').onClick(() => { /* 触发搜索 */ })
            Button('比价格').onClick(() => { /* 跳转比价 */ })
            Button('查物流').onClick(() => { /* 查物流 */ })
          }
        }
        .padding(16)
        .borderRadius(16)
        .backgroundColor('#FFFFFF')
        .shadow({ radius: 8, color: '#20000000', offsetX: 0, offsetY: 2 })
      } else {
        // 收起态:只显示一个小圆球
        Image($r('app.media.assistant_icon'))
          .width(48)
          .height(48)
          .borderRadius(24)
      }
    }
    .onClick(() => {
      this.isExpanded = !this.isExpanded;
    })
    .animation({ duration: 200, curve: Curve.EaseInOut })
  }
}

悬浮窗组件配合闪控球,用户随时能呼出语音助手。点击闪控球展开完整面板,再点一次收起。这个交互在购物流程中特别顺滑。

踩坑记录

开发过程中踩了几个坑,分享一下:

窗口尺寸变化时数据丢失。从全屏切到分屏,组件会重新布局,如果状态管理没做好,@State 里的数据可能丢失。解决方案是用 @StorageLink 或者把关键状态存到 AppStorage 里,这样窗口变化不影响数据。

侧边栏宽度适配。侧边栏模式下可用宽度比预期小很多,硬编码宽度很容易溢出。建议侧边栏模式全部用百分比布局或者 Flex 弹性布局。

多窗口间的通信。两个分屏窗口想共享数据,别用 EventHub——它只在同一个 UIAbility 内有效。用 @ohos.app.ability.common 里的 sharedData 或者分布式数据管理,跨窗口通信更靠谱。

我的感受

多窗口体系是 HarmonyOS 7 在用户体验层面感知最强的升级之一。但说实话,适配工作量不小。三种设备形态 × 四种窗口模式,排列组合下来有十几种布局状态。我的建议是先做手机全屏 + 悬浮窗,跑通核心流程,再逐步加分屏和平行视界。一口吃不成胖子,分阶段来会轻松很多。

EasyGo 的分屏比价确实好用,我自己在折叠屏上试了试,同时看两个平台的商品价格,体验非常直觉。如果你的 App 有类似的"对比"场景,强烈建议试试这个能力。

下一篇我们来聊方舟引擎,看看怎么把这些窗口界面的渲染性能拉上去。

Logo

讨论HarmonyOS开发技术,专注于API与组件、DevEco Studio、测试、元服务和应用上架分发等。

更多推荐