🌟 前言:为什么它是“用户体验”的神?

传统的通知是“一次性”的:“骑手已接单” -> 划掉 -> “骑手已送达” -> 划掉。
用户想看中间的进度,必须解锁手机 -> 打开 App -> 等待加载 -> 查看地图。

实况窗的逻辑是“伴随”:
任务开始,它出现;任务进行中,它实时更新(剩余 500米 -> 300米 -> 100米);任务结束,它自动消失。

它有两种核心形态:

  1. 胶囊态 (Capsule):位于状态栏左上角,显示极简信息(如:图标 + 时间)。
  2. 卡片态 (Card):在通知中心或锁屏界面,显示详细信息(如:地图、司机电话、车牌)。

🏗️ 一、 架构原理:从后台到前台

实况窗本质上是一种特殊的 Form (卡片)。它的生命周期由后台服务驱动。

数据流转图 (Mermaid):

锁屏卡片 状态栏胶囊 实况窗管理器 (System) 你的 App (后台 Service) 锁屏卡片 状态栏胶囊 实况窗管理器 (System) 你的 App (后台 Service) 携带初始数据: {status: "接单", time: "5min"} {status: "赶往中", distance: "300m"} loop [任务进行中] 1. 创建实况窗 (Create) 渲染胶囊 UI 渲染卡片 UI 2. 更新数据 (Update) 刷新 刷新 3. 结束任务 (End) 移除 移除

🛠️ 二、 核心实战:编写 ArkTS UI

实况窗的 UI 代码写在 EntryFormAbility 对应的 Widget 页面中。
关键在于:一套代码,适配两种形态。我们需要判断 ohos.extra.param.key.form_dimension 或者是自定义的布局类型。

1. 定义数据结构
class RideInfo {
  status: string;   // "已接单", "行程中"
  plate: string;    // 车牌号 "苏A·88888"
  time: string;     // "3 分钟"
  icon: Resource;
}

2. 编写响应式布局 (LiveViewCard.ets)

鸿蒙提供了 LiveView 专用组件布局结构。

@Entry
@Component
struct LiveViewCard {
  // 接收外部更新的数据
  @LocalStorageProp('status') status: string = '等待接单';
  @LocalStorageProp('time') time: string = '--';
  
  // 自动判断当前是胶囊还是卡片
  @StorageProp('formDimension') formDimension: number = 0;

  build() {
    // 根容器
    RelativeContainer() {
      // --- 形态 1: 胶囊态 (状态栏左上角) ---
      // 只有在胶囊模式下显示
      if (this.isCapsule()) {
        Row() {
          Image($r('app.media.car_icon'))
            .width(18).height(18).margin({ right: 4 })
          Text(this.time)
            .fontSize(14).fontColor(Color.White)
        }
        .backgroundColor('#007DFF')
        .borderRadius(20)
        .padding({ left: 8, right: 8, top: 2, bottom: 2 })
      }

      // --- 形态 2: 卡片态 (锁屏/通知中心) ---
      // 只有在卡片模式下显示
      if (this.isCard()) {
        Column() {
          // 标题栏
          Row() {
            Text('正在前往').fontSize(16).fontWeight(FontWeight.Bold)
            Blank()
            Text(this.time).fontSize(20).fontColor('#007DFF')
          }
          .width('100%')
          
          // 详细信息
          Row() {
            Text(`车牌: 苏A·88888`).fontSize(14).fontColor('#666')
            Text(this.status).fontSize(14).fontColor('#333')
          }
          .width('100%').margin({ top: 10 })
          
          // 进度条
          Progress({ value: 60, total: 100, type: ProgressType.Linear })
            .color('#007DFF')
            .width('100%')
            .margin({ top: 15 })
        }
        .padding(15)
        .backgroundColor(Color.White)
        .borderRadius(16)
      }
    }
  }

  isCapsule(): boolean {
    // 这里的判断逻辑需参考官方文档的 dimension 枚举
    return this.formDimension === 2; // 假设 2 代表胶囊
  }

  isCard(): boolean {
    return this.formDimension === 3; // 假设 3 代表卡片
  }
}


📡 三、 逻辑驱动:后台更新数据

UI 写好了,怎么让它动起来?
我们需要在后台服务(ServiceExtensionAbility)或者主进程中调用 notificationManagerformProvider

注意: 实况窗通常归类为 Notification (通知) 的一种特殊类型 (LiveView)。

import notificationManager from '@ohos.notificationManager';

// 1. 创建实况窗 (Publish)
async function startLiveView() {
  let notificationRequest = {
    id: 1001,
    content: {
      contentType: notificationManager.ContentType.NOTIFICATION_CONTENT_LIVE_VIEW,
      liveView: {
        status: notificationManager.LiveViewStatus.LIVE_VIEW_STATUS_UPDATE,
        // 这里关联你的 UI Ability 和初始数据
        extraInfo: {
            "status": "司机已出发",
            "time": "5 分钟"
        }
      }
    },
    // 设为常驻,用户无法划掉,直到任务结束
    isOngoing: true,
    isUnremovable: true
  };
  
  await notificationManager.publish(notificationRequest);
}

// 2. 实时更新 (Update)
// 比如每隔 10秒 调用一次
async function updateProgress(timeLeft: string) {
  let request = {
    id: 1001, // ID 必须一致
    content: {
      contentType: notificationManager.ContentType.NOTIFICATION_CONTENT_LIVE_VIEW,
      liveView: {
        status: notificationManager.LiveViewStatus.LIVE_VIEW_STATUS_UPDATE,
        extraInfo: {
            "status": "即将到达",
            "time": timeLeft
        }
      }
    }
  };
  await notificationManager.publish(request);
}

// 3. 结束实况窗 (End)
async function endLiveView() {
   // 发送结束状态,系统会自动做收尾动画
   // 或者直接 cancel
   await notificationManager.cancel(1001);
}


🎨 四、 交互细节:灵动的核心

要让实况窗看起来“高级”,必须处理好 Transitions (状态流转)

  1. 颜色语义
  • 蓝色:进行中(如:司机赶往中)。
  • 绿色:已完成/到达(如:司机已到达)。
  • 红色:异常/警告(如:订单超时)。
  1. 点击跳转
    实况窗被点击时,应该直接跳转到 App 的详情页(如地图页),而不是首页。
    notificationRequest 中配置 wantAgent 实现跳转。
  2. 动画过渡
    鸿蒙系统自动处理了从“胶囊”展开为“卡片”的动画,但你需要确保两个布局的数据是对应的,这样视觉上才不会突兀。

🎯 总结

实况窗是鸿蒙 Next 版本中最具辨识度的特性之一。
它打破了 App 的边界,将服务延伸到了 System UI 中。

对于打车、外卖、航班、比赛比分这类 “长时效、强关注” 的业务场景,实况窗不是“锦上添花”,而是 “必选项”

Next Step:
在模拟器中运行上面的代码,点击“开始接单”按钮。观察状态栏左上角是否出现了一个蓝色的小图标。尝试下拉通知栏,看它是否平滑地展开成了一张详细的卡片。

Logo

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

更多推荐