47 直播头部:LiveHeader 与 LiveHeaderPC

系列五:直播与画中画 · 第 7 篇

对应工程:features/multishoppinglive/src/main/ets/components/LiveHeader.etsLiveHeaderPC.ets

引言

直播页顶部信息栏是"主播是谁、多少人在看、怎么退出"的门面。项目提供了两个头部组件:LiveHeader(默认产品用)与 LiveHeaderPC(PC 产品用)。它们长得像、职责不同:手机头部右侧有关闭按钮,点击触发画中画;PC 头部用返回箭头 + 无关闭按钮,因为 PC 的窗口由系统装饰管理。两个组件共享同一个 LiveHeaderState,把"店铺名 + 观看人数"的数据逻辑抽到公共状态类里,是"一个状态、两个视图"的复用样板。

状态层:LiveHeaderState

头部数据极简,两个文本 + 一次资源读取:

@ObservedV2
export class LiveHeaderState {
  public storeName: string = '';
  public viewersText: string = '';

  constructor(context: Context) {
    try {
      this.storeName = context.resourceManager.getStringSync($r('app.string.LIVE_HEADER_STORE_NAME').id);
      this.viewersText = context.resourceManager.getStringSync($r('app.string.LIVE_HEADER_VIEWERS').id);
    } catch (error) {
      Logger.info('getStringSync called.');
      this.storeName = '';
      this.viewersText = '';
    }
  }
}

@ObservedV2 标记让字段可被 V2 组件观察;构造时用 getStringSync 同步读字符串资源,读取失败降级为空串而不是抛错。LIVE_HEADER_VIEWERS 是"观看人数"文案(演示工程为静态文案,真实项目应改为 number 类型 + 定时刷新)。

两个头部组件都用 @Local 持有状态:

@Local private headerState: LiveHeaderState = new LiveHeaderState(getHostContextSafe(this.getUIContext()));

LiveHeader:手机/默认形态

LiveHeader 的布局是"左侧主播信息卡 + 右侧关闭按钮",两端对齐:

Row() {
  Row() {
    Image($r('app.media.live_vmall'))     // 主播头像
      .width(36)
      .aspectRatio(1)
      .margin({ right: 4 })
    Column() {
      Text(this.headerState.storeName).fontSize(12)
        .fontColor($r('sys.color.font_on_primary'))
      Text(this.headerState.viewersText).fontSize(10)
        .fontColor($r('sys.color.font_on_primary'))
    }
    .alignItems(HorizontalAlign.Start)
    .margin({ right: 16 })
  }
  .height(36)
  .backgroundColor($r('app.color.live_header_scrim'))   // 半透明遮罩
  .border({ radius: 18 })

  Image($r('app.media.icon_close_3'))                   // 关闭按钮
    .width(24)
    .height(24)
    .onClick(() => {
      this.onClose();
    })
}
.width('100%')
.height(36)
.padding({
  left: new WidthBreakpointType(16, 24, 32, 32).getValue(this.widthBp),
  right: new WidthBreakpointType(16, 24, 32, 32).getValue(this.widthBp)
})
.justifyContent(FlexAlign.SpaceBetween)
.background($r('app.color.live_stage_background'))

设计要点:

  • 主播卡是"胶囊"height(36) + borderRadius(18) 恰好圆角为高度一半,背景 live_header_scrim 是半透明遮罩色,主播信息"浮"在画面上又不刺眼。
  • 关闭按钮触发 onCloseonClose 由页面通过 @Event 注入。默认产品版 LiveBroadCastPageonClose 接到 onLiveClose()——注意第 1 篇讲过,default 版的 onLiveClose 走的是"先 startPip 再 pop"(画中画退出),所以手机用户点关闭看到的不是页面消失,而是视频飞进小窗。
  • 左右内边距随断点WidthBreakpointType(16, 24, 32, 32) 与直播页整体间距体系一致。

LiveHeaderPC:PC 差异化顶栏

LiveHeaderPC 与手机版有三处关键差异:

Row() {
  SymbolGlyph($r('sys.symbol.chevron_left'))   // 返回箭头替代关闭按钮
    .fontSize(24)
    .onClick(() => {
      this.onClose();
    })
    .margin({ right: 12 })
    .fontColor([$r('sys.color.font_on_primary')])
  Row() {
    Image($r('app.media.live_vmall'))
      .width(36).aspectRatio(1).margin({ right: 4 })
    Column() {
      Text(this.headerState.storeName).fontSize(12)
      Text(this.headerState.viewersText).fontSize(10)
    }
    .alignItems(HorizontalAlign.Start)
    .margin({ right: 16 })
  }
  .height(36)
  .backgroundColor($r('app.color.live_header_scrim'))
  .border({ radius: 18 })
}
.width('100%')
.height(36)
.padding({ left: ..., right: ... })
.margin({ top: 12 })
.justifyContent(FlexAlign.Start)

  • 返回箭头而非关闭SymbolGlyph($r('sys.symbol.chevron_left'))(系统符号图标)替代 icon_close_3.png。PC 的交互语义是"返回上一页"而非"关闭直播"。
  • 无关闭按钮:PC 窗口右上角的关闭/最小化由系统窗口装饰(Decor)接管,应用内不再重复放关闭键——这正是第 1 篇 LiveBroadCastPage PC 版 onLiveClose 直接 pop()、不走画中画的另一层原因(PC 用户习惯用窗口按钮退出)。
  • 靠左对齐 + 顶部留白justifyContent(FlexAlign.Start) + .margin({ top: 12 }),顶栏悬浮在直播画面左上角,视觉上更像"信息标签"而非"工具栏"。

窗口按钮(装饰)处理

PC 端(2in1 设备)窗口默认带系统装饰:标题栏 + 最小化/最大化/关闭按钮。本项目页面通过 .hideTitleBar(true) 隐藏标题栏、但保留窗口级装饰,于是:

  • 应用内的关闭/返回只负责页面栈mainPageMap.pop() / 画中画)。
  • 窗口关闭由系统装饰按钮完成,触发 UIAbility 生命周期,页面 aboutToDisappear 会兜底执行 stopPip()avPlayerUtil.release(),资源不会泄漏。
两版头部都由页面在断点分支里选择装配:LiveBroadCastPage 的侧栏模式(MD/LG/XL)在 PC 产品用 LiveHeaderPC,默认产品即使侧栏模式也用 LiveHeader

if (this.isSideMode()) {
  Row() {
    Column() {
      LiveHeaderPC({ widthBp: ..., onClose: () => { this.onLiveClose(); } })  // pc 版
      LiveBroadCast({ ... })
    }
  }
}

注意事项与总结

  • 交互语义因端而异:手机"关闭=画中画",PC"返回=退页面",同一 onClose 事件在不同产品里绑定了不同实现,事件只传意图、不传实现。
  • 系统符号图标优先:PC 版用 SymbolGlyph($r('sys.symbol.chevron_left')) 而非位图,随系统字体/主题自适应,PC 上是更"原生"的选择。
  • 窗口装饰与应用内按钮分工.hideTitleBar(true) 只藏标题栏;窗口级关闭由系统装饰负责,应用内按钮只管导航栈;aboutToDisappear 兜底释放资源。
  • 状态复用:两个头部共享 LiveHeaderState,数据逻辑只写一遍;差异全部收敛在视图层。
头部是直播页的"门面",两个组件一对比,多端差异化的取舍一目了然。下一篇进入购物链路:直播页如何与购物袋联动,把"看直播"变成"下单"。
Logo

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

更多推荐