[经验分享] 基于OpenHarmony 5.0Release 开发的在线音乐应用卡片 原创 精华

深开鸿-孙炼 显示全部楼层 发表于 2024-10-14 09:53:52

前言

OpenHarmony社区最新发布了5.0 Release版本,在多媒体播放能力上有所加强,另外卡片的功能支持也更加丰富,对于在线音乐播放类应用,卡片是一个必不可少的功能,因此,本文在OpenHarmony 5.0Release (API12)版本上开发了在线音乐应用的卡片。

功能

预览图2*4:

image.png

卡片功能包括:

1、展示并实时刷新播放信息:歌曲名称、歌手、播放状态、歌曲封面图(网络)

2、播放控制:播放模式、上一首、播放/暂停、下一首、收藏

实现

本功能在集成测试仓的在线音乐播放应用基础上开发:在线音乐播放应用。开发卡片功能包含如下几个部分:

1、创建卡片工程

右键基础工程Entry,创建卡片工程:

image.png

根据提示创建完成后,生成卡片工程文件:

1)Ability

image.png

2)布局

image.png

3)配置

image.png

并且在工程原配置文件中增加了Ability标签:

"extensionAbilities": [
      {
        "name": "EntryFormAbility",
        "srcEntry": "./ets/entryformability/EntryFormAbility.ets",
        "label": "$string:EntryFormAbility_label",
        "description": "$string:EntryFormAbility_desc",
        "type": "form",
        "metadata": [
          {
            "name": "ohos.extension.form",
            "resource": "$profile:form_config"
          }
        ]
      }
    ],

2、卡片UI布局

WidgeCard布局代码:

build() {
    Stack({ alignContent: Alignment.TopStart }) {
      Row() {
      }
      .width(this.FULL_PERCENT)
      .height(this.FULL_PERCENT)
      .displayPriority(this.DISPLAY_PRIORITY_ONE)
      .backgroundImage('memory://' + this.imgName)
      .backgroundImageSize(ImageSize.Cover)

      Column()
        .width(this.FULL_PERCENT)
        .height(this.FULL_PERCENT)
        .backgroundColor(Color.White)
        .opacity(0.9)

      Row() {
        Column() {
          Row()
            .width(128)
            .height(128)
            .borderRadius(64)
            .margin(8)
            .shadow({ radius: 2, color: Color.Gray })
            .backgroundImage('memory://' + this.imgName)
            .backgroundImageSize(ImageSize.Cover)
            .onClick(() => {
              postCardAction(this, {
                action: this.ACTION_TYPE,
                abilityName: this.ABILITY_NAME,
              });
            })
        }
        .justifyContent(FlexAlign.Center)
        .height(this.FULL_PERCENT)

        Column() {
          Row() {
            Column() {
              Text(this.audioItem.title)
                .fontSize(32)
                .fontColor($r('app.color.information_title_font'))
                .fontWeight(FontWeight.Medium)
                .maxLines(this.MAX_LINES_VALUE)
                .textOverflow({ overflow: TextOverflow.Ellipsis })
              Text(this.audioItem.artist)
                .fontSize(24)
                .fontColor($r('app.color.item_text_font'))
                .fontWeight(FontWeight.Regular)
                .margin({ top: 4 })
                .maxLines(this.MAX_LINES_VALUE)
                .textOverflow({ overflow: TextOverflow.Ellipsis })
            }
            .justifyContent(FlexAlign.SpaceEvenly)
            .alignItems(HorizontalAlign.Start)
            .margin({ left: 8, right: 8 })
            .width('80%')
            .onClick(() => {
              postCardAction(this, {
                action: this.ACTION_TYPE,
                abilityName: this.ABILITY_NAME,
              });
            })

            Image(this.audioItem.isFavor ? $r('app.media.heart_fill') : $r('app.media.heart'))
              .width(48)
              .height(48)
              .onClick(() => {
                this.onSetFavor()
              })
          }
          .alignItems(VerticalAlign.Top)
          .justifyContent(FlexAlign.SpaceBetween)
          .width(this.FULL_PERCENT)

          Row() {
            Image(this.playMode === PLAY_MODE.REPEAT ? $r('app.media.repeat') : (this.playMode === PLAY_MODE.REPEAT1 ? $r('app.media.repeat_1') : $r('app.media.shuffle')))
              .width(64)
              .height(64)
              .padding(4)
              .onClick(() => {
                this.setPlayMode();
              })

            Image($r('app.media.backward_end_fill'))
              .width(64)
              .height(64)
              .padding(4)
              .onClick(() => {
                this.onPreviousClick()
              })
            if (this.isPlaying === ServerConstants.PLAYER_STATE_PLAYING) {
              Image($r('app.media.pause'))
                .width(64)
                .height(64)
                .padding(8)
                .onClick(() => {
                  this.onPauseClick();
                })
                .onAppear(() => {
                  if (this.audioItem.id !== '') {
                    if (this.downloadImgId === this.audioItem.id) {
                      return;
                    }
                    this.downloadImgId = this.audioItem.id;
                    postCardAction(this, {
                      action: 'message',
                      params: {
                        info: ServerConstants.SONG_IMAGE_URL + this.audioItem.id
                      }
                    });
                  }
                })
            } else {
              Image($r('app.media.play_fill'))
                .width(64)
                .height(64)
                .padding(4)
                .onClick(() => {
                  this.onPlayClick();
                })
            }

            Image($r('app.media.forward_end_fill'))
              .width(64)
              .height(64)
              .padding(4)
              .onClick(() => {
                this.onNextClick()
              })

          }
          .justifyContent(FlexAlign.SpaceBetween)
          .width(this.FULL_PERCENT)
        }
        .width('60%')
        .height(this.FULL_PERCENT)
        .justifyContent(FlexAlign.SpaceEvenly)
      }
      .width(this.FULL_PERCENT)
      .justifyContent(FlexAlign.SpaceEvenly)
      .displayPriority(this.DISPLAY_PRIORITY_TWO)
    }
  }

预览效果:

image.png

3、卡片内容更新

WidgetCard通过LocalStorageProp 绑定需要刷新的数据:

  @LocalStorageProp('formId') formId: string = '';
  @LocalStorageProp('audioItem') audioItem: AudioData = new AudioData('未播放', '--', '')
  @LocalStorageProp('isPlaying') isPlaying: string = ServerConstants.PLAYER_STATE_UNKNOWN;
  @LocalStorageProp('imgName') imgName: string = '';
  @LocalStorageProp('playMode') playMode: number = PLAY_MODE.REPEAT;

在播放后台通过formProvider.updataForm更新卡片内容:

  updateCard() {
    let formData: Record<string, AudioData| string> = {
      'audioItem': this.item,
      'isPlaying': this.state,
    };
    let formMsg: formBindingData.FormBindingData = formBindingData.createFormBindingData(formData);
    formProvider.updateForm(this.formId, formMsg).then(() => {
      console.info('FormAbility updateForm success.');
    }).catch((error: Base.BusinessError) => {
      console.info(`Operation updateForm failed. Cause: ${JSON.stringify(error)}`);
    })
  }

4、卡片功能事件

通过三种Action事件方式实现卡片的三种功能:

1、Router:点击图片或歌曲名跳转到应用主页面:

postCardAction(this, {
                action: 'router',
                abilityName: this.ABILITY_NAME,
              });

2、Call:点击播放/暂停、上一首下一首等,调用后台播放功能:

发送消息:

  onPlayClick(): void {
    postCardAction(this, {
      'action': 'call',
      'abilityName': this.ABILITY_NAME,
      'params': {
        'method': 'play'
      }
    });
    console.info(TAG, 'postCardAction onPlayClick');
  }

后台消息处理:

try {
      this.callee.on('play', (data: rpc.MessageSequence) => {
        aPlayerManager.resume();
        return null;
      });

3、通过Message消息通知EntryFormAbility下载封面的网络图片并显示:

发送消息,将图片url发给EntryFormAbility的OnFormEvent:

       postCardAction(this, {
                      action: 'message',
                      params: {
                        info: ServerConstants.SONG_IMAGE_URL + this.audioItem.id
                      }
                    });

下载图片并通知UI加载:

let httpRequest = http.createHttp()
    httpRequest.request(params.info, (err, data) => {
      if (!err && data.responseCode == http.ResponseCode.OK) {
        let imgFile = fs.openSync(tmpFile, fs.OpenMode.READ_WRITE | fs.OpenMode.CREATE);
        fs.write(imgFile.fd, data.result as ArrayBuffer).then((writeLen: number) => {
          console.info(TAG, "write data to file succeed and size is:" + writeLen);
        }).catch((err: Base.BusinessError) => {
          console.error(TAG, "write data to file failed with error message: " + err.message + ", error code: " + err.code);
        }).finally(() => {
          fs.closeSync(imgFile);
        });

        console.info(TAG, 'ArkTSCard download complete: %{public}s', tmpFile);
        let file: fileFs.File;
        let fileInfo: Record<string, string | number> = {};
        try {
          file = fs.openSync(tmpFile);
          fileInfo[fileName] = file.fd;
        } catch (e) {
          console.error(TAG, `openSync failed: ${JSON.stringify(e as Base.BusinessError)}`);
        }

        class FormDataClass {
          imgName: string = fileName;
          formImages: object = fileInfo;
        }

        let formData = new FormDataClass();
        let formInfo = formBindingData.createFormBindingData(formData);
        formProvider.updateForm(formId, formInfo).then(() => {
          console.info(TAG, '%{public}s', 'FormAbility updateForm success.');
        }).catch((error: Base.BusinessError) => {
          console.error(TAG, `FormAbility updateForm failed: ${JSON.stringify(error)}`);
        });
      } else {
        console.error(TAG, `ArkTSCard download task failed. Cause: ${JSON.stringify(err)}`);
      }
      httpRequest.destroy();
    })

效果

Screenshot_20241014095106954.jpeg

总结

当前卡片的基本功能都可以实现,但是仍有些不足,比如:不支持图片url自动加载;不支持点击效果ClickEffect;卡片对于动画的支持比较弱,不能实现长时间的动画效果;卡片进程不能成为后台任务,在生命周期没有活动时只能存活5秒,加载网络图片超时即失败等。

完整工程代码已开源到集成测试仓:在线音乐应用

后续随着版本迭代,OpenHarmony的功能会更加完善。

©著作权归作者所有,转载或内容合作请联系作者

您尚未登录,无法参与评论,登录后可以:
参与开源共建问题交流
认同或收藏高质量问答
获取积分成为开源共建先驱

Copyright   ©2023  OpenHarmony开发者论坛  京ICP备2020036654号-3 |技术支持 Discuz!

返回顶部