当前位置:   article > 正文

在HarmonyOS中实现基于JS卡片的音乐播放器

根据harmonyos系统的支持音乐播放器项目介绍

e18a92de9d6640b099ff8f7f77a08d16.png

/   今日科技快讯   /

近日,苹果首席执行官蒂姆·库克接受《时代》杂志专访,谈及他本人对领导力、企业价值和新技术的看法。库克表示,苹果不仅要引领创新,还要努力让世界变得更安全更公平,而这一点很重要。

在谈到令人兴奋的新技术时,库克表示自己对人工智能很感兴趣。他说,“我对人工智能很感兴趣。如今,人工智能已经出现在许多你根本想不到的产品中。从我们识别用户面部、指纹的方式,归纳整理照片的方式,再到Siri的工作方式都是如此。关于人工智能能为人们做什么,如何让人们生活更轻松,实际上我们还处在早期阶段。”

/   作者简介   /

大家周三好,这周已然过半,新的一周继续加油吧!

本篇文章来自半夏微凉的投稿,文章主要分享了如何在HarmonyOS中实现基于JS卡片的音乐播放器,相信会对对鸿蒙感兴趣的朋友们有所帮助!同时也感谢作者贡献的精彩文章。

半夏微凉的博客地址:

https://developer.huawei.com/consumer/cn/personalcenter/myCommunity/communityBlog?uid=5747c32f82a141bf8af5febde6b6af2f&siteId=1

/   文章简介   /

这是一款基于JS卡片打造的音乐播放卡片应用,我们可以通过桌面卡片来获取音乐播放的信息,也可以进行播放、暂停、歌曲切换等功能。

效果展示

5325a7caa7162a3a417fa32bcaaad80e.gif

  • 2X2卡片展示歌曲封面、播放状态、播放进度、歌曲名称等信息。

  • 4X4卡片增加歌词展示。

  • 通过卡片即可操作音乐播放、暂停、切换等操作。

/   搭建HarmonyOS环境   /

安装DevEco Studio,详情请参考DevEco Studio下载。

设置DevEco Studio开发环境,DevEco Studio开发环境需要依赖于网络环境,可以根据如下两种情况来配置开发环境:

  • 如果可以直接访问Internet,只需进行下载HarmonyOS SDK操作

  • 如果网络不能直接访问Internet,需要通过代理服务器才可以访问,请参考配置开发环境

本程序需要在真机运行,需要提前申请证书:

  1. 准备密钥和证书请求文件

  2. 申请调试证书

/   音乐卡片开发   /


功能设计

卡片的功能有三部分:

  1. 信息展示

  2. 页面跳转

  3. 数据交互

因此我们可以实现的功能有:

  1. 歌曲名称、歌手名称、歌曲封面等信息展示

  2. 卡片跳转至播放器主页,并且数据同步

  3. 播放、暂停、歌曲切换

  4. 播放进度展示


Java卡片与JS卡片选型

确定了我们要实现的功能后,再进行Java卡片与JS卡片选型,如图所示,java卡片支持的组件比较少,无法满足我们的功能需求。

JS卡片功能更强大,支持的组件也更多,可以实现我们的开发需求。

b2562cc5bcf05fa74784d8b68054fb2b.png


代码结构

9dad2d71a7192e4a5f9ca2663658e457.png

包名

功能

bean

歌词、柱状图数据实体类

customcomponent

自定义组件柱状图

database

数据库和数据表

provider

列表数据提供者

service

播放器服务类

slice

启动页和主页面

utils

数据库、log、歌词解析、线程切换工具类


JS界面实现

新建卡片

e071efd4cde8840e4bbf61f990dd874e.png


选择模板

工具为我们提供了多个模板,我们选择一个音乐播放模板。

6aaacf698398dcba8c9d87787e4128fd.png


卡片配置

创建完成后,config.json文件中会自动生成卡片配置的参数,我们可以在此处设置卡片的名称、规格、类型等参数。

Java Ability中的jsComponentName和js模块下的name相对应。

2ba8390fc140d39882b6cde9bb04a3c1.png


JS page

创建完成后有三个文件,js文件为卡片提供数据,hml文件编写布局属性,css文件编写控件样式。

82670d7bea1ce57aedfe7d243e715ee1.png


布局编写

js为卡片提供数据,配置跳转事件和message事件。

  1. export default {
  2.     data: {
  3.     //歌曲封面
  4.         picName: "/common/music.png",
  5.     //进度条的值
  6.         progressValue: 0,
  7.     //按钮点击发送message
  8.         last: "last",
  9.         play: "play",
  10.         next: "next",
  11.     //歌曲名
  12.         songName: "未播放",
  13.     //歌手名
  14.         singer: "未知歌手",
  15.     //歌词列表
  16.         lyric: [{
  17.                     fontColor: "#9C9C9C",
  18.                     fontSize: "14px",
  19.                     Lrc: "",
  20.                 }],
  21.     actions: {
  22.     //跳转事件
  23.         routerEvent: {
  24.             action: "router",
  25.             bundleName: "com.example.wryproject",
  26.             abilityName: "com.example.wryproject.MusicAbility"
  27.         },
  28.     //message事件
  29.         lastEvent: {
  30.             action: "message",
  31.             params: {
  32.                 message: this.last
  33.             }
  34.         },
  35.         playEvent: {
  36.             action: "message",
  37.             params: {
  38.                 message: this.play
  39.             }
  40.         },
  41.         nextEvent: {
  42.             action: "message",
  43.             params: {
  44.                 message: this.next
  45.             }
  46.         },
  47.     }
  48. }

html代码

  1. <div class="container">
  2.     <div class="title_container">
  3.         <image src="{{ picName }}" @click="routerEvent" class="music-img"></image>
  4.         <div class="songData">
  5.             <text class="songName">{{ songName }}</text>
  6.             <text class="singer">{{ singer }}</text>
  7.         </div>
  8.     </div>
  9.     <list class="lyric_list">
  10.         <list-item class="lyric_list_item" for="{{ lyric }}">
  11.             <text class="lyric_text" style="color : {{ $item.fontColor }}; font-size : {{ $item.fontSize }};">{{
  12.                 $item.Lrc }}</text>
  13.         </list-item>
  14.     </list>
  15.     <div class="button_container">
  16.         <progress class="progress" type="ring" percent="{{ progressValue }}">
  17.         </progress>
  18.         <image src="{{ lastImage }}" @click="lastEvent" class="last-img"></image>
  19.         <image src="{{ playImage }}" @click="playEvent" class="play-img"></image>
  20.         <image src="{{ nextImage }}" @click="nextEvent" class="next-img"></image>
  21.     </div>
  22. </div>

css代码较长,这里只贴出一部分:

  1. .container {
  2.     width: 100%;
  3.     height: 100%;
  4. }
  5. .title_container {
  6.     height: 70%;
  7.     width: 100%;
  8.     flex-direction: row;
  9.     position: absolute;
  10.     top: 5;
  11. }
  12. .music-img {
  13.     background-color: #0F000000;
  14.     height: 50%;
  15.     width: 40%;
  16.     object-fit: contain;
  17.     border-radius: 10px;
  18.     margin-left: 8%;
  19.     margin-top: 8%;
  20.     position: absolute;
  21. }
  22. .songData {
  23.     height: 50%;
  24.     width: 50%;
  25.     position: absolute;
  26.     flex-direction: column;
  27.     align-items: center;
  28.     padding-right: 10%;
  29.     right: 3%;
  30.     top: 8%;
  31. }

初始预览效果:

33c6ee5960bfc3364e48d699961893fc.png


音频资源获取

JS界面已经准备好了,接下来我们实现音乐播放功能,音频播放的实现不是我们文章的重点,所以下面我只大致分享一下实现流程,感兴趣的小伙伴可以到文章末尾获取项目代码。

音频获取

通过AVStorage,我们可以获取到本地歌曲的地址、名称、时长等信息,但很遗憾,AVStorage目前没有歌手信息,当我以为项目功能又要“裁剪”时,我想到大部分音频文件的地址是包含歌手信息的,我们将音频地址打印出来(如下图),这样我们就可以通过字符串截取,来拿到歌手信息了,然后将音频数据保存至数据库中,此项目使用了对象关系映射数据库 ,它可以让开发者不必再去编写复杂的SQL语句, 以操作对象的形式来操作数据库,相当友好呢。

739917b4003d4595b15981f1eb40b76e.png

  1. DataAbilityHelper helper = DataAbilityHelper.creator(this);
  2.         try {
  3.             ResultSet resultSet = helper.query(AVStorage.Audio.Media.EXTERNAL_DATA_ABILITY_URI, null, null);
  4.             //通过while循环拿到所有音频数据
  5.             while (resultSet != null && resultSet.goToNextRow()) {
  6.                 //音乐ID
  7.                 int musicId = resultSet.getInt(resultSet.getColumnIndexForName(AVStorage.AVBaseColumns.ID));
  8.                 //音频地址
  9.                 String musicPath = resultSet.getString(resultSet.getColumnIndexForName(AVStorage.AVBaseColumns.DATA));
  10.                 //音频名称
  11.                 String musicTitle = resultSet.getString(resultSet.getColumnIndexForName(AVStorage.AVBaseColumns.TITLE));
  12.                 //音频时常
  13.                 int musicDuration = resultSet.getInt(resultSet.getColumnIndexForName(AVStorage.AVBaseColumns.DURATION));
  14.                 //AVStorage自带属性中暂无歌手信息,但musicPath中包含歌手信息,此处将歌手名称截取出来
  15.                 int startIndex = musicPath.lastIndexOf("/");
  16.                 int endIndex = musicPath.lastIndexOf("-");
  17.                 String singer = "未知歌手";
  18.                 if (startIndex != -1 && endIndex != -1) {
  19.                     if (endIndex < startIndex) {
  20.                         endIndex = musicPath.lastIndexOf(".");
  21.                     }
  22.                     singer = musicPath.substring(startIndex + 1, endIndex - 1);
  23.                 }
  24.                 //过滤小于10秒的音频
  25.                 if (musicDuration > 10000) {
  26.                     //将音频数据插入数据库
  27.                 }
  28.             }
  29.             resultSet.close();
音频封面的获取

音频封面的获取比较消耗性能,而且获取项目内音频封面和获取本地音频封面的方法不同,我将它单独放在一个方法里实现。

封面获取主要用到AVMetadataHelper对象,官网示例链接:获取音频的图像数据的开发步骤。

获取音频的图像数据的开发步骤:

https://developer.harmonyos.com/cn/docs/documentation/doc-guides/media-data-mgmt-obtaining-0000001050751061

  1. Uri uri = Uri.appendEncodedPathToUri(AVStorage.Audio.Media.EXTERNAL_DATA_ABILITY_URI, stringId);
  2. fileDescriptor = helper.openFile(uri, "r");
  3. //通过文件描述符获取封面
  4. avMetadataHelper.setSource(fileDescriptor);
  5. byte[] data = avMetadataHelper.resolveImage();

通过AVStorage.Audio.Media.EXTERNAL_DATA_ABILITY_URI和音频ID,我们拿到了音频的Uri,helper.openFile(uri, "r")拿到文件描述符,调用avMetadataHelper.resolveImage方法拿到字节数组,我们就可以创建PixelMap对象了(见下图):

  1. /**
  2.          * 通过MP3文件的数据流获取专辑封面
  3.          *
  4.          * @return 封面
  5.          */
  6.         public PixelMap getPixelMapCover() {
  7.             OrmContext ormContext = DatabaseUtils.getOrmContext(getContext());
  8.             OrmPredicates ormPredicates = ormContext.where(MusicData.class);
  9.             List<MusicData> musicDataList = ormContext.query(ormPredicates);
  10.             MusicData musicData = musicDataList.get(currentPosition);
  11.             Blob musicCover = musicData.getMusicCover();
  12.             PixelMap pixelMap = null;
  13.             if (musicCover != null && musicCover.length() != 0) {
  14.                 byte[] bytes = musicCover.getBytes(1, Math.toIntExact(musicCover.length()));
  15.                 ImageSource imageSource = ImageSource.create(bytes, null);
  16.                 //普通解码createPixelMap传入的DecodingOptions 设置为null
  17.                 pixelMap = imageSource.createPixelmap(null);
  18.             }
  19.             return pixelMap;
  20.         }
歌词获取

歌词是通过网络获取(资源网站地址+歌曲名+歌手名),拿到歌词地址数据。

7d08b35f13eb9d65a7c1fcccbd8f37de.png

最终拿到歌词lrc文件,解析为歌词列表,免费的资源毕竟是少数,所以这个网站只有传唱度高的歌曲才有歌词资源。

歌词解析代码较多,见项目LyricAnalysisUtil类。

630b784ce1eadaf60ff016dc1350a3ff.png


播放功能实现

播放

第一次点击播放就随机设置资源,如果正在播放就暂停,反之就播放,然后更新列表播放状态。

  1. /**
  2.          * 播放音乐
  3.          */
  4.         public void playMusic() {
  5.             if (currentPosition == -1) {
  6.                 randomPlayer(musicDataList.size());
  7.             } else if (player.isNowPlaying()) {
  8.                 player.pause();
  9.             } else {
  10.                 player.play();
  11.             }
  12.             musicService.notice();
  13.             if (MusicAbilitySlice.getProvider() != null) {
  14.                 ThreadUtil.runUI(() -> MusicAbilitySlice.getProvider()
  15.                     .updatePlayerState(-1, currentPosition, player.isNowPlaying()));
  16.             }
  17.         }
上下曲切换

调整播放列表的pisition,实现播放资源的切换。

  1. /**
  2.          * 播放上一曲
  3.          */
  4.         public void lastMusic() {
  5.             if (currentPosition == -1) {
  6.                 //未设置歌曲资源弹出dialog提示
  7.                 new ToastDialog(mContext)
  8.                     .setAlignment(LayoutAlignment.CENTER)
  9.                     .setText("请选择需要播放的歌曲~")
  10.                     .show();
  11.             } else if (currentPosition == 0) {
  12.                 //提示用户已经是第一首了
  13.                 new ToastDialog(mContext)
  14.                     .setAlignment(LayoutAlignment.CENTER)
  15.                     .setText("已经是第一首了~")
  16.                     .show();
  17.             } else {
  18.                 //播放上一首
  19.                 currentPosition -= 1;
  20.                 setSource();
  21.             }
  22.         }
随机播放
  1. /**
  2.  * 根据传入的数值区间,生成随机数,并播放
  3.  *
  4.  * @param end 随机数的最大区间
  5.  */
  6. public void randomPlayer(int end) {
  7.     Random random = new Random();
  8.     currentPosition = random.nextInt(end);
  9.     setSource();
  10. }
设置播放资源
  1. /**
  2.  * 重置播放器,设置资源重新播放
  3.  */
  4. public void setSource() {
  5.     //判断地址是raw文件下资源还是本地资源
  6.     if (!path.contains("Music")) {
  7.         RawFileDescriptor rawFileDescriptor;
  8.         try {
  9.             rawFileDescriptor = getResourceManager().getRawFileEntry(path).openRawFileDescriptor();
  10.             player.setSource(rawFileDescriptor);
  11.         } catch (IOException e) {
  12.             e.printStackTrace();
  13.         }
  14.     } else {
  15.         source = new Source(path);
  16.         player.setSource(source);
  17.     }
  18.     player.prepare();
  19.     player.play();
  20. }

卡片数据更新

接下来就是本文的主题了:如何将获取到的数据显示到卡片上?

卡片的生命周期

我们通过IDE创建卡片时,会自动绑定MainAbility,生成卡片的回调方法。

在onCreateForm方法中我们要将卡片ID保存至数据库,后面将会使用ID来进行卡片更新。

  1. @Override
  2. protected ProviderFormInfo onCreateForm(Intent intent) {
  3.     if (intent == null) {
  4.         return new ProviderFormInfo();
  5.     }
  6.     //返回主界面后musicRemoteObject就会为空,此时操作卡片就需要重新获取musicRemoteObject对象
  7.         if (musicRemoteObject == null) {
  8.             musicRemoteObject = MusicServiceAbility.get();
  9.         }
  10.     // 获取卡片id
  11.     long cardId = INVALID_CARD_ID;
  12.     if (intent.hasParameter(AbilitySlice.PARAM_FORM_IDENTITY_KEY)) {
  13.         cardId = intent.getLongParam(AbilitySlice.PARAM_FORM_IDENTITY_KEY, INVALID_CARD_ID);
  14.     }
  15.     ProviderFormInfo providerFormInfo = new ProviderFormInfo();
  16.     // 获取卡片规格
  17.     int dimension = DEFAULT_DIMENSION_2X2;
  18.     if (intent.hasParameter(AbilitySlice.PARAM_FORM_DIMENSION_KEY)) {
  19.         dimension = intent.getIntParam(AbilitySlice.PARAM_FORM_DIMENSION_KEY, DEFAULT_DIMENSION_2X2);
  20.     }
  21.     CardData cardData = new CardData(cardId, dimension);
  22.     DatabaseUtils.insertCardData(cardData, DatabaseUtils.getOrmContext(this));
  23.     musicRemoteObject.updateCardCover();
  24.     musicRemoteObject.lrcLoading("暂无歌词数据");
  25.     providerFormInfo.setJsBindingData(new FormBindingData());
  26.     return providerFormInfo;
  27. }

onDeleteForm方法是卡片删除时的回调,所以我们要同步删除数据库中对应的卡片ID。

  1. @Override
  2. protected void onUpdateForm(long formId) {
  3.     HiLog.info(TAG, "onUpdateForm");
  4.     super.onUpdateForm(formId);
  5. }
  6. @Override
  7. protected void onDeleteForm(long formId) {
  8.     HiLog.info(TAG, "onDeleteForm: formId=" + formId);
  9.     super.onDeleteForm(formId);
  10.     DatabaseUtils.deleteCardData(formId, DatabaseUtils.getOrmContext(this));
  11. }

onTriggerFormEvent方法用来接收卡片的message 事件,卡片的播放、暂停、歌曲切换都是通过这个方法来进行交互。

根据卡片传过来的字符串对象判断下一步操作。

  1. @Override
  2. protected void onTriggerFormEvent(long formId, String message) {
  3.     super.onTriggerFormEvent(formId, message);
  4.     //返回主界面后musicRemoteObject就会为空,此时操作卡片就需要重新获取musicRemoteObject对象
  5.         if (musicRemoteObject == null) {
  6.             musicRemoteObject = MusicServiceAbility.get();
  7.         }
  8.     String musicMessage = (String) ZSONObject.stringToZSON(message).get("message");
  9.     switch (musicMessage) {
  10.         case "last":
  11.             musicRemoteObject.lastMusic();
  12.             break;
  13.         case "play":
  14.             //当用户杀死应用时,player就会为null,此时点击卡片播放,需做判空处理
  15.             if (musicRemoteObject.getPlayer() == null) {
  16.                 musicRemoteObject.initPlayer(this);
  17.             }
  18.             musicRemoteObject.playMusic();
  19.             break;
  20.         case "next":
  21.             musicRemoteObject.nextMusic();
  22.             break;
  23.     }
  24.     musicRemoteObject.updateCardCover();
  25. }
设置卡片数据

ZSONObject设置卡片数据, zsonObject.put("songName", title)方法,第一个参数相当于key,要和JS文件的数据名称相对应,第二个参数为要传入的数据。

73763f09aba3472ab1c3868fb7b4a550.png

  1. /**
  2.  * 设置卡片数据
  3.  */
  4. private ZSONObject setCardData() {
  5.     // 设置卡片页面需要的数据
  6.     if (ormContext == null) {
  7.         ormContext = DatabaseUtils.getOrmContext(musicService.getContext());
  8.     }
  9.     ZSONObject zsonObject = new ZSONObject();
  10.     int position = getCurrentPosition();
  11.     //如果player没有设置资源,直接return
  12.     if (position == -1) {
  13.         return null;
  14.     } else {
  15.         //player设置了资源,将卡片按钮点亮
  16.         zsonObject.put("lastImage""/common/last.png");
  17.         zsonObject.put("nextImage""/common/next.png");
  18.     }
  19.     //根据播放状态设置卡片播放或暂停
  20.     if (getIsPlay()) {
  21.         zsonObject.put("playImage""/common/pause.png");
  22.     } else {
  23.         zsonObject.put("playImage""/common/play.png");
  24.     }
  25.     //设置卡片title
  26.     String title = DatabaseUtils.queryMusicData(position, ormContext).getMusicTitle();
  27.     String singer = DatabaseUtils.queryMusicData(position, ormContext).getSinger();
  28.     zsonObject.put("songName", title);
  29.     zsonObject.put("singer", singer);
  30.     return zsonObject;
  31. }

在歌曲播放、切换时设置卡片封面。

此处有一个知识点 --> JS卡片如何设置本地图片或网络图片?官方文档给出了示例:通过内存图片方式使用image组件。他的核心就是如下两个方法:

  • zsonObject.put("图片名称", 图片地址)

  • formBindingData.addImageData(图片名称, 图片字节数据)

重点就是我们要拿到图片的数据流,并添加到 "memory://" 这个内存地址。

代码

  1. /**
  2.  * 更新所有卡片封面
  3.  */
  4. public void updateCardCover() {
  5.     ThreadUtil.runWork(() -> {
  6.         ZSONObject zsonObject = setCardData();
  7.         List<CardData> cardDataList = DatabaseUtils.queryAllCardData(ormContext);
  8.         FormBindingData formBindingData = new FormBindingData(zsonObject);
  9.         if (getCardCover() != null) {
  10.             int musicId = getMusicList().get(currentPosition).getMusicId();
  11.             String picName = musicId + ".png";
  12.             String picPath = "memory://" + picName;
  13.             assert zsonObject != null;
  14.             zsonObject.put("picName", picPath);
  15.             formBindingData.addImageData(picName, getCardCover());
  16.         }
  17.         //遍历卡片列表更新所有卡片
  18.         for (CardData cardData : cardDataList) {
  19.             try {
  20.                 updateForm(cardData.getCardId(), formBindingData);
  21.             } catch (FormException e) {
  22.                 DatabaseUtils.deleteCardData(cardData.getCardId(), ormContext);
  23.             }
  24.         }
  25.     });
  26. }

音乐开始播放后就开启一个Timer计时器来更新播放进度和歌词。

  1. /**
  2.  * 播放进度的更新
  3.  */
  4. private void playListener() {
  5.     EventRunner mainEventRunner = EventRunner.getMainEventRunner();
  6.     eventHandler = new EventHandler(mainEventRunner) {
  7.         @Override
  8.         protected void processEvent(InnerEvent event) {
  9.             super.processEvent(event);
  10.                 //更新播放进度和歌词
  11.             }
  12.         }
  13.     };
  14.     if (timer != null) {
  15.         timer.cancel();
  16.         timer = null;
  17.     }
  18.     timer = new Timer();
  19.     timer.schedule(new TimerTask() {
  20.         @Override
  21.         public void run() { 
  22.                 InnerEvent innerEvent = InnerEvent.get();
  23.                 innerEvent.param = getPlayer().getCurrentTime();
  24.                 eventHandler.sendEvent(innerEvent);
  25.         }
  26.     }, 0200);
  27. }

/   开发总结   /

此项目的开发,使我们掌握了:

  1. JS卡片的布局编写:JS编写布局相对于JAVA更加高效。例如 JS 组件list,只需三五行代码即可实现列表的基本展示,大大提升了我们的开发效率,JS+JAVA的混合开发模式是一个很不错的开发方案。

  2. 卡片的跳转和交互:通过卡片快速直达我们需要的页面,减少层级交互,应用的使用场景更加丰富,操作也更加便捷高效。

  3. 卡片数据的实时更新:卡片成为应用信息展示的直接载体,真正做到“信息直达”,只需解锁手机即可获取到我们需要的信息。

  4. Player的简单使用:音视频播放作为手机的常用功能之一,有相当多的用武之地,掌握Player的使用是各位开发者的必经之路。

  5. 本地音频资源的获取:数据是一个应用的基础,学会合理使用这些数据、资源也是各位开发者需要掌握的基础。

开发过程中遇到的问题:

直接传入音频地址封面获取失败

问题原因:传入的地址中有中文,资源解析失败。

  1. AVMetadataHelper avMetadataHelper= new AVMetadataHelper();
  2. avMetadataHelper.setSource("/path/冬天的秘密.mp4");

解决方案:通过传入文件描述符获取封面。

  1. Uri uri = Uri.appendEncodedPathToUri(AVStorage.Audio.Media.EXTERNAL_DATA_ABILITY_URI, stringId);
  2. fileDescriptor = helper.openFile(uri, "r");
  3. //通过文件描述符获取封面
  4. avMetadataHelper.setSource(fileDescriptor);
  5. byte[] data = avMetadataHelper.resolveImage();

网络获取歌词失败

问题原因:需要添加网络明文请求的设置。

解决方案:在config.json中添加网络明文请求设置为true

7de7e0035ef24a681b8e4e5e212c8cd0.png

卡片封面更新无效

问题原因:只有卡片出现在桌面时,调用updateForm方法才可以更新封面。

解决方案:考虑到定时器中更新封面代价较大,我们可以分为两种方案:

① 计时器定时更新文字、进度条等占用性能较小的组件。

② 返回桌面时应用失去焦点,在onInactive方法中更新卡片的图片数据即可减少性能损耗。

6911bdca174eda81a9579f673d4e026c.png

卡片歌词列表如何实现

问题原因:卡片中的组件是无法调用其方法的,所以无法控制列表滚动。

解决方案:定时刷新列表数据,我们只给列表设置5条数据,然后定时更新不同的歌词传入列表,达到列表伪滚动效果。

音乐在后台播放,几秒钟后音乐自动暂停,进入应用后音乐又开始自动播放。

问题原因:鸿蒙的应用启动管理默认为系统自动管理,有时会将应用后台冻结。

解决方案:在手机自带的手机管家-->应用启动管理-->找到自己的应用关闭自动管理-->打开允许后台活动,应用就不会被冻结了。

bedf535546005f6c6d8a4f4c39e30cd1.png

以上就是本人遇到的一些问题,希望可以给各位开发者提供参考。由于篇幅有限,文章有很多细节并不能面面俱到,感兴趣的小伙伴可以通过文章末尾链接下载项目,文中写的不好的地方欢迎大家提出建议,大家也可以在评论区多多交流,共同进步~

代码地址:

https://gitee.com/WRY666/ohos_-music-card

推荐阅读:

我的新书,《第一行代码 第3版》已出版!

Android自定义控件之弹幕的实现,这效果 666

Android 12 SplashScreen API快速入门

欢迎关注我的公众号

学习技术或投稿

7af271e836af1f53f9e5628b9edf605b.png

a3bd7dee0c0d5681973e090e402a495b.png

长按上图,识别图中二维码即可关注

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/知新_RL/article/detail/254513
推荐阅读
相关标签
  

闽ICP备14008679号