HarmonyOS基于ArkTS卡片服务
卡片服务
前言
- Form Kit(卡片开发框架)提供了一种在桌面、锁屏等系统入口嵌入显示应用信息的开发框架和API,可以将应用内用户关注的重要信息或常用操作抽取到服务卡片(以下简称“卡片”)上,通过将卡片添加到桌面上,以达到信息展示、服务直达的便捷体验效果
- 使用场景
- 支持设备类型:卡片可以在手机、平板等设备上使用。
- 支持开发卡片应用类型:应用和元服务内均支持开发卡片。
- 支持卡片使用位置:用户可以在桌面、锁屏等系统应用上添加使用,暂不支持在普通应用内嵌入显示卡片
- 卡片类型
- 静态卡片(Static Widget):规格默认只能是2*4,静态卡片展示的信息是固定的,通常是一次性加载后就不再更新的内容
- 动态卡片(Dynamic Widget):规格任意,动态卡片展示的信息是实时更新的,内容随着时间、事件、状态等变化而变化
总体思路
卡片创建
-
创建app项目
-
创建卡片项目
卡片分为静态卡片和动态卡片 静态卡片和动态卡片的区别:静态卡片不接受事件,通过FormLink组件来触发卡片动作动态卡片接受事件,通过事件来接触postCardAction对象触发相应的卡片动作
-
卡片设置
- 卡片项目生成后,在resource中的profile中有 form_config配置卡片信息【可以设置规定规格的卡片supportDimensions】
- 卡片项目生成后,在module.json5中有 EntryFormAbility与卡片有关【第一次创建卡片时会新建一个生命周期在此处,该生命周期为卡片独有的生命周期】
- 每次构建卡片都会新增一个widget文件,此文件为卡片的页面文件
- 同一个模块的卡片共享一个生命周期
前期准备
- 在资源resource中加入float资源文件和midea资源,string资源(默认,中文,英文)
卡片数据层
- 创建common公共资源目录,将卡片数据存放在公共资源目录中
CommonConstants
//卡片更新的数据常量import { CardListItemData } from './CommonData'//工具常量数据
export class CommonConstants{//第一列卡片清单数据static readonly CARD_LIST_DATA_FIRST: Array<CardListItemData> = [{id: 1,title: $r('app.string.title1'),content: $r('app.string.content1'),icon: $r('app.media.item1')},{id: 2,title: $r('app.string.title2'),content: $r('app.string.content2'),icon: $r('app.media.item2')},{id: 3,title: $r('app.string.title3'),content: $r('app.string.content3'),icon: $r('app.media.item3')},{id: 4,title: $r('app.string.title4'),content: $r('app.string.content4'),icon: $r('app.media.item4')}]//第二列卡片清单数据static readonly CARD_LIST_DATA_SECOND: Array<CardListItemData> = [{id: 1,title: $r('app.string.title5'),content: $r('app.string.content5'),icon: $r('app.media.item5')},{id: 2,title: $r('app.string.title6'),content: $r('app.string.content6'),icon: $r('app.media.item6')},{id: 3,title: $r('app.string.title7'),content: $r('app.string.content7'),icon: $r('app.media.item7')},{id: 4,title: $r('app.string.title8'),content: $r('app.string.content8'),icon: $r('app.media.item8')}];//第三列卡片清单数据static readonly CARD_LIST_DATA_THIRD: Array<CardListItemData> = [{id: 1,title: $r('app.string.title9'),content: $r('app.string.content9'),icon: $r('app.media.item9')},{id: 2,title: $r('app.string.title10'),content: $r('app.string.content10'),icon: $r('app.media.item10')},{id: 3,title: $r('app.string.title11'),content: $r('app.string.content11'),icon: $r('app.media.item11')},{id: 4,title: $r('app.string.title12'),content: $r('app.string.content12'),icon: $r('app.media.item12')}];
}
CommonData
// 卡片更新的数据模型
// 存放数据模型以及数据处理
import { CommonConstants } from "./CommonConstants"export class CommonData {// 根据改值与3的余数决定显示那个板块static flag: number = 0// 制作对应的卡片数据 返回的类型是CardListItemDatastatic getData() :Array<CardListItemData>{// 先制作数据模型// 判断flag值,与3的余数if (CommonData.flag % 3 === 0) {// 前往CommonConstants制作数据return CommonConstants.CARD_LIST_DATA_FIRST} else if (CommonData.flag % 3 === 1) {return CommonConstants.CARD_LIST_DATA_SECOND} else {return CommonConstants.CARD_LIST_DATA_THIRD}}static changeFlage() {// 每调取一次数据,flage加一CommonData.flag++}
}//卡片列表数据模型
export interface CardListItemData {id: number// 标题title: ResourceStr// 内容content: ResourceStr// 图标icon?: Resource// 是否喜爱favour?: boolean
}/** 只接收卡片数据 = 卡片id+卡片数据本身* */
//卡片本身数据模型
export class FormData {// 卡片id 会自动生成id(卡片生命周期里面有个want里面藏有id,且每一次运行id都不一样)// 生命周期一旦创建需要拿到idformId: string = ''// 时间(更新,为了之后稳定触发@Watch)formTime: string = ''// 图片相关iamgeItem?: ImageItem// 索引index?: number = 0//卡片信息列表cardList: Array<CardListItemData> = []// 是否喜爱isFavor?:boolean = falseconstructor(formId: string) {this.formId = formId}
}// 定义类需要初始化(constructor)
@Observed
export class ImageItem {id: number = 0image: ResourceStr = ''isFavor: boolean = falseconstructor(id: number, image: ResourceStr, isFavor: boolean) {this.id = idthis.image = imagethis.isFavor = isFavor}
}
卡片功能的实现
EntryAbility
import { AbilityConstant, UIAbility, Want } from '@kit.AbilityKit';
import { hilog } from '@kit.PerformanceAnalysisKit';
import { window } from '@kit.ArkUI';
import { rpc } from '@kit.IPCKit';
import { CommonData, FormData } from '../common/CommonData';
import { formBindingData, formProvider } from '@kit.FormKit';
import { BusinessError } from '@kit.BasicServicesKit';export default class EntryAbility extends UIAbility {// 构建程序应用于卡片之间数据的桥梁(在桥梁上将数据准备好)// 数据处理/** @ohos.rpc(rpc通信)* 该模块提供进程间通信的能力,包括设备内的进程通信IPC 和设备间的进程间通信RPC* *//** 在RPC或者IPC过程中,发送方使用MessageSequence提供写方法* 接收方使用MessageSequence提供的读方法从该对象中读取特定格式的数据* */// 1:卡片的更新的接收方触发的回调private oneCardData_bridge_call = (data: rpc.MessageSequence) => {// readString 从MessageSequence读取字符串值// JSON.parse 将数据转为对象格式,同时还有解码的功能// params:有卡片id和messagelet params: Record<string, string> = JSON.parse(data.readString())console.log(`<<<<EntryAbility页面,接收卡片方数据为${JSON.stringify(params)}`)console.log(`<<<<EntryAbility页面,接收方的卡片id为:${params.formId}`)// 如果接收卡片数据中有ID存在if (params.formId !== undefined) {// 定义变量接收卡片IDlet formId: string = params.formId// 只有formId使用了构建器,只可以对其进行初始化,其他只能进行修改// 定义卡片数据,并将接收的卡片ID进行初始化赋值let formData = new FormData(formId)// 将常量卡片数据赋值给FormData对象formData.cardList = CommonData.getData()// 赋值完毕后,将flage++,为下一次赋值做准备CommonData.changeFlage()// 此时为普通数据,需要转换为卡片数据,然后进行卡片数据的更新// 3this.updateFormData(formId,formData)// 需要的类型return new MyParcelable()}return new MyParcelable()}// 更新卡片数据,将普通数据转变为卡片数据// 2private updateFormData(formId: string, formData: FormData) {//formBindingData 卡片数据绑定模块,提供卡片数据绑定的能力 包括对象创建相关信息描述//FormBindingData 卡片要展示的数据,可以是若干键值对的Object或者json格式的字符串let formMsg: formBindingData.FormBindingData = formBindingData.createFormBindingData(formData)/** formProvider模块提供了卡片提供方相关接口的能力* 通过该模块实现更新卡片,设置卡片时间,获取卡片信息,请求发布卡片* updateForm 更新指定卡片 formId 卡片标识,来自卡片创建时的生命周期want* */// 更新卡片数据,localStage里面会进行存储formProvider.updateForm(formId,formMsg).then(()=>{console.log('更新卡片数据成功')}).catch((err:BusinessError)=>{console.log(`<<<更新卡片失败${err.message}`)})}onCreate(want: Want, launchParam: AbilityConstant.LaunchParam): void {hilog.info(0x0000, 'testTag', '%{public}s', 'Ability onCreate');// 订阅数据 callee(UIAbility中的订阅方式)/** 此处的回调函数需要返回的类型为CalleeCallback* 返回值类型rpc.Parcelable* this.oneCardData_bridge_call 接收卡片返回过来的值** this.callee.on 相当于拦截器,会订阅一切想找updateCardInfo的,拦截到了,触发callback回调** */// on 订阅 of 取消订阅// 4this.callee.on('updateCardInfo',this.oneCardData_bridge_call)}onDestroy(): void {hilog.info(0x0000, 'testTag', '%{public}s', 'Ability onDestroy');}onWindowStageCreate(windowStage: window.WindowStage): void {// Main window is created, set main page for this abilityhilog.info(0x0000, 'testTag', '%{public}s', 'Ability onWindowStageCreate');// 视频横竖屏播放。获取屏幕安全区域,并将安全区域高度存入AppStorage中let windowClass:window.Window = windowStage.getMainWindowSync()// getWindowAvoidArea 获取当前窗口内容规避区域,系统栏,刘海屏,手势,软键盘等可能与窗口内容重叠需要内容避让的区域// TYPE_SYSTEM 系统默认区域 包含状态栏,导航栏等let area:window.AvoidArea = windowClass.getWindowAvoidArea(window.AvoidAreaType.TYPE_SYSTEM)// 将获取到的顶部避让区域的高度存入AppStorageAppStorage.setOrCreate('statusBarHeight',px2vp(area.topRect.height))// 'pages/Index'windowStage.loadContent('pages/VideoDetail', (err) => {if (err.code) {hilog.error(0x0000, 'testTag', 'Failed to load the content. Cause: %{public}s', JSON.stringify(err) ?? '');return;}hilog.info(0x0000, 'testTag', 'Succeeded in loading the content.');});}onWindowStageDestroy(): void {// Main window is destroyed, release UI related resourceshilog.info(0x0000, 'testTag', '%{public}s', 'Ability onWindowStageDestroy');}onForeground(): void {// Ability has brought to foregroundhilog.info(0x0000, 'testTag', '%{public}s', 'Ability onForeground');}onBackground(): void {// Ability has back to backgroundhilog.info(0x0000, 'testTag', '%{public}s', 'Ability onBackground');}
}class MyParcelable implements rpc.Parcelable{/** 可看成序列化与反序列化* */// 封可序列对象marshalling(dataOut: rpc.MessageSequence): boolean {// throw new Error('Method not implemented.');return true}// 从MessageSequence解封此可序列对象unmarshalling(dataIn: rpc.MessageSequence): boolean {throw new Error('Method not implemented.');}}
index
/** 相关权限* KEEP_BACKGROUND_RUNNING* 允许Service Ability在后台持续运行* call事件需要提供方(应用)具备后台运行的权限* */// 卡片相对于原应用相当于是两个进程,but卡片时依托于程序的
//卡片更新的应用页面import { preferences } from '@kit.ArkData';
import { FormData, ImageItem } from '../common/CommonData';
import { formBindingData, formProvider } from '@kit.FormKit';
import { BusinessError } from '@kit.BasicServicesKit';@Entry
@Component
struct IndexPage {//获取首选项中的值@StorageLink('myPreferences') myPreferences: preferences.Preferences | undefined = undefined;//读取/存放图片@StorageLink('imageArr') imageInfoArray: ImageItem[] = [//三张轮播图图片new ImageItem(0, $r('app.media.ic_social_circle1'), false),new ImageItem(1, $r('app.media.ic_social_circle2'), false),new ImageItem(2, $r('app.media.ic_social_circle3'), false)];aboutToAppear(): void {//如果首选项中存在值if (this.myPreferences) {if (this.myPreferences.hasSync('imageArr')) {//将用户首选项中的图片读取出来this.imageInfoArray = this.myPreferences.getSync('imageArr', []) as ImageItem[];return;//如果不存在} else {//将图片存入用户首选项并做持久化处理this.myPreferences?.putSync('imageArr', this.imageInfoArray);this.myPreferences?.flush();}}}build() {Navigation(){Swiper(){ForEach(this.imageInfoArray,(item:ImageItem,index) => {//调用图片子组件ImageView({imageItem: item,isFavor: item.isFavor,index: index})},(item: ImageItem) => JSON.stringify(item))}.width('100%').borderRadius(24)}.mode(NavigationMode.Stack).title($r('app.string.EntryAbility_label')).height('100%').width('100%').margin({top: 16}).padding({left: 16,right: 16})}
}//图片视图
@Component
struct ImageView{@StorageLink('myPreferences') myPreferences: preferences.Preferences | undefined = undefined;//图片信息@ObjectLink imageItem: ImageItem;//是否喜爱@State isFavor: boolean = false;//索引index: number = 0;build() {Stack(){Image(this.imageItem.image).objectFit(ImageFit.Auto).width('100%').height('33%')//喜好图标Image(this.isFavor ? $r('app.media.ic_public_favor_filled') : $r('app.media.ic_public_favor')).height(30).aspectRatio(1).margin({right: 8,bottom: 8})}.alignContent(Alignment.BottomEnd)}
}
EntryFromAbility
// 卡片的生命周期
import { formBindingData, FormExtensionAbility, formInfo, formProvider } from '@kit.FormKit';
import { Want } from '@kit.AbilityKit';
import { CommonData, FormData } from '../common/CommonData';
import { BusinessError, systemDateTime } from '@kit.BasicServicesKit';export default class EntryFormAbility extends FormExtensionAbility {onAddForm(want: Want) {// Called to return a FormBindingData object./** 使用方(用户)创建卡片时触发,提供方需要返回卡片数据绑定类* */let formData = '';// 经过卡片数据更新之后,会往LocalStorageProp中存值if (want && want.parameters) {console.log(`<<<卡片创建生命周期${JSON.stringify(want)}`)// 卡片名称let formName: string = want.parameters[`ohos.extra.param.key.form_name`] as string// 卡片idlet formId: string = want.parameters[`ohos.extra.param.key.form_identity`] as string// 检索是否是对应的卡片 做数据发送的区分if (formName === 'widget') {let formData = new FormData(formId)// 系统时间,毫秒,从1970年1.1至今formData.formTime = systemDateTime.getTime().toString()formData.index = 110// 创建封装成formBindingData对象let formInfo: formBindingData.FormBindingData = formBindingData.createFormBindingData(formData)// Provider:卡片提供者// 会向localStage中存储formData对应数据 formTime,formId...formProvider.updateForm(formId, formInfo)return formInfo}}return formBindingData.createFormBindingData(formData);}onCastToNormalForm(formId: string) {// Called when the form provider is notified that a temporary form is successfully// converted to a normal form.}onUpdateForm(formId: string) {// Called to notify the form provider to update a specified form.}onFormEvent(formId: string, message: string) {// 由卡片行为中的message事件触发// Called when a specified message event defined by the form provider is triggered.let formData = new FormData(formId)formData.cardList = CommonData.getData()CommonData.changeFlage()let formInfo: formBindingData.FormBindingData = formBindingData.createFormBindingData(formData)// 向卡片页面中的本地存储添加内容formProvider.updateForm(formId, formInfo).then(() => {console.log(`<<<<message刷新卡片成功`)}).catch((err: BusinessError) => {console.log(err.message)})}onRemoveForm(formId: string) {// Called to notify the form provider that a specified form has been destroyed.}onAcquireFormState(want: Want) {// Called to return a {@link FormState} object.return formInfo.FormState.READY;}
};
卡片UI模块的实现
WidgetCard
- 卡片pages目录下的 WidgetCard.ets 文件
import { CardListParameter } from '../viewmodel/CardListParameter';
import { CardListItemData } from '../../common/CommonData'let storageUpdate = new LocalStorage();@Entry(storageUpdate)
@Component
struct WidgetCard {/** 动作类型。*/readonly ACTION_TYPE: string = 'router';/** 能力名称。*/readonly ABILITY_NAME: string = 'EntryAbility';/** 消息*/readonly MESSAGE: string = 'add detail';/** 高度百分比设置。*/readonly FULL_HEIGHT_PERCENT: string = '100%';/** 宽度百分比设置。*/readonly FULL_WIDTH_PERCENT: string = '100%';//读取本地存储中的卡片时间 formTime formId的值由卡片创建生命周期给予@LocalStorageProp('formTime') @Watch('onFormTimeChange') formTime: string = '';@LocalStorageProp('formId') formId: string = '';//由发送完毕后在EntryAbility的回调处给予@LocalStorageProp('cardList') cardList: Array<CardListItemData> = [];@State cardListParameter: CardListParameter = new CardListParameter($r('sys.color.ohos_id_color_background'),$r('app.string.card_list_title'), '', ImageSize.Cover, $r('app.media.logo'), false,$r('sys.color.ohos_id_color_background'), true, this.cardList.length, $r('sys.color.ohos_id_color_emphasize'),$r('app.color.list_item_count_background'), '', false);//卡片时间改变onFormTimeChange() {/** postCardAction 用于卡片内部与提供方应用间的交互* 当前支持三种类型事件 router,message,call* router* 跳转提供方指定的UIAbility* message* 自定义消息,触发后调用卡片生命周期EntryFormAbility中的onFormEvent生命周期钩子函数* call* 后台启动提供方的应用,触发后会拉起提供方应用的指定UIAbility(仅支持跳转类型为singleton的UIAbility)* 但不会调度到前台。提供方需要具备后台运行的权限* *///用于卡片内部和提供方应用间的交互,当前支持router、message和call三种类型的事件,仅在卡片中可以调用。//component 当前自定义组件的实例,通常传入this。//action/** action action的类型,支持三种预定义的类型* router:跳转到提供方应用的指定UIAbility。message:自定义消息,触发后会调用提供方FormExtensionAbility的onFormEvent()生命周期回调。* call:后台启动提供方应用。* 触发后会拉起提供方应用的指定UIAbility(仅支持launchType为singleton的UIAbility,* 即启动模式为单实例的UIAbility),但不会调度到前台。提供方应用需要具备后台运行权限* bundleName action为router / call 类型时跳转的包名。* moduleName action为router / call 类型时跳转的模块名。* abilityName action为router / call 类型时跳转的UIAbility名。* uri11+ action为router 类型时跳转的UIAbility的统一资源标识符。uri和abilityName同时存在时,abilityName优先。* params 当前action携带的额外参数,内容使用JSON格式的键值对形式。* */console.log(`Widget页面时间发生了改变${this.formId}`)postCardAction(this, {action: 'call',abilityName: 'EntryAbility',// 传递的参数 准备需要发往EntryAbility的数据params: {formId: this.formId,// 与EntryAbility中this.callee.on里面的标识符进行匹配method: 'updateCardInfo',message: '更新卡片.' //自定义要发送的message}});}//卡片按钮@BuilderbuttonBuilder(text: ResourceStr, action: string, message: string, method?: string) {Column() {//刷新图标Image($r('app.media.refresh')).width($r('app.float.refresh_image_size')).height($r('app.float.refresh_image_size'))//文字Text(text).fontColor($r('app.color.refresh_color')).fontSize($r('app.float.item_content_font_size')).margin({ top: $r('app.float.text_image_space') })}.justifyContent(FlexAlign.Center).height($r('app.float.refresh_area_height')).width($r('app.float.refresh_area_width')).borderRadius($r('app.float.border_radius')).backgroundColor($r('sys.color.comp_background_focus'))//触发点击方法需要创建动态卡片.onClick(() => {postCardAction(this, {action: action,abilityName: 'EntryAbility',params: {formId: this.formId,method: method,message: message}});})}//卡片列表@BuildercardListBuilder() {if (this.cardList.length > 0) {Column() {Column() {ForEach(this.cardList, (item: CardListItemData) => {ListItem() {Row() {Column() {Text(item.title).maxLines(1).textOverflow({ overflow: TextOverflow.Ellipsis }).fontSize($r('app.float.item_content_font_size')).fontWeight(FontWeight.Medium).fontColor(Color.Black).height($r('app.float.item_text_height')).margin({ top: $r('app.float.item_text_margin') })Text(item.content).maxLines(1).fontSize($r('app.float.item_content_font_size')).textOverflow({ overflow: TextOverflow.Ellipsis }).fontWeight(FontWeight.Regular).height($r('app.float.item_text_height'))Divider().strokeWidth(0.38).lineCap(LineCapStyle.Square).margin({ top: $r('app.float.list_divider_margin') }).visibility(item.id === 4 ? Visibility.None : Visibility.Visible)}.margin({ right: $r('app.float.list_row_margin') }).alignItems(HorizontalAlign.Start).layoutWeight(1)Image(item.icon).width($r('app.float.item_image_size')).height($r('app.float.item_image_size')).borderRadius($r('app.float.border_radius'))}.alignItems(VerticalAlign.Center).width(this.FULL_WIDTH_PERCENT)}.width(this.FULL_WIDTH_PERCENT).height($r('app.float.item_height'))}, (item: number, index) => index + JSON.stringify(item))}Row() {this.buttonBuilder($r('app.string.router'), 'router', 'Router refresh card.')this.buttonBuilder($r('app.string.call'), 'call', 'Call refresh card.', 'updateCardInfo')this.buttonBuilder($r('app.string.message'), 'message', 'Message refresh card.')}.width(this.FULL_WIDTH_PERCENT).justifyContent(FlexAlign.SpaceBetween)}.height(this.FULL_HEIGHT_PERCENT).justifyContent(FlexAlign.SpaceBetween)}}build() {Row() {this.cardListBuilder()}.height(this.FULL_HEIGHT_PERCENT).onClick(() => {postCardAction(this, {action: this.ACTION_TYPE,abilityName: this.ABILITY_NAME,params: {message: this.MESSAGE}});})}
}
结尾
功能实现思路
技术栈实现
EntryAbility中
-
编写自定义的箭头函数onCardData_bridge_call(返回值rpc.Parcelable,接收的参数为data: rpc.MessageSequence)来更新卡片
-
private oneCardData_bridge_call = (data: rpc.MessageSequence) => {// readString 从MessageSequence读取字符串值// JSON.parse 将数据转为对象格式,同时还有解码的功能// params:有卡片id和messagelet params: Record<string, string> = JSON.parse(data.readString())console.log(`<<<<EntryAbility页面,接收卡片方数据为${JSON.stringify(params)}`)console.log(`<<<<EntryAbility页面,接收方的卡片id为:${params.formId}`)// 如果接收卡片数据中有ID存在if (params.formId !== undefined) {// 定义变量接收卡片IDlet formId: string = params.formId// 定义卡片数据,并将接收的卡片ID进行初始化赋值let formData = new FormData(formId)// 将常量卡片数据赋值给FormData对象formData.cardList = CommonData.getData()// 赋值完毕后,将flage++,为下一次赋值做准备CommonData.changeFlage()// 此时为普通数据,需要转换为卡片数据,然后进行卡片数据的更新this.updateFormData(formId,formData)// 需要的类型return new MyParcelable()}return new MyParcelable()}
-
-
onCardData_bridge_call接收卡片发送过来的卡片id(formId)将其赋值到自定义的卡片对象(FormData)中,通过自定义的updateFormData函数,将普通类型转变味卡片类型数据
-
updateFormData函数调用 formProvider.updateForm进行卡片数据更新,同时也会将数据存储到LocalStage中
-
// 更新卡片数据,将普通数据转变为卡片数据private updateFormData(formId: string, formData: FormData) {//formBindingData 卡片数据绑定模块,提供卡片数据绑定的能力 包括对象创建相关信息描述//FormBindingData 卡片要展示的数据,可以是若干键值对的Object或者json格式的字符串let formMsg: formBindingData.FormBindingData = formBindingData.createFormBindingData(formData)/** formProvider模块提供了卡片提供方相关接口的能力* 通过该模块实现更新卡片,设置卡片时间,获取卡片信息,请求发布卡片* updateForm 更新指定卡片 formId 卡片标识,来自卡片创建时的生命周期want* */// 更新卡片数据,localStage里面会进行存储formProvider.updateForm(formId,formMsg).then(()=>{console.log('更新卡片数据成功')}).catch((err:BusinessError)=>{console.log(`<<<更新卡片失败${err.message}`)})}
-
-
调用EntryAbility中的 this.callee.on方法进行数据拦截
-
// 订阅数据 callee(UIAbility中的订阅方式)/** 此处的回调函数需要返回的类型为CalleeCallback* 返回值类型rpc.Parcelable* this.oneCardData_bridge_call 接收卡片返回过来的值** this.callee.on 相当于拦截器,会订阅一切想找updateCardInfo的,拦截到了,触发callback回调** */// on 订阅 of 取消订阅this.callee.on('updateCardInfo',this.oneCardData_bridge_call)
-
EntryFormAbility中
-
卡片创建触发 onAddForm,并将卡片id存储在want中
-
读取want中卡片的id和formName,进行判断后获取系统时间,将formId和formTime封装,通过 formProvider.updateForm进行卡片更新且向LocalStage中存储
-
onAddForm(want: Want) {// Called to return a FormBindingData object./** 使用方(用户)创建卡片时触发,提供方需要返回卡片数据绑定类* */let formData = '';// 经过卡片数据更新之后,会往LocalStorageProp中存值if (want && want.parameters) {console.log(`<<<卡片创建生命周期${JSON.stringify(want)}`)// 卡片名称let formName: string = want.parameters[`ohos.extra.param.key.form_name`] as string// 卡片idlet formId: string = want.parameters[`ohos.extra.param.key.form_identity`] as string// 检索是否是对应的卡片 做数据发送的区分if (formName === 'widget') {let formData = new FormData(formId)// 系统时间,毫秒,从1970年1.1至今formData.formTime = systemDateTime.getTime().toString()formData.index = 110// 创建封装成formBindingData对象let formInfo: formBindingData.FormBindingData = formBindingData.createFormBindingData(formData)// Provider:卡片提供者// 会向localStage中存储formData对应数据 formTime,formId...formProvider.updateForm(formId, formInfo)return formInfo}}return formBindingData.createFormBindingData(formData);}
-
WidgetCard中
-
向 LocalStage获取数据,对formTime进行@Watch监听
-
若时间发生变化 使用postCardAction对卡片内部和提供方进行交互
-
//卡片时间改变onFormTimeChange() {//用于卡片内部和提供方应用间的交互,当前支持router、message和call三种类型的事件,仅在卡片中可以调用。//component 当前自定义组件的实例,通常传入this。//action/** action action的类型,支持三种预定义的类型* router:跳转到提供方应用的指定UIAbility。message:自定义消息,触发后会调用提供方FormExtensionAbility的onFormEvent()生命周期回调。* call:后台启动提供方应用。* 触发后会拉起提供方应用的指定UIAbility(仅支持launchType为singleton的UIAbility,* 即启动模式为单实例的UIAbility),但不会调度到前台。提供方应用需要具备后台运行权限* bundleName action为router / call 类型时跳转的包名。* moduleName action为router / call 类型时跳转的模块名。* abilityName action为router / call 类型时跳转的UIAbility名。* uri11+ action为router 类型时跳转的UIAbility的统一资源标识符。uri和abilityName同时存在时,abilityName优先。* params 当前action携带的额外参数,内容使用JSON格式的键值对形式。* */console.log(`Widget页面时间发生了改变${this.formId}`)postCardAction(this, {action: 'call',abilityName: 'EntryAbility',// 传递的参数 准备需要发往EntryAbility的数据params: {formId: this.formId,// 与EntryAbility中this.callee.on里面的标识符进行匹配method: 'updateCardInfo',message: '更新卡片.' //自定义要发送的message}});}
-
-
在build中进行卡片UI的渲染
相关文章:
HarmonyOS基于ArkTS卡片服务
卡片服务 前言 Form Kit(卡片开发框架)提供了一种在桌面、锁屏等系统入口嵌入显示应用信息的开发框架和API,可以将应用内用户关注的重要信息或常用操作抽取到服务卡片(以下简称“卡片”)上,通过将卡片添加…...
Elasticsearch 性能测试工具 Loadgen 之 001——部署及应用详解
在现代软件开发中,性能测试是确保应用程序稳定性和响应速度的关键环节。 今天,我们就来深入了解一款国产化功能强大的 Elasticsearch 负载测试工具——INFINI Loadgen。 一、INFINI Loadgen 简介 Github地址:https://github.com/infinilabs/l…...
Python算法详解:动态规划
动态规划(Dynamic Programming,简称 DP)是计算机科学中一种高效解决问题的算法思想。它通过将复杂问题分解为更小的子问题,记录中间结果,避免重复计算,从而提升效率。本文将从动态规划的基础思想出发&#…...
python3+TensorFlow 2.x(二) 回归模型
目录 回归算法 1、线性回归 (Linear Regression) 一元线性回归举例 2、非线性回归 3、回归分类 回归算法 回归算法用于预测连续的数值输出。回归分析的目标是建立一个模型,以便根据输入特征预测目标变量,在使用 TensorFlow 2.x 实现线性回归模型时&…...
lombok 没生效 java: 找不到符号 符号: 方法 setName(java.lang.String)
今天使用lombok 添加了 Data注解 set方法却没起效 解决方法 1 给lombok 添加版本号 再maven刷新下 <dependency><groupId>org.projectlombok</groupId><artifactId>lombok</artifactId><version>1.18.8</version><optional>…...
uiautomator2教程
一、简介 uiautomator2 是一个 Python 库,用于 Android 的 UI 自动化测试,底层基于 Google uiautomator。 二、安装 1、安装adb 2、pip install uiautomator2 3、设备安装 atx - agent,python -m uiautomator2 init 4、安装weditor&…...
旅游风景的代码项目
敦煌莫高窟:用代码打开千年艺术的大门 ——一个零基础也能看懂的神奇项目 前言:当古老艺术遇上现代代码 想象一下,你坐在电脑前,指尖轻轻一点,就能穿越到敦煌莫高窟——看飞天的衣袂飘飘、听千年的驼铃声声。这不是科…...
【后端开发】字节跳动青训营之性能分析工具pprof
性能分析工具pprof 一、测试程序介绍二、pprof工具安装与使用2.1 pprof工具安装2.2 pprof工具使用 资料链接: 项目代码链接实验指南pprof使用指南 一、测试程序介绍 package mainimport ("log""net/http"_ "net/http/pprof" // 自…...
【测试】-- 认识测试
1. 软件测试定义 软件测试就是验证软件产品特性(功能、性能、界面、易用性等)是否满足用户的需求。 2. 测试的岗位 软件测试开发工程师(测开) 开发:开发测试效率工具(自动化、性能测试、覆盖率等&#x…...
浏览器hid 和蓝牙bluetooth技术区别
HID与蓝牙技术区别 引言 在前端开发中,与外部设备的交互越来越重要,尤其是在移动设备和物联网设备日益普及的今天。HID(Human Interface Device)和蓝牙(Bluetooth)是两种常用的技术,用于实现设备…...
PCIE模式配置
对于VU系列FPGA,当DMA/Bridge Subsystem for PCI Express IP配置为Bridge模式时,等同于K7系列中的AXI Memory Mapped To PCI Express IP。...
mysql 学习3 SQL语句--整体概述。SQL通用语法;DDL创建数据库,查看数据库,删除数据库,使用数据库;
SQL通用语法 SQL语句分类 DDL data definition language : 用来创建数据库,创建表,创建表中的字段,创建索引。因此成为 数据定义语言 DML data manipulation language 有了数据库和表以及字段后,那么我们就需要给这个表中 添加数…...
Swing使用MVC模型架构
什么是MVC模式? MVC是一组英文的缩写,其全名是Model-View-Controller,也就是“模型-视图-控制器”这三个部分组成。这三个部分任意一个部分发生变化都会引起另外两个发生变化。三者之间的关系示意图如下所示: MVC分为三个部分,所以在MVC模型中将按照此三部分分成三…...
Java定时任务实现方案(二)——ScheduledExecutorService
这篇笔记,我们要来介绍实现Java定时任务的第二个方案,使用ScheduledExecutorService,以及该方案的优点和缺点。 ScheduledExecutorService是Java并发包java.util.concurrent中用于执行定时任务和周期性任务的接口,它拓展了Executo…...
Agent群舞,在亚马逊云科技搭建数字营销多代理(Multi-Agent)(下篇)
在本系列的上篇中,小李哥为大家介绍了如何在亚马逊云科技上给社交数字营销场景创建AI代理的方案,用于社交动态的生成和对文章进行推广曝光。在本篇中小李哥将继续本系列的介绍,为大家介绍如何创建主代理,将多个子代理挂载到主代理…...
Leecode刷题C语言之收集所有金币可获得的最大积分
执行结果:通过 执行用时和内存消耗如下: int dfs(int node, int parent, int f, int* coins, int k, int **children, int *childCount, int **memo) {if (memo[node][f] ! -1) {return memo[node][f];}int res0 (coins[node] >> f) - k;int res1 coins[no…...
STM32_SD卡的SDIO通信_基础读写
本篇将使用CubeMXKeil, 创建一个SD卡读写的工程。 目录 一、SD卡要点速读 二、SDIO要点速读 三、SD卡座接线原理图 四、CubeMX新建工程 五、CubeMX 生成 SD卡的SDIO通信部分 六、Keil 编辑工程代码 七、实验效果 实现效果,如下图: 一、SD卡 速读…...
新手理解:Android 中 Handler 和 Thread.sleep 的区别及应用场景
新手理解:Android 中 Handler 和 Thread.sleep 的区别及应用场景 Handler 是啥?Handler 的几个核心功能: Thread.sleep 是啥?Thread.sleep 的核心特点: 两者的区别它们的应用场景1. Handler 的应用场景2. Thread.sleep…...
C语言-----扫雷游戏
扫雷游戏的功能说明 : • 使⽤控制台实现经典的扫雷游戏 • 游戏可以通过菜单实现继续玩或者退出游戏 • 扫雷的棋盘是9*9的格⼦ • 默认随机布置10个雷 • 可以排查雷: ◦ 如果位置不是雷,就显⽰周围有⼏个雷 ◦ 如果位置是雷,就…...
监控与调试:性能优化的利器 — ShardingSphere
在分布式数据库系统中,监控和调试是确保系统高效运行的关键。ShardingSphere 提供了多种监控和调试工具,帮助开发者实时跟踪和优化性能,识别瓶颈,进行故障排查,从而提升系统的稳定性和响应速度。本文将介绍如何使用 Sh…...
Kubernetes相关知识入门详解
一、Pod的滚动升级 1.服务升级的一般思路:停止与该服务相关的所有服务pod,重新拉去更新后的镜像并启动。这种方法存在一个比较现实的问题是逐步升级导致较长时间的服务不可用。 2.Kubernetes滚动升级的思路:通过滚动升级的命令创建新的rc&…...
多层 RNN原理以及实现
数学原理 多层 RNN 的核心思想是堆叠多个 RNN 层,每一层的输出作为下一层的输入,从而逐层提取更高层次的抽象特征。 1. 单层 RNN 的数学表示 首先,单层 RNN 的计算过程如下。对于一个时间步 t t t,单层 RNN 的隐藏状态 h t h_t…...
Unity阿里云OpenAPI 获取 Token的C#【记录】
获取Token using UnityEngine; using System; using System.Text; using System.Linq; using Newtonsoft.Json.Linq; using System.Security.Cryptography; using UnityEngine.Networking; using System.Collections.Generic; using System.Globalization; using Cysharp.Thr…...
java+vue项目部署记录
目录 前言 一、java和vue 二、部署记录 1.获取代码 2.运行前端 3.运行后端 三、其他 1.nvm 总结 前言 近期工作需要部署一套javavue前后分离的项目,之前都略有接触,但属于不及皮毛的程度,好在对其他开发语言、html js这些还算熟&am…...
PID 控制算法(二):C 语言实现与应用
在本文中,我们将用 C 语言实现一个简单的 PID 控制器,并通过一个示例来演示如何使用 PID 控制算法来调整系统的状态(如温度、速度等)。同时,我们也会解释每个控制参数如何影响系统的表现。 什么是 PID 控制器…...
深入MapReduce——计算模型设计
引入 通过引入篇,我们可以总结,MapReduce针对海量数据计算核心痛点的解法如下: 统一编程模型,降低用户使用门槛分而治之,利用了并行处理提高计算效率移动计算,减少硬件瓶颈的限制 优秀的设计,…...
在Spring Boot中使用SeeEmitter类实现EventStream流式编程将实时事件推送至客户端
😄 19年之后由于某些原因断更了三年,23年重新扬帆起航,推出更多优质博文,希望大家多多支持~ 🌷 古之立大事者,不惟有超世之才,亦必有坚忍不拔之志 🎐 个人CSND主页——Mi…...
Qt实践:一个简单的丝滑侧滑栏实现
Qt实践:一个简单的丝滑侧滑栏实现 笔者前段时间突然看到了侧滑栏,觉得这个抽屉式的侧滑栏非常的有趣,打算这里首先尝试实现一个简单的丝滑侧滑栏。 首先是上效果图 (C,GIF帧率砍到毛都不剩了) QProperty…...
基于ESP32-IDF驱动GPIO输出控制LED
基于ESP32-IDF驱动GPIO输出控制LED 文章目录 基于ESP32-IDF驱动GPIO输出控制LED一、点亮LED3.1 LED电路3.2 配置GPIO函数gpio_config()原型和头文件3.3 设置GPIO引脚电平状态函数gpio_set_level()原型和头文件3.4 代码实现并编译烧录 一、点亮LED 3.1 LED电路 可以看到&#x…...
OpenCV文字绘制支持中文显示
OpenCV版本:4.4 IDE:VS2019 功能描述 OpenCV绘制文本的函数putText()不支持中文的显示,网上很多方法推荐的都是使用FreeType来支持,FreeType是什么呢?FreeType的官网上有介绍 FreeType官网 https://www.freetype.or…...
jenkins-k8s pod方式动态生成slave节点
一. 简述: 使用 Jenkins 和 Kubernetes (k8s) 动态生成 Slave 节点是一种高效且灵活的方式来管理 CI/CD 流水线。通过这种方式,Jenkins 可以根据需要在 Kubernetes 集群中创建和销毁 Pod 来执行任务,从而充分利用集群资源并实现更好的隔离性…...
消息队列篇--基础篇(消息队列特点,应用场景、点对点和发布订阅工作模式,RabbmitMQ和Kafka代码示例等)
1、消息队列的介绍 消息(Message)是指在应用之间传送的数据,消息可以非常简单,比如只包含文本字符串,也可以更复杂,可能包含嵌入对象。 消息队列(Message Queue,简称MQ)…...
Jetpack架构组件学习——使用Glance实现桌面小组件
基本使用 1.添加依赖 添加Glance依赖: // For AppWidgets supportimplementation "androidx.glance:glance-appwidget:1.1.0"// For interop APIs with Material 3implementation "androidx.glance:glance-material3:1.1.0"// For interop APIs with Mater…...
go读取excel游戏配置
1.背景 游戏服务器,配置数据一般采用csv/excel来作为载体,这种方式,策划同学配置方便,服务器解析也方便。在jforgame框架里,我们使用以下的excel配置格式。 然后可以非常方便的进行数据检索,例如ÿ…...
Linux系统下速通stm32的clion开发环境配置
陆陆续续搞这个已经很久了。 因为自己新电脑是linux系统无法使用keil,一开始想使用vscode里的eide但感觉不太好用;后面想直接使用cudeide但又不想妥协,想趁着这个机会把linux上的其他单片机开发配置也搞明白;而且非常想搞懂cmake…...
快慢指针及原理证明(swift实现)
目录 链表快慢指针一、快慢指针基本介绍二、快慢指针之找特殊节点1.删除链表的倒数第k个结点题目描述解题思路 2.链表的中间节点题目描述解题思路 三、快慢指针之环形问题1.判断环形链表题目描述解题思路 2.判断环形链表并返回入环节点题目描述解题思路 3.变种——判断快乐数题…...
web前端3--css
注意(本文一切代码一律是在vscode中书写) 1、书写位置 1、行内样式 //<标签名 style"样式声明"> <p style"color: red;">666</p> 2、内嵌样式 1、style标签 里面写css代码 css与html之间分离 2、css属性:值…...
一文大白话讲清楚webpack基本使用——5——babel的配置和使用
文章目录 一文大白话讲清楚webpack基本使用——5——babel的配置和使用1. 建议按文章顺序从头看,一看到底,豁然开朗2. babel-loader的配置和使用2.1 针对ES6的babel-loader2.2 针对typescript的babel-loader2.3 babel配置文件 一文大白话讲清楚webpack基…...
Python自动化运维:一键掌控服务器的高效之道
《Python OpenCV从菜鸟到高手》带你进入图像处理与计算机视觉的大门! 解锁Python编程的无限可能:《奇妙的Python》带你漫游代码世界 在互联网和云计算高速发展的今天,服务器数量的指数增长使得手动运维和管理变得异常繁琐。Python凭借其强大的可读性和丰富的生态系统,成为…...
基于quartz,刷新定时器的cron表达式
文章目录 前言基于quartz,刷新定时器的cron表达式1. 先看一下测试效果2. 实现代码 前言 如果您觉得有用的话,记得给博主点个赞,评论,收藏一键三连啊,写作不易啊^ _ ^。 而且听说点赞的人每天的运气都不会太差&…...
HTML常用属性
HTML标签的常见属性包括许多不同的功能,可以为元素提供附加信息或控制元素的行为。以下是一些常见的属性及其解释: 1. src 描述:src(source)属性指定一个资源的路径,通常用于图像、音频、视频等标签。常见…...
在 Babylon.js 中使用 Gizmo:交互式 3D 操作工具
在 3D 应用程序中,交互式操作对象(如平移、旋转、缩放)是一个常见的需求。Babylon.js 提供了一个强大的工具——Gizmo,用于在 3D 场景中实现这些功能。本文将介绍如何在 Babylon.js 中使用 Gizmo,并展示如何通过代码实…...
蓝桥杯练习日常|递归-进制转换
蓝桥云课760数的计算 一、递归 题目: 我的解题代码: #include <iostream> using namespace std; int sum0; int main() {// 请在此输入您的代码int n;cin>>n;int fun(int n);fun(n); cout<<sum<<\n;return 0; } // void fu…...
LabVIEW滤波器选择与参数设置
在信号处理应用中,滤波器是去除噪声、提取目标信号的重要工具。LabVIEW 提供多种类型的滤波器(如低通、高通、带通、带阻),用户需要根据采样频率、信号特性和应用需求合理选择滤波器类型及参数设置。本文以 采样率 100kHz…...
【c语言日寄】Vs调试——新手向
【作者主页】siy2333 【专栏介绍】⌈c语言日寄⌋:这是一个专注于C语言刷题的专栏,精选题目,搭配详细题解、拓展算法。从基础语法到复杂算法,题目涉及的知识点全面覆盖,助力你系统提升。无论你是初学者,还是…...
C#中的Timers.Timer使用用法及常见报错
System.Timers.Timer 是一个基于服务器的计时器,它可以在应用程序中定期触发事件。这个计时器特别适合用于多线程环境,并且不应该与用户界面(UI)直接交互。在 ASP.NET 中,通常使用 System.Timers.Timer 来处理周期性的任务。 主要使用步骤&am…...
chrome小插件:长图片等分切割
前置条件: 安装有chrome谷歌浏览器的电脑 使用步骤: 1.打开chrome扩展插件 2.点击管理扩展程序 3.加载已解压的扩展程序 4.选择对应文件夹 5.成功后会出现一个扩展小程序 6.点击对应小程序 7.选择图片进行切割,切割完成后会自动保存 代码…...
mysql数据被误删的恢复方案
文章目录 一、使用备份恢复二、使用二进制日志(Binary Log)三、使用InnoDB表空间恢复四、使用第三方工具预防措施 数据误删是一个严重的数据库管理问题,但通过合理的备份策略和使用适当的恢复工具,可以有效地减少数据丢失的风险…...
K8S-Pod资源清单的编写,资源的增删改查,镜像的下载策略
1. Pod资源清单的编写 1.1 Pod运行单个容器的资源清单 ##创建工作目录 mkdir -p /root/manifests/pods && cd /root/manifests/pods vim 01-nginx.yaml ##指定api版本 apiVersion: v1 ##指定资源类型 kind: Pod ##指定元数据 metadata:##指定名称name: myweb ##用户…...
Unity Line Renderer Component入门
Overview Line Renderer 组件是 Unity 中用于绘制连续线段的工具。它通过在三维空间中的两个或两个以上的点的数组,并在每个点之间绘制一条直线。可以绘制从简单的直线到复杂的螺旋线等各种图形。 1. 连续性和独立线条 连续性:Line Renderer 绘制的线条…...