07服务卡片
服务卡片开发
1-创建卡片
- 创建卡片当前有两种入口:
- 创建 工程时,选择
Application
,可以在创建工程后右键新建卡片。 - 创建工程时,选择
Atomic Service
(元服务),也可以在创建工程后右键新建卡片。
在已有的应用工程中,可以通过右键新建ArkTS卡片
,具体的操作方式如下。
右键新建卡片。
说明
在
API 10
及以上Stage
模型的工程中,在Service Widget
菜单可直接选择创建动态或静态服务卡片。创建服务卡片后,也可以在卡片的form_config.json
配置文件中,通过isDynamic
参数修改卡片类型:
isDynamic
置空或赋值为"true"
,则该卡片为动态卡片;isDynamic
赋值为"false"
,则该卡片为静态卡片。
2 根据实际业务场景,选择一个卡片模板。
3 在选择卡片的开发语言类型(Language
)时,选择ArkTS
选项,然后单击“Finish
”,即可完成ArkTS卡片
创建。
建议根据实际使用场景命名卡片名称,
ArkTS卡片
创建完成后,工程中会新增如下卡片相关文件:卡片生命周期管理文件(EntryFormAbility.ets
)、卡片页面文件(DemoCardCard.ets
)和卡片配置文件(form_config.json
)。
2-设置卡片数据
可以简单调整DemoCardCard
卡片页面文件UI代码。先暂时展示默认数据:标题和描述。
@Entry
@Component
struct DemoCardCard {
// 卡片数据
readonly title: string = '标题:帝心卡片';
readonly description: string = '描述:学习卡片开发知识';
readonly actionType: string = 'router';
readonly abilityName: string = 'EntryAbility';
readonly message: string = 'add detail';
build() {
Column({ space: 30 }) {
Text(this.title)
.fontSize(14)
Text(this.description)
.fontSize(14)
}
.width('100%')
.height('100%')
.justifyContent(FlexAlign.SpaceEvenly)
.onClick(() => {
postCardAction(this, {
action: this.actionType,
abilityName: this.abilityName,
params: {
message: this.message
}
});
})
}
}
展示卡片
长按应用图标
/元服务右上角功能键
,进行卡片添加。将卡片添加到桌面进行显示。值得注意的是,在添加卡片时,会自动触发卡片生命周期文件src/main/ets/entryformability/EntryFormAbility.ets
中的onAddForm
方法。
- 片在创建时,会触发
onAddForm
生命周期,此时返 回数据可以直接传递给卡片- 另外卡片在被卸载时,会触发
onRemoveForm
生命周期
设置卡片数据
以上展示的卡片数据是在卡片页面文件中硬编码的静态数据,而卡片如果需要展示动态数据,则需要通过其他能力来获取。例如,EntryFormAbility
/EntryAbility
/应用或者元服务的页面文件
因为卡片不支持
import
,所以没法通过引入@ohos.net.http
等包来进行数据请求,因此需要通过借助外部能力获取数据后,再传入卡片内部进行展示
动态展示卡片数据的场景:
- 添加卡片到桌面时设置初始数据:在
EntryFormAbility
的onAddForm
生命周期函数中通过formBindingData.createFormBindingData(formData)
绑定数据到卡片页面中。 - 在应用或者元服务页面获取完数据后,主动将数据更新到卡片中
添加卡片时设置卡片数据
在EntryFormAbility
的onAddForm
生命周期函数中通过formBindingData.createFormBindingData(formData)
绑定到卡片页面中的数据默认会存储在LocalStorage
中。因此卡片页面可以从LocalStorage
中 使用@LocalStorageProp
取出数据。
EntryFormAbility.ets
// 添加卡片生命周期函数
onAddForm(want: Want) {
//绑定的数据要求:键值对类型的对象或者json
let formData:Record<string,string> = {
"title":"初始化数据",
"description":"数据绑定卡片成功,同时存储在localStorage中"
};
return formBindingData.createFormBindingData(formData);
}
DemoCardCard
const localStorageObj = new LocalStorage()
@Entry(localStorageObj)
@Component
struct DemoCardCard {
// 卡片数据
@LocalStorageProp('title') title: string = '标题:帝心卡片';
@LocalStorageProp('description') description: string = '描述:学习卡片开发知识';
readonly actionType: string = 'router';
readonly abilityName: string = 'EntryAbility';
readonly message: string = 'add detail';
build() {
Column({ space: 30 }) {
Text(this.title)
.fontSize(14)
Text(this.description)
.fontSize(14)
}
.width('100%')
.height('100%')
.justifyContent(FlexAlign.SpaceEvenly)
.onClick(() => {
postCardAction(this, {
action: this.actionType,
abilityName: this.abilityName,
params: {
message: this.message
}
});
})
}
}
3-卡片内容更新
ArkTS卡片
框架为提供方提供了updateForm
接口,用来触发卡片的页面更新能力。
提供方主动刷新卡片流程示意:
卡片提供方应用运行过程中,如果识别到有要更新卡片数据的诉求,可以主动通过
formProvider
提供的updateForm
接口更新卡片。
使用异步方式模拟在卡片提供方运行的过程中更新数据。添加卡片的过程中,会先显示初始化数据,定时结束会更新数据。
EntryFormAbility.ets
// 添加卡片生命周期函数
onAddForm(want: Want) {
//绑定的数据要求:键值对类型的对象或者json
let formData: Record<string, string> = {
"title": "初始化数据",
"description": "数据绑定卡片成功,同时存储在localStorage中"
};
// 卡片接收异步数据
setTimeout(() => {
// 准备新的待绑定的数据
formData = {
"title": "异步数据",
"description": "卡片更新数据:异步"
};
// 定义需要传递给卡片的数据对象
let bindData = formBindingData.createFormBindingData(formData)
console.log(`dxin => bindData ${JSON.stringify(bindData)}`)
// 获取当前添加到桌面的卡片id
let wantParams: Record<string, object> | undefined = want.parameters
console.log(`dxin =>wantParams ${JSON.stringify(wantParams)}`)
if (wantParams) {
let formId = wantParams[formInfo.FormParam.IDENTITY_KEY]
console.log(`dxin => formId ${formId}`)
// 更新数据到卡片上
formProvider.updateForm(formId.toString(), bindData)
.then(() => {
console.log(`dxin => 卡片数据更新成功 `)
})
.catch(() => {
console.log(`dxin => 卡片数据更新失败`)
})
} else {
console.log(`dxin => wantParams 获取失败。结果为undefined`)
}
}, 5000)
return formBindingData.createFormBindingData(formData);
}
卡片id
被存储在want.parameters
对象中。
片ID是字符串类型的数字。
want.parameters返回的wantParams对象格式。
{
"isDynamic": true,
"moduleName": "entry",
"ohos.extra.param.key.form_border_width": 0,
"ohos.extra.param.key.form_customize": [],
"ohos.extra.param.key.form_dimension": 2,
"ohos.extra.param.key.form_height": 513.5,
"ohos.extra.param.key.form_identity": "1767654127",
"ohos.extra.param.key.form_launch_reason": 1,
"ohos.extra.param.key.form_location": 2,
"ohos.extra.param.key.form_name": "demoCard",
"ohos.extra.param.key.form_obscured_mode": false,
"ohos.extra.param.key.form_rendering_mode": 0,
"ohos.extra.param.key.form_temporary": false,
"ohos.extra.param.key.form_width": 513.5,
"ohos.extra.param.key.module_name": "entry",
"ohos.inner.key.font_follow_system": true
}
获取卡片ID的方式也可以进行简写
// 获取当前添加到桌面的卡片id
let formId:string= want.parameters![formInfo.FormParam.IDENTITY_KEY].toString()
4-卡片事件
针对动态卡片,ArkTS卡片中提供了postCardAction
接口用于卡片内部和提供方应用间的交互,当前支持router
、message
和call
三种类型的事件,仅在卡片中可以调用。
动态卡片事件能力说明
动态卡片事件的主要使用场景如下:
router
事件:可以使用router
事件跳转到指定UIAbility
,并通过router
事件刷新卡片内容。- call事件:可以使用call事件拉起指定
UIAbility
到后台,并通过call
事件刷新卡片内容。message
事件:可以使用message
拉起FormExtensionAbility
,并通过FormExtensionAbility
刷新卡片内容。
router类型
拉起应用指定页面,可以做为快捷入口进行使用。
- 创建应用页面文件方便通过卡片入口拉起,模拟收集管家拉起不同页面
模拟手机管家的电池管理页面
@Entry
@Component
struct Battery {
build() {
Column() {
Text('电池管理页面').fontSize(30).fontColor('red')
}
.width('100%')
.height('100%')
}
}
模拟手机管家的骚扰拦截页面
@Entry
@Component
struct Intercept {
build() {
Column() {
Text('骚扰拦截页面').fontSize(30).fontColor('green')
}
.width('100%')
.height('100%')
}
}
- 在卡片页面准备不同的入口按钮。并在其点击事件中调用
postCardAction
接口。传递不同的参数。以方便在EntryAbility
中获取不同的参数,进行不同的页面跳转
模拟手机管家的卡片页面
Button('电池管理')
.onClick(() => {
postCardAction(this, {
action: 'router',
abilityName: 'EntryAbility',
params: {
targetPage: 'Battery'
}
});
})
Button('骚扰拦截')
.onClick(() => {
postCardAction(this, {
action: 'router',
abilityName: 'EntryAbility',
params: {
targetPage: 'Intercept'
}
});
})
- 卡片页面中
postCardAction
接口使用router
拉起页面时会跳转到提供方应用的指定UIAbility
。所以携带的参数会出现在EntryAbility
中,通过want
对象获取。
want
对象数据格式
{
"deviceId": "",
"bundleName": "com.dxin.DxinCard",
"abilityName": "EntryAbility",
"moduleName": "entry",
"uri": "",
"type": "",
"flags": 0,
"action": "",
"parameters": {
"formID": 1996538372,
"isCallBySCB": false,
"isShellCall": false,
"moduleName": "entry",
"ohos.aafwk.param.callerAbilityName": "com.ohos.sceneboard.MainAbility",
"ohos.aafwk.param.callerAppId": "com.ohos.sceneboard_BN5A7NPX8f5Y1CcU5/41YJg0mKSlTaYtl/ZdbgUdgoKFTy03BXMhv4m/1Es7dTBOa9XOBMGogbnP3YVIa06TCqciNzqEX+VfUqxKGPQLDCymV13eg4qZioi0DCA3vrbdLg==",
"ohos.aafwk.param.callerAppIdentifier": "5765880207853062833",
"ohos.aafwk.param.callerBundleName": "com.ohos.sceneboard",
"ohos.aafwk.param.callerPid": 1188,
"ohos.aafwk.param.callerToken": 537544831,
"ohos.aafwk.param.callerUid": 20020012,
"ohos.aafwk.param.displayId": 0,
"ohos.extra.param.key.appCloneIndex": 0,
"ohos.extra.param.key.form_identity": 1996538372,
"ohos.param.callerAppCloneIndex": 0,
"params": "{"targetPage":"Battery"}",
"specifyTokenId": 537206241,
"targetPage": "Battery"
},
"fds": {
},
"entities": [
]
}
需要注意区分两种情况。
- 当应用未被拉起时,即初次启动,在
onCreate
中获取参数。onCreate
生命周期函数执行后,默认自动执行onWindowStageCreate
生命周期函数。- 当应用已经被拉起过,非初次启动,在
onNewWant
中获取参数。此时需要手动执行onWindowStageCreate
生命周期。
src/main/ets/entryability/EntryAbility.ets
import { AbilityConstant, ConfigurationConstant, UIAbility, Want } from '@kit.AbilityKit';
import { window } from '@kit.ArkUI';
export default class EntryAbility extends UIAbility {
private selectPage: string = '';
private currentWindowStage: window.WindowStage | null = null;
// 初次启动应用
onCreate(want: Want, launchParam: AbilityConstant.LaunchParam): void {
if (want?.parameters?.params) {
// want.parameters.params 对应 postCardAction() 中 params 内容
let params: Record<string, Object> = JSON.parse(want.parameters.params as string);
this.selectPage = params.targetPage as string;
}
}
// 再次拉起应用
onNewWant(want: Want, launchParam: AbilityConstant.LaunchParam): void {
if (want?.parameters?.params) {
// want.parameters.params 对应 postCardAction() 中 params 内容
let params: Record<string, Object> = JSON.parse(want.parameters.params as string);
this.selectPage = params.targetPage as string;
}
if (this.currentWindowStage !== null) {
this.onWindowStageCreate(this.currentWindowStage);
}
}
onWindowStageCreate(windowStage: window.WindowStage): void {
let targetPage: string;
// 根据传递的targetPage不同,选择拉起不同的页面
switch (this.selectPage) {
case 'Battery':
targetPage = 'pages/Battery'; //与实际的UIAbility页面路径保持一致
break;
case 'Intercept':
targetPage = 'pages/Intercept'; //与实际的UIAbility页面路径保持一致
break;
default:
targetPage = 'pages/Index'; //与实际的UIAbility页面路径保持一致
}
if (this.currentWindowStage === null) {
this.currentWindowStage = windowStage;
}
windowStage.loadContent(targetPage, (err) => {
if (err.code) {
return;
}
});
}
}
call类型
在服务卡片拉起应用的功能,例如音乐卡片界面点击暂停
/播放
,上一首
/下一首
。例如计分器,点击按钮数字增加等场景。
"action"
为"call"
类型时,
- 提供方应用需要具备后台运行权限(
ohos.permission.KEEP_BACKGROUND_RUNNING
)。- params需填入参数
'method'
,且类型需为string
类型,用于触发UIAbility
中对应的方法。
- 配置权限
src/main/module.json5
"requestPermissions": [
{
"name": "ohos.permission.KEEP_BACKGROUND_RUNNING"
}
]
- 准备卡片页面UI代码
计分器服务卡片页面布局
let localStorage = new LocalStorage()
@Entry(localStorage)
@Component
struct DemoCardCard {
// 当前歌曲id ,
@LocalStorageProp('count') count: number = 1
// 当前卡片id
@LocalStorageProp('formId') formId: string = '666666'
build() {
Column() {
Text('计分器').fontSize(20)
Text(this.count + '').fontSize(30)
Button('+')
.fontSize(14)
.width('75%')
.height(40)
.onClick(() => {
postCardAction(this, {
action: 'call',
abilityName: 'EntryAbility',
params: {
// 需要调用的方法名称
method: 'changeCount',
count: this.count,
formId: this.formId
}
});
})
}
.width('100%')
.height('100%')
.justifyContent(FlexAlign.SpaceEvenly)
}
}
EntryFormAbility
中初始化服务卡片数据
src/main/ets/entryformability/EntryFormAbility.ets
// 添加卡片生命周期函数
onAddForm(want: Want) {
// 获取当前添加到桌面的卡片id
let formId: string = want.parameters![formInfo.FormParam.IDENTITY_KEY].toString()
console.log(`dxin =>formId ${formId}`)
// 准备一个临时数据结构 存放formId 和数字
interface FromData {
formId: string,
count: number
}
// 准备初始化数据
let formData: FromData = {
formId: formId,
count: 1
}
// 返回给卡片
return formBindingData.createFormBindingData(formData);
}
- EntryAbility中处理卡片页面触发的call事件
EntryAbility.ets
onCreate(want: Want, launchParam: AbilityConstant.LaunchParam): void {
try {
this.callee.on('changeCount', (data) => {
// 准备一个临时数据结构 存放formId 和数字
interface FromData {
formId: string,
count: number
}
// 从data中获取数据
let params = JSON.parse(data.readString()) as FromData
console.log(`dxin =>onCreate params.count ${params.count}`)
const newCount: Record<string, number> = {
// 此处切勿使用++ 那是给 params.count 增加 而非 "count"
"count": params.count+1
}
// 将这个新的数据响应给卡片
const formData = formBindingData.createFormBindingData(newCount)
formProvider.updateForm(params.formId, formData)
console.log(`dxin =>onCreate params.formId ${params.formId}`)
return null
})
} catch (err) {
console.log(`dxin => onCreate call 事件出错了 `)
}
}
message类型
卡片更新自己卡片页面数据,不会拉起应用,一定时间内(10s)如果没有更新的事件,会被销毁,适合做不太耗时的任务
当卡片组件发起message事件时,我们可以通过
onFormEvent
监听到
- 准备卡片页面结构
message案例卡片页面
let localStorage = new LocalStorage()
@Entry(localStorage)
@Component
struct DemoCardCard {
// 当前歌曲id ,
@LocalStorageProp('count') count: number = 1
// 当前卡片id
@LocalStorageProp('formId') formId: string = '666666'
build() {
Column() {
Text('message').fontSize(20)
Text(this.count + '').fontSize(30)
Button('刷新')
.fontSize(14)
.width('75%')
.height(40)
.onClick(() => {
postCardAction(this, {
action: 'message',
abilityName: 'EntryAbility',
params: {
count: this.count,
formId: this.formId
}
});
})
}
.width('100%')
.height('100%')
.justifyContent(FlexAlign.SpaceEvenly)
}
}
2.在EntryFormAbility
中的onFormEvent
中处理message
事件
EntryFormAbility
// 响应卡片页面的 message 事件
onFormEvent(formId: string, message: string) {
// formId 哪个卡片点的。就是哪个卡片的id
//message : {"method":"changeCount","count":5,"formId":"1341186179","action":"message","params":{"method":"changeCount","count":5,"formId":"1341186179"}}
console.log(`dxin =>onFormEvent formId ${formId}`)
// 准备一个临时数据结构
interface MessageParamsType {
method: string
count: number
formId: string
action: string
}
//获取参数
const params = JSON.parse(message) as MessageParamsType
let count = params.count
// 更新数据 准备数据
const newCount: Record<string, number> = {
"count": count + 10
}
const formData = formBindingData.createFormBindingData(newCount)
formProvider.updateForm(formId, formData)
}
定点刷新
指定时间点更新数据,例如上图所示,
09:37
分定时更新数据,由数字1
更新为666
- 准备卡片页面
卡片页面布局效果
let localStorage = new LocalStorage()
@Entry(localStorage)
@Component
struct DemoCardCard {
// 当前歌曲id ,
@LocalStorageProp('count') count: number = 1
// 当前卡片id
@LocalStorageProp('formId') formId: string = '666666'
build() {
Column() {
Text('定时刷新').fontSize(20)
Text(this.count + '').fontSize(30)
Text('id: ' + this.formId)
}
.width('100%')
.height('100%')
.justifyContent(FlexAlign.SpaceEvenly)
}
}
- 修改配置文件,启动更新能力和设置更新时间。
当同时配置了定时刷新
updateDuration
和定点刷新scheduledUpdateTime
时,定时刷新的优先级更高且定点刷新不会执行。如果想要配置定点刷新,则需要将updateDuration
配置为0
。即:使用
scheduledUpdateTime
定点刷新的时候,要设置updateDuration
定时刷新的值为0
。
src/main/resources/base/profile/form_config.json
"updateEnabled": true,
"scheduledUpdateTime": "09:07",
"updateDuration": 0,
- 在
EntryFormAbility
的onUpdateForm
生命周期函数中设置更新数据
EntryFormAbility
// 定时刷新
onUpdateForm(formId: string) {
// 准备新数据
const newData: Record<string, string | number> = {
"formId": formId,
"count": 666
}
// 更新数据
const formData = formBindingData.createFormBindingData(newData)
formProvider.updateForm(formId, formData)
.then(() => {
console.log(`dxin => 定时更新了数据`)
})
.catch(() => {
console.log(`dxin => 定时更新数据出错了`)
})
}
定时刷新
定时刷新:表示在一定时间间隔内调用
onUpdateForm
的生命周期回调函数自动刷新卡片内容。可以在form_config.json
配置文件的updateDuration
字段中进行设置。例如,可以将updateDuration
字段的值设置为2
,表示刷新时间设置为每小时一次。卡片定时刷新的更新周期单位为
30
分钟。应用市场配置的刷新周期范围是1~336
,即最短为半小时(1 * 30min
)刷新一次,最长为一周(336 * 30min
)刷新一次。
- 设置定时刷新,值为1代表30分钟刷新一次。
src/main/resources/base/profile/form_config.json
"updateDuration": 1,
- 卡片UI布局和
onUpdateForm
生命周期同定点刷新