前言

音乐卡片的播放按钮看起来只是一个图标,但背后串了很多东西:普通卡片发动作、应用侧播放音乐、必要时展开 LiveForm、最后还要刷新桌面卡片状态。

这篇就按项目代码,把播放按钮这条链路讲清楚。

效果图

Hand-drawn educational flowchart on warm cream pap

音乐卡片的核心就是播放、暂停、上一首、下一首这一组反馈。先看效果,再拆它背后怎么从点击一路走到刷新卡片。

音乐卡片播放效果

先看这些文件

  • entry/src/main/ets/widget/pages/MusicCard.ets
  • entry/src/main/ets/utils/ActionUtils.ets
  • entry/src/main/ets/utils/CardActionHandler.ets
  • entry/src/main/ets/viewmodel/music/MediaService.ets
  • entry/src/main/ets/utils/FormUtils.ets

小白看音乐卡片时,不要只盯着 MusicCard.ets。它只是入口,真正业务在后面。

播放按钮代码

MusicCard.ets 里有:

SymbolGlyph(this.isPlay ? $r('sys.symbol.pause_fill') : $r('sys.symbol.play_fill'))
  .fontSize(IconSize.MEDIUM)
  .symbolEffect(new ReplaceSymbolEffect(EffectScope.WHOLE), true)
  .onClick(() => {
    if (this.isPlay) {
      ActionUtils.playByAction(this, PlayActionType.PAUSE, this.formId);
    } else {
      ActionUtils.playByAction(this, PlayActionType.PLAY, this.formId);
      ActionUtils.requestOverFlowWithAction(
        this,
        LiveCardScale.MUSIC_WIDTH,
        LiveCardScale.MUSIC_HEIGHT,
        LIVE_CARD_DURATION,
        'PLAY',
        this.songId
      );
    }
  });

A hand-drawn doodle illustration on pure white pap

这段代码分两种情况。

如果正在播放:点击就是暂停,只调用 playByAction(PAUSE)

如果没在播放:点击就是播放,同时调用 requestOverFlowWithAction() 展开音乐 LiveForm 动画。

为什么暂停不展开 LiveForm

这是一个体验设计。

播放时给用户一个动效反馈,很合理。暂停时如果也展开大动画,就会有点吵。项目这里选择“播放才展开”,更克制。

小白做自己的卡片时也要注意:不是所有点击都要展开 LiveForm。只有需要强化反馈的动作才展开。

playByAction 发给应用侧

ActionUtils.ets 里:

public playByAction(component: object, type: PlayActionType, formId: string): void {
  postCardAction(component, {
    action: FormCarAction.CALL,
    abilityName: ENTRY_ABILITY,
    params: {
      method: 'cardAction',
      actionType: CardActionType.PLAY_ACTION,
      playActionType: type,
      formId: formId,
    },
  });
}

这里用的是 CALL,目标是主应用 ENTRY_ABILITY

因为播放音乐是应用能力,不是 EntryFormAbility 的事情。

CardActionHandler 接住播放动作

CardActionHandler.ets 里:

A hand-drawn doodle illustration on pure white pap

private handlePlayAction(params: Record<string, string>): void {
  if (params.playActionType) {
    let playActionType: PlayActionType = params.playActionType as PlayActionType;
    MediaService.getInstance().initAndPlayByAction(playActionType);
  }
}

这里没有直接写播放器逻辑,而是交给 MediaService

这很适合小白学习:Handler 只负责分发,不要什么都干。

MediaService 负责真正播放

MediaService 负责根据 PlayActionType 做具体业务,比如播放、暂停、上一首、下一首。

虽然这里不展开播放器内部细节,但你要知道它做完业务后,还会通过 FormUtils 更新卡片。

大概链路是:

播放状态变化
  -> 更新当前歌曲和播放状态
  -> MusicFileStore 保存当前歌曲
  -> FormUtils.updateMusicControlCards 更新所有 MusicCard

卡片怎么刷新播放状态

Technical framework diagram. Clean lines, structur

MusicCard.ets 接收:

@LocalStorageProp('isPlay') isPlay: boolean = false;
@LocalStorageProp('title') title: string = 'SongName';
@LocalStorageProp('singer') singer: string = 'Singer';
@LocalStorageProp('musicCover') musicCover: Resource = $r('app.media.ic_dream');

应用侧通过 FormUtils.updateMusicControlCards() 下发这些字段。

所以播放按钮图标能从播放变暂停,不是按钮自己切状态,而是应用侧业务完成后刷新卡片。

requestOverFlowWithAction 的作用

播放时还会调用:

ActionUtils.requestOverFlowWithAction(..., 'PLAY', this.songId)

它多传了一个 PLAY。后面 MusicLiveCardAbility 会读取这个动作,告诉 MusicLiveCard 播放“跳舞”动画。

如果是上一首/下一首,则传 PREVIOUSNEXT,LiveForm 就会播放切歌动画。

小白排查播放按钮

按钮点了没反应时,按这个顺序查:

  1. MusicCard.ets.onClick() 有没有执行。
  2. formId 是否为空。
  3. ActionUtils.playByAction() 是否发出 PLAY_ACTION
  4. EntryAbility 是否注册 cardAction
  5. CardActionHandler.handlePlayAction() 是否被调用。
  6. MediaService 是否初始化。
  7. FormUtils.updateMusicControlCards() 是否刷新卡片。

写在最后

音乐播放按钮不是一个孤立按钮,它是“卡片 UI -> 应用业务 -> 卡片刷新 -> LiveForm 动画”的完整闭环。

小白记住这句话:按钮只发意图,真正状态以应用侧刷新为准。

Logo

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

更多推荐