跳到主要内容鸿蒙卡片开发实战:音乐播放器卡片构建指南 | 极客日志TypeScript大前端
鸿蒙卡片开发实战:音乐播放器卡片构建指南
综述由AI生成鸿蒙卡片开发实战教程,演示了基于 ArkTS 构建音乐播放器卡片的全过程。内容涵盖项目初始化、UI 页面设计、三种核心事件(message、router、call)的实现逻辑,以及后台服务与配置文件的设置。通过具体代码示例,展示了如何利用 FormKit 和 AbilityKit 实现卡片状态刷新与应用跳转,适合希望掌握鸿蒙原子化服务开发的开发者参考。
数字游民26 浏览 鸿蒙卡片开发实战:音乐播放器卡片构建指南
本文介绍如何创建一个集成核心事件的音乐播放器卡片,涵盖 message、router 和 call 事件的交互实现。
项目结构
创建包含以下关键文件的项目:
MusicWidget.ets: 卡片 UI 页面,包含所有交互按钮。
EntryFormAbility.ets: 卡片的生命周期管理,处理 message 事件和卡片刷新。
EntryAbility.ets: 应用的主 UIAbility,处理 router 事件的跳转。
MusicBackgroundAbility.ets: 后台 UIAbility,处理 call 事件的播放/暂停逻辑。
module.json5: 应用配置文件,注册所有 Ability 和权限。
form_config.json: 卡片配置文件。
第 1 步:项目初始化
- 打开 DevEco Studio,创建一个新的 Application 项目,选择 Stage 模型和 API 10。
- 项目创建成功后,右键点击
entry 模块,选择 New > Service Widget。
- 选择动态卡片,模板任选,语言选择 ArkTS,卡片名称填写为
MusicWidget。
DevEco Studio 会自动生成 EntryFormAbility.ets、MusicWidget.ets 和 form_config.json。接下来修改这些文件并添加新文件。
第 2 步:代码实现
1. 卡片 UI 页面 (src/main/ets/widget/pages/MusicWidget.ets)
这是用户直接看到的界面,包含了触发所有事件的按钮。
import { postCardAction } from '@kit.FormKit';
let storage = new LocalStorage();
@Entry(storage)
@Component
struct MusicWidget {
@LocalStorageProp('formId') formId: string;
@LocalStorageProp('songName') songName: string = '未播放';
@LocalStorageProp() : = ;
() : = ;
() {
({ : })
.()
.()
.()
.() {
()
.()
.(.)
.({ : });
()
.()
.(.);
()
.()
.(.)
.({ : });
(. ? : )
.()
.()
.(. ? . : .)
.(. ? . : .)
.({ : , : . })
.( { .(); });
()
.()
.()
.(.)
.(.)
.( { .(); });
()
.()
.()
.(.)
.(.)
.( { .(. === ? : ); });
}
}
(): {
(, {
: ,
: {
: .,
: ,
: !.
}
});
}
(): {
(, {
: ,
: ,
: {
: ,
: .
}
});
}
(: ): {
(, {
: ,
: ,
: {
: .,
: method,
:
}
});
}
}
'isLiked'
isLiked
boolean
false
@LocalStorageProp
'playStatus'
playStatus
string
'暂停'
build
Column
space
15
width
'100%'
height
'100%'
padding
15
justifyContent
FlexAlign.Center
Text
'音乐卡片'
fontSize
20
fontWeight
FontWeight
Bold
margin
bottom
10
Text
`🎵 ${this.songName}`
fontSize
16
fontWeight
FontWeight
Normal
Text
`▶️ 状态:${this.playStatus}`
fontSize
14
fontColor
Color
Grey
margin
bottom
20
Button
this
isLiked
'❤️ 已喜欢'
'🤍 喜欢'
width
'80%'
height
40
backgroundColor
this
isLiked
Color
Pink
Color
White
fontColor
this
isLiked
Color
White
Color
Black
border
width
1
color
Color
Pink
onClick
() =>
this
triggerMessageEvent
Button
'📱 打开播放列表'
width
'80%'
height
40
backgroundColor
Color
Blue
fontColor
Color
White
onClick
() =>
this
triggerRouterEvent
Button
`${this.playStatus === '播放' ? '⏸️ 暂停' : '▶️ 播放'}`
width
'80%'
height
40
backgroundColor
Color
Green
fontColor
Color
White
onClick
() =>
this
triggerCallEvent
this
playStatus
'播放'
'pause'
'play'
private
triggerMessageEvent
void
postCardAction
this
action
'message'
params
formId
this
formId
command
'toggleLike'
isLiked
this
isLiked
private
triggerRouterEvent
void
postCardAction
this
action
'router'
abilityName
'EntryAbility'
params
targetPage
'PlayListPage'
currentSong
this
songName
private
triggerCallEvent
method
string
void
postCardAction
this
action
'call'
abilityName
'MusicBackgroundAbility'
params
formId
this
formId
method
songId
'song_001'
2. 卡片生命周期管理 (src/main/ets/entryformability/EntryFormAbility.ets)
这个文件是卡片的'大脑',负责处理 message 事件并执行刷新。
import { formBindingData, FormExtensionAbility, formInfo, formProvider, } from '@kit.FormKit';
import { Want, BusinessError } from '@kit.AbilityKit';
import { hilog } from '@kit.PerformanceAnalysisKit';
const TAG: string = 'EntryFormAbility';
const DOMAIN_NUMBER: number = 0xFF00;
export default class EntryFormAbility extends FormExtensionAbility {
onAddForm(want: Want): formBindingData.FormBindingData {
hilog.info(DOMAIN_NUMBER, TAG, 'onAddForm');
const formId = want.parameters?.[formInfo.FormParam.IDENTITY_KEY] as string;
const initData = {
formId: formId,
songName: '七里香 - 周杰伦',
isLiked: false,
playStatus: '暂停'
};
return formBindingData.createFormBindingData(initData);
}
async onFormEvent(formId: string, message: string): Promise<void> {
hilog.info(DOMAIN_NUMBER, TAG, `onFormEvent: ${message}`);
const params = JSON.parse(message);
if (params.command === 'toggleLike') {
const newData = { isLiked: params.isLiked };
try {
await formProvider.updateForm(formId, formBindingData.createFormBindingData(newData));
hilog.info(DOMAIN_NUMBER, TAG, `卡片刷新成功:${formId}`);
} catch (error) {
const err = error as BusinessError;
hilog.error(DOMAIN_NUMBER, TAG, `卡片刷新失败:${err.message}`);
}
}
}
onRemoveForm(formId: string): void {
hilog.info(DOMAIN_NUMBER, TAG, `onRemoveForm: ${formId}`);
}
}
3. 应用主 UIAbility (src/main/ets/entryability/EntryAbility.ets)
处理 router 事件,负责跳转到应用内的页面。
import { AbilityConstant, UIAbility, Want } from '@kit.AbilityKit';
import { window } from '@kit.ArkUI';
import { hilog } from '@kit.PerformanceAnalysisKit';
const TAG: string = 'EntryAbility';
const DOMAIN_NUMBER: number = 0xFF00;
export default class EntryAbility extends UIAbility {
private currentWindowStage: window.WindowStage | null = null;
onCreate(want: Want, launchParam: AbilityConstant.LaunchParam): void {
hilog.info(DOMAIN_NUMBER, TAG, `onCreate: ${JSON.stringify(want.parameters)}`);
if (want.parameters.params) {
const params = JSON.parse(want.parameters.params as string);
if (params.targetPage === 'PlayListPage') {
hilog.info(DOMAIN_NUMBER, TAG, `准备跳转到播放列表页,当前歌曲:${params.currentSong}`);
}
}
}
onNewWant(want: Want): void {
hilog.info(DOMAIN_NUMBER, TAG, `onNewWant: ${JSON.stringify(want.parameters)}`);
}
onWindowStageCreate(windowStage: window.WindowStage): void {
this.currentWindowStage = windowStage;
this.loadPage('pages/Index');
}
private loadPage(pageName: string): void {
this.currentWindowStage?.loadContent(pageName, (err) => {
if (err.code) {
hilog.error(DOMAIN_NUMBER, TAG, `页面加载失败:${err.message}`);
}
});
}
}
4. 后台 UIAbility (src/main/ets/ability/MusicBackgroundAbility.ets)
注意: 这个文件需要手动创建。在 ets 目录下新建一个 ability 文件夹,然后创建此文件。
import { UIAbility, Want, AbilityConstant } from '@kit.AbilityKit';
import { BusinessError } from '@kit.BasicServicesKit';
import { formBindingData, formProvider } from '@kit.FormKit';
import { rpc } from '@kit.IPCKit';
import { hilog } from '@kit.PerformanceAnalysisKit';
const TAG: string = 'MusicBackgroundAbility';
const DOMAIN_NUMBER: number = 0xFF00;
class CallResult implements rpc.Parcelable {
result: string;
constructor(result: string) {
this.result = result;
}
marshalling(messageSequence: rpc.MessageSequence): boolean {
messageSequence.writeString(this.result);
return true;
}
unmarshalling(messageSequence: rpc.MessageSequence): boolean {
this.result = messageSequence.readString();
return true;
}
}
export default class MusicBackgroundAbility extends UIAbility {
onCreate(want: Want, launchParam: AbilityConstant.LaunchParam): void {
hilog.info(DOMAIN_NUMBER, TAG, 'onCreate');
try {
this.callee.on('play', this.handlePlay.bind(this));
this.callee.on('pause', this.handlePause.bind(this));
} catch (error) {
const err = error as BusinessError;
hilog.error(DOMAIN_NUMBER, TAG, `方法监听失败:${err.message}`);
}
}
private handlePlay(data: rpc.MessageSequence): CallResult {
const params = JSON.parse(data.readString());
const formId = params.formId;
hilog.info(DOMAIN_NUMBER, TAG, `后台执行播放:${params.songId}`);
this.updateCardStatus(formId, '播放');
return new CallResult('播放成功');
}
private handlePause(data: rpc.MessageSequence): CallResult {
const params = JSON.parse(data.readString());
const formId = params.formId;
hilog.info(DOMAIN_NUMBER, TAG, `后台执行暂停:${params.songId}`);
this.updateCardStatus(formId, '暂停');
return new CallResult('暂停成功');
}
private async updateCardStatus(formId: string, status: string): Promise<void> {
try {
await formProvider.updateForm(formId, formBindingData.createFormBindingData({ playStatus: status }));
hilog.info(DOMAIN_NUMBER, TAG, `卡片状态刷新成功:${status}`);
} catch (error) {
const err = error as BusinessError;
hilog.error(DOMAIN_NUMBER, TAG, `卡片状态刷新失败:${err.message}`);
}
}
onDestroy(): void {
hilog.info(DOMAIN_NUMBER, TAG, 'onDestroy');
this.callee.off('play');
this.callee.off('pause');
}
}
5. 应用配置文件 (src/main/module.json5)
这是最重要的配置文件,必须正确注册所有组件和权限。
{
"module": {
"name": "entry",
"type": "entry",
"description": "$string:module_desc",
"mainElement": "EntryAbility",
"deviceTypes": ["phone", "tablet"],
"deliveryWithInstall": true,
"installationFree": false,
"pages": "$profile:main_pages",
"abilities": [
{
"name": "EntryAbility",
"srcEntry": "./ets/entryability/EntryAbility.ets",
"description": "$string:EntryAbility_desc",
"icon": "$media:icon",
"label": "$string:EntryAbility_label",
"startWindowIcon": "$media:icon",
"startWindowBackground": "$color:start_window_background"
},
{
"name": "MusicBackgroundAbility",
"srcEntry": "./ets/ability/MusicBackgroundAbility.ets",
"description": "处理卡片 call 事件的后台服务"
}
],
"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"
}
]
}
],
"requestPermissions": [
{
"name": "ohos.permission.INTERNET"
},
{
"name": "ohos.permission.KEEP_BACKGROUND_RUNNING",
"reason": "$string:keep_background_running_reason",
"usedScene": {
"abilities": ["MusicBackgroundAbility"],
"when": "always"
}
}
]
}
}
注意: 您需要在 src/main/resources/base/element/string.json 中添加 keep_background_running_reason 的定义。
{
"string": [
{
"name": "keep_background_running_reason",
"value": "用于在后台处理卡片的播放/暂停指令"
}
]
}
6. 卡片配置文件 (src/main/resources/base/profile/form_config.json)
{
"forms": [
{
"name": "MusicWidget",
"description": "$string:MusicWidget_desc",
"src": "./ets/widget/pages/MusicWidget.ets",
"uiSyntax": "arkts",
"window": {
"designWidth": 720,
"autoDesignWidth": true
},
"colorMode": "auto",
"isDefault": true,
"updateEnabled": true,
"scheduledUpdateTime": "10:30",
"updateDuration": 0,
"defaultDimension": "2*2",
"supportDimensions": ["2*2"],
"formConfigAbility": "",
"dataProxyEnabled": false,
"isDynamic": true
}
]
}
第 3 步:运行指南
- 环境准备:确保您的 DevEco Studio 版本支持 API 10,并且已经配置好了 HarmonyOS SDK 和模拟器/真实设备。
- 创建项目:按照'项目初始化'的步骤创建一个新的 Application 项目。
- 替换代码:将上面提供的代码片段,分别复制并替换到项目中对应的文件里。特别注意手动创建
MusicBackgroundAbility.ets 文件。
- 配置 HAP 包:
- 打开
entry > src > main > module.json5。
- 在编辑器下方的
HAP 标签页中,点击 Add,新增一个 HAP,例如命名为 entry_feature。
- 将
MusicBackgroundAbility 从 entry 模块移动到 entry_feature 模块中。这是因为包含后台运行权限的 Ability 需要放在一个独立的 feature HAP 中。
- 确保
module.json5 中 requestPermissions 部分也在 entry_feature 模块中。
- 编译运行:将项目编译并运行到您的 HarmonyOS 设备或模拟器上。
- 添加卡片:
- 在设备的桌面上,长按空白处,选择'添加卡片'。
- 找到您的应用,选择刚刚创建的'MusicWidget'并添加到桌面。
- 测试事件:
- 点击卡片上的'喜欢'按钮,按钮文字和颜色会立即变化(
message 事件)。
- 点击'打开播放列表'按钮,应用会被启动并进入主界面(
router 事件)。
- 点击'播放/暂停'按钮,卡片上的状态文字会切换,同时后台会有日志输出(
call 事件)。
相关免费在线工具
- Base64 字符串编码/解码
将字符串编码和解码为其 Base64 格式表示形式即可。 在线工具,Base64 字符串编码/解码在线工具,online
- Base64 文件转换器
将字符串、文件或图像转换为其 Base64 表示形式。 在线工具,Base64 文件转换器在线工具,online
- Markdown转HTML
将 Markdown(GFM)转为 HTML 片段,浏览器内 marked 解析;与 HTML转Markdown 互为补充。 在线工具,Markdown转HTML在线工具,online
- HTML转Markdown
将 HTML 片段转为 GitHub Flavored Markdown,支持标题、列表、链接、代码块与表格等;浏览器内处理,可链接预填。 在线工具,HTML转Markdown在线工具,online
- JSON 压缩
通过删除不必要的空白来缩小和压缩JSON。 在线工具,JSON 压缩在线工具,online
- JSON美化和格式化
将JSON字符串修饰为友好的可读格式。 在线工具,JSON美化和格式化在线工具,online