A clean Notion-style educational diagram illustrat

前言

上篇我们搞定了 3D 商品展示,今天换个方向——看看 HarmonyOS 7 最吸引眼球的两个 UI 新能力:互动卡片闪控窗

这两个东西组合起来能干嘛?简单说就是:你的 App 卡片在桌面上能"动起来",用户摇一摇手机卡片就有动态反馈;同时还能有一个全局悬浮的闪控球,随时呼出智能助手,不用切应用。

互动卡片:不只是静态展示

传统的桌面服务卡片就是一张静态图,点击跳 App。HarmonyOS 7 的互动卡片加了一层"活"的东西——你可以通过传感器事件(比如摇一摇)触发卡片的动态效果。

核心能力

  • 静态转动态:摇一摇手机,卡片从静态图变成动画状态
  • 前景出框:卡片里的人物/商品可以"溢出"卡片边界,产生立体感
  • 视差效果:随手机倾斜角度变化,卡片内元素产生视差移动

互动卡片组件开发

互动卡片还是基于 @Entry 装饰器的 Form 组件,但多了 @Interactive 装饰器和传感器事件绑定:

// card/SmartAssistantCard.ets
@Entry
@Component
@Interactive
struct SmartAssistantCard {
  @State isShaking: boolean = false;
  @State tiltX: number = 0;
  @State tiltY: number = 0;
  @StorageLink('cardWeather') weatherData: string = '晴';

  build() {
    Stack() {
      // 背景层——静态天气场景
      Image($r('app.media.weather_bg'))
        .width('100%')
        .height('100%')
        .objectFit(ImageFit.Cover)

      // 前景元素——会"出框"的天气图标
      Image($r('app.media.sun_icon'))
        .width(80)
        .height(80)
        .offset({
          x: this.tiltX * 10,
          y: this.tiltY * 10 - (this.isShaking ? 20 : 0)
        })
        .scale({
          x: this.isShaking ? 1.2 : 1.0,
          y: this.isShaking ? 1.2 : 1.0
        })
        .animation({
          duration: 300,
          curve: Curve.EaseOut
        })

      // 文字信息
      Column() {
        Text(this.weatherData)
          .fontSize(24)
          .fontWeight(FontWeight.Bold)
          .fontColor('#FFFFFF')
        Text('智能生活助手')
          .fontSize(12)
          .fontColor('#CCFFFFFF')
      }
      .alignItems(HorizontalAlign.Start)
      .padding(16)
      .layoutAlign(Alignment.BottomStart)
    }
    .width('100%')
    .height('100%')
    .borderRadius(16)
    // 绑定摇一摇事件
    .onShake(() => {
      this.isShaking = true;
      setTimeout(() => { this.isShaking = false; }, 1500);
    })
    // 绑定倾斜传感器
    .onTilt((event: TiltEvent) => {
      this.tiltX = event.angleX;
      this.tiltY = event.angleY;
    })
  }
}

A structured code documentation view in Notion sty

几个要点:@Interactive 装饰器告诉系统这个卡片支持交互事件;onShakeonTilt 是卡片专属的传感器回调,普通页面里用不到这套 API。前景出框效果的核心就是让元素在 offset 方向上突破卡片边界——系统会自动裁切掉超出部分,但通过 clip(false) 可以让它"溢出来",产生 3D 感。

卡片配置注册

别忘了在 module.json5 里声明互动能力:

{
  "module": {
    "extensionAbilities": [{
      "name": "SmartAssistantForm",
      "type": "form",
      "metadata": [{
        "name": "ohos.extension.form",
        "resource": "$profile:form_config"
      }],
      "interactive": true,
      "sensorCapabilities": ["shake", "tilt"]
    }]
  }
}

interactive: truesensorCapabilities 这两行是关键,少了任何一个,摇一摇和倾斜事件都不会触发。

闪控窗:悬浮窗的终极形态

闪控窗是 HarmonyOS 7 把悬浮窗、侧边栏暂存、闪控球三个东西融为一体的新方案。以前做一个悬浮功能,你得处理窗口管理、边界检测、收纳逻辑,现在系统全帮你搞定。

三种形态

  1. 悬浮窗模式:正常浮动在屏幕上的小窗口,可以自由拖动和调整大小
  2. 侧边栏暂存:拖到屏幕边缘自动吸附,变成侧边条,不占空间但随时可拉出来
  3. 闪控球模式:缩成一个悬浮小球,在任何 App 上方都能显示,点击展开恢复窗口

闪控窗配置

闪控窗通过 window 模块的 StageWindowManager 来管理:

// utils/FlashControlManager.ets
import { window } from '@kit.ArkUI';

export class FlashControlManager {
  private flashWindow: window.FlashControlWindow | null = null;

  // 创建闪控窗
  async create(context: Context): Promise<void> {
    const config: window.FlashControlConfig = {
      // 初始化为悬浮窗模式
      initialMode: window.FlashControlMode.FLOATING,
      // 窗口初始尺寸和位置
      bounds: {
        x: 100,
        y: 600,
        width: 360,
        height: 480
      },
      // 允许的模式切换
      supportedModes: [
        window.FlashControlMode.FLOATING,
        window.FlashControlMode.SIDEBAR,
        window.FlashControlMode.BALL
      ],
      // 闪控球的外观
      ballConfig: {
        size: 56,
        icon: $r('app.media.assistant_ball'),
        autoHide: true,      // 无操作 5 秒后半透明
        edgeAttach: true     // 允许吸附屏幕边缘
      }
    };

    this.flashWindow = await window.createFlashControl(context, config);

    // 加载卡片内容页面
    await this.flashWindow.setUIContent('pages/AssistantFlashPanel');

    // 监听模式切换
    this.flashWindow.on('modeChanged', (mode: window.FlashControlMode) => {
      switch (mode) {
        case window.FlashControlMode.BALL:
          // 缩成球时,暂停不必要的后台任务
          console.info('闪控窗已缩为闪控球');
          break;
        case window.FlashControlMode.FLOATING:
          // 展开时恢复
          console.info('闪控窗已展开');
          break;
      }
    });

    // 显示
    this.flashWindow.show();
  }

  // 从闪控球状态主动展开
  async expand(): Promise<void> {
    if (this.flashWindow) {
      await this.flashWindow.setMode(window.FlashControlMode.FLOATING);
    }
  }

  // 销毁
  destroy(): void {
    this.flashWindow?.destroy();
    this.flashWindow = null;
  }
}

A three-column comparison chart in a Notion databa

FlashControlConfig 里的 supportedModes 决定了用户能怎么操作。如果不想让用户切成侧边栏,去掉 SIDEBAR 就行。ballConfig.autoHide 挺实用的——闪控球在用户不碰它几秒后自动变半透明,不会挡住底下内容。

闪控窗内的 UI 面板

闪控窗里加载的是一个独立的页面,我们的智能助手面板长这样:

// pages/AssistantFlashPanel.ets
@Entry
@Component
struct AssistantFlashPanel {
  @State query: string = '';
  @State messages: string[] = [];

  build() {
    Column() {
      // 顶部拖动条(系统自动渲染,但你可以自定义颜色)
      Row()
        .width(40)
        .height(4)
        .borderRadius(2)
        .backgroundColor('#CCCCCC')
        .margin({ top: 8, bottom: 8 })

      // 消息列表
      List() {
        ForEach(this.messages, (msg: string) => {
          ListItem() {
            Text(msg)
              .fontSize(14)
              .padding(8)
              .backgroundColor('#F0F0F0')
              .borderRadius(8)
          }
        })
      }
      .layoutWeight(1)

      // 输入区
      Row() {
        TextInput({ placeholder: '问点什么...', text: this.query })
          .layoutWeight(1)
          .onChange((value: string) => { this.query = value; })

        Button('发送')
          .onClick(() => {
            if (this.query.trim()) {
              this.messages.push(this.query);
              this.query = '';
            }
          })
      }
      .padding(12)
    }
    .width('100%')
    .height('100%')
    .backgroundColor('#FFFFFF')
    .borderRadius(16)
  }
}

把互动卡片和闪控窗串起来

在我们的智能生活助手里,这两个功能是这么配合的:

桌面卡片展示天气/日程摘要 → 用户摇一摇看到动态效果 → 点击卡片"展开详情" → 闪控球弹出悬浮面板 → 用户在面板里跟助手对话。

关键代码是在卡片的点击事件中拉起闪控窗:

// 卡片中点击展开的回调
.onClick(() => {
  // 通过 Want 通知主应用启动闪控窗
  const want: Want = {
    bundleName: 'com.example.smartassistant',
    abilityName: 'MainAbility',
    action: 'action.showFlashControl'
  };
  context.startAbility(want);
})

然后在 MainAbilityonNewWant 里判断 action,决定是否拉起闪控窗。这部分逻辑比较直白就不展开了。

踩坑与心得

卡片刷新频率有限制。互动卡片的动态更新不是无限的,系统有帧率管控(大约 30fps 上限),别在里面做太复杂的逐帧动画,会卡。

闪控窗权限。创建闪控窗需要 ohos.permission.SYSTEM_FLOAT_WINDOW 权限,这是个 system_grant 权限,需要在 module.json5 里声明。普通应用能申请到,但审核时华为会看你的使用场景是否合理。

侧边栏暂存的 UI 适配。从悬浮窗拖到侧边栏时,窗口宽度会被压到很窄。你的 UI 得能响应这个变化,建议用 MediaQuery 做适配,或者监听 boundsChanged 事件动态调整布局。

小结

互动卡片和闪控窗是 HarmonyOS 7 在系统级 UI 上的大升级。互动卡片让桌面不再只是图标排列,闪控窗让悬浮交互有了标准化方案。做智能助手这类需要"常驻"和"随时可达"的应用,这两个能力几乎是必选项。

下一篇我们聊多形态服务窗口——悬浮窗、分屏、平行视界,看看怎么让 App 在不同设备形态上都好用。

Logo

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

更多推荐