赞
踩
作为一个文艺青年,偶尔会有想要表达一下自己心情的需求,这时候鸿蒙系统的桌面卡片就非常合适。
简单设想一下,这是一个常驻桌面、能显示个性文案的元服务。如果用户对文案内容不满意的话可以手动切换文案。由于每次只能单条刷新很不方便,因此还需要一个应用界面,里面能够分类显示很多不同的文案,便于快速选择,只要手动点击某一条文案,就能直接显示到桌面卡片上。
使用端云一体化开发可以很容易的实现这个效果
效果展示:
每日走心文案阶段性功能演示
简单介绍一下端云一体化开发:
作为一个独立开发者,由于时间和开发能力的限制,想要独立开发一个应用程序其实还是挺难的,微博、B站那种功能复杂又高大上的应用自然不必说,很多时候连简单的应用也会受限于前后端开发的差异,学前端的不会写后端、不会配置服务器,学后端的搞不懂前端三件套,让人搞得一头雾水,项目未半而中道崩卒。
DevEco的端云一体化开发工具能让应用开发变得非常简单,前后端语法统一,前端用基于JS衍生的ArkTS,后端能直接用Node.js,语法高度相似。
severless不需要手动安装各种服务器软件,也不需要进行大量复杂难懂的系统配置。直接新建项目就能让客户端自动连接上severless,并且服务器常用的登录、后端环境、数据库、文件存储系统、日志系统他都有。突出一个简单快捷,傻瓜式操作。
文案可以统一放在云数据库,使用云数据库的接口来查询。
桌面卡片的刷新事件主要靠postCardAction接口的message事件来实现,
点击跳转应用靠postCardAction接口的router事件来实现
FormAbility和应用页面点击后刷新卡片的功能依靠formProvider.updateForm接口来实现。
为了能管理多张应用卡片,避免出现明明没有卡片但应用提示刷新成功,已经明明有应用卡片但应用找不到对应的FormID导致卡片不刷新的情况,又需要一个能记录所有FormID的函数。
关于postCardAction接口和formProvider.updateForm接口可以参考以下文章:
第一步:在AGC中新建项目,新建应用。
如果不会创建端云一体化工程的话可以参考一下这篇文章:【纯新手向】手把手带你使用模板创建第一个端云一体化元服务
第二步:按照项目需求开通对应的服务。
由于这个案例非常的简单,登陆服务、云函数、云存储都用不上,只要有一个云数据库就够了。在这里就只开通云数据库。
完成云数据库的基本配置
存储区命名为sloganZone
云数据配置好以后导出对象类型的js文件。后续会用到。
手动录入一些数据以便后续开发中进行测试
配置完成后新建本地工程。(强烈建议新手先完成severless的各种配置再新建本地项目,因为建立本地项目时会自动下载与severless配置情况对应的agconnect-services.json与schema.json文件。改动severless配置后需要手动更新这两个文件,否则可能会无法运行。)
最终的项目结构如下:
开发桌面卡片的代码
let storage=new LocalStorage()//页面级存储,靠它才能卡片内容的跨文件刷新 @Entry(storage) @Component struct WidgetCard { @LocalStorageProp('formId') @Watch('sentFormID') formId: string = '0';//保存FormID的变量 @LocalStorageProp('slogan') slogan:string='我希望有个如你一般的人。如山间清爽的风,如古城温暖的光,由清晨到傍晚,由山野到书房,只要最后是你,就好。'//卡片的默认文案 sentFormID(){ console.log('发送formID') postCardAction(this,{//call事件,可以后台启动UIAbility并执行预设的事件。 'action':'call', "abilityName": 'EntryAbility', 'params': { 'method': 'createFormId', 'formId': this.formId, 'detail': '' } }) } build() { Stack() { Image($r("app.media.background1")) .width('100%') .height('100%') Column() { Row(){ Image($r("app.media.logo_white")) .objectFit(ImageFit.Contain) .width('30vp') Text('每日走心文案') .fontColor(Color.White) } .height('20%') .width('100%') .justifyContent(FlexAlign.Start) Row(){ Text(this.slogan) .fontColor(Color.White) .width('80%') Column(){ Image($r('app.media.freshButton')) .height('35%') .objectFit(ImageFit.Contain) .padding(5) } .onClick(()=>{ postCardAction(this,{//message事件,用于启动FormAbility并执行预设的事件。 'action':'message', 'params': { 'msgTest': 'messageEvent' } }) }) .padding(5) .margin(5) .backgroundColor(Color.Gray) .borderRadius(50) .justifyContent(FlexAlign.Center) .alignItems(HorizontalAlign.Center) } .height('80%') .width('100%') .justifyContent(FlexAlign.Center) } .width('100%') .height('100%') .padding($r('app.float.column_padding')) } .width('100%') .height('100%') .onClick(() => { postCardAction(this, {//router事件,可以跳转到UIAbiliity,同时也可以执行UIAbiliity中预设的事件 "action": "router", "abilityName": 'EntryAbility', "params": { "message": '' } }); }) } }
import formInfo from '@ohos.app.form.formInfo'; import formBindingData from '@ohos.app.form.formBindingData'; import FormExtensionAbility from '@ohos.app.form.FormExtensionAbility'; import formProvider from '@ohos.app.form.formProvider'; import agconnect from '@hw-agconnect/api-ohos'; import { AGConnectCloudDB, CloudDBZone, CloudDBZoneConfig, CloudDBZoneQuery } from '@hw-agconnect/database-ohos'; import {dailySlogan} from'../models/dailySlogan' export default class EntryFormAbility extends FormExtensionAbility { objectTypeInfo=null cloudZone:CloudDBZone=null cloud:AGConnectCloudDB=null onAddForm(want) {//want中包含了FormId等信息,在卡片创建时读取并更新,以便卡片执行call时使用 let formId = want.parameters["ohos.extra.param.key.form_identity"]; let dataObj1 = { "formId": formId }; let obj1 = formBindingData.createFormBindingData(dataObj1); console.log('formId向卡片发送'+formId) return obj1; } async onFormEvent(formId, message) {//当message事件触发时,查询数据库并刷新卡片 // Called when a specified message event defined by the form provider is triggered. try { agconnect.instance().init(this.context)//初始化agc } catch (error){} try { await AGConnectCloudDB.initialize(this.context)//初始化AGConnectCloudDB } catch (error){} if (this.cloud == null) { this.cloud = await AGConnectCloudDB.getInstance()//获取实例 } if (this.objectTypeInfo == undefined) {//查询并构建云数据库的数据类型 const value = await this.context.resourceManager.getRawFile('schema.json'); let json = ""; for (var i = 0; i < value.length; i++) { json += String.fromCharCode(value[i]); } this.objectTypeInfo = JSON.parse(json); this.cloud.createObjectType(this.objectTypeInfo); } if (this.cloudZone == null) { this.cloudZone = await this.cloud.openCloudDBZone(new CloudDBZoneConfig("sloganZone"))//打开存储区 } const sql = CloudDBZoneQuery.where(dailySlogan) .limit(1, Math.floor(Math.random() * 9)) //用于查询的sql语句 this.cloudZone.executeQuery(sql).then((sloganInfo) => { let info:dailySlogan[]=sloganInfo.getSnapshotObjects()//处理查询结果 let slogan=info[0].Slogan let formData = { 'slogan': slogan, // 此处需要和卡片页面的变量相对应 }; let formInfo = formBindingData.createFormBindingData(formData) formProvider.updateForm(formId, formInfo).then((data) => {//刷新卡片 console.info('FormAbility updateForm success.' + JSON.stringify(data)); }).catch((error) => { console.error('FormAbility updateForm failed: ' + JSON.stringify(error)); }) }).catch((error) => { console.error('数据查询失败: ' + JSON.stringify(error)); }) } onRemoveForm(formId) { // Called to notify the form provider that a specified form has been destroyed. } onAcquireFormState(want) { // Called to return a {@link FormState} object. return formInfo.FormState.READY; } };
import agconnect from '@hw-agconnect/api-ohos'; import { AGConnectCloudDB, CloudDBZone, CloudDBZoneConfig, CloudDBZoneQuery } from '@hw-agconnect/database-ohos'; import {dailySlogan} from'../models/dailySlogan' import formBindingData from '@ohos.app.form.formBindingData'; import formProvider from '@ohos.app.form.formProvider'; import formInfo from '@ohos.app.form.formInfo'; @Entry @Component struct homepage{ @State sloganList:Array<string>=[]//保存文案列表的变量 objectTypeInfo=null cloudZone:CloudDBZone=null cloud:AGConnectCloudDB=null aboutToAppear(){ this.fresh() } async fresh(){//用于查询新文案的代码,与FormAbility中的基本一致 try { try { agconnect.instance().init(getContext(this)) } catch (error){} try { await AGConnectCloudDB.initialize(getContext(this)) } catch (error){} if (this.cloud == null) { this.cloud = await AGConnectCloudDB.getInstance() } if (this.objectTypeInfo == undefined) { const value = await getContext(this).resourceManager.getRawFile('schema.json'); let json = ""; for (var i = 0; i < value.length; i++) { json += String.fromCharCode(value[i]); } this.objectTypeInfo = JSON.parse(json); this.cloud.createObjectType(this.objectTypeInfo); } if (this.cloudZone == null) { this.cloudZone = await this.cloud.openCloudDBZone(new CloudDBZoneConfig("sloganZone")) } const sql = CloudDBZoneQuery.where(dailySlogan) .limit(4, Math.floor(Math.random() * 9)) this.cloudZone.executeQuery(sql).then((sloganInfo) => { let info: dailySlogan[] = sloganInfo.getSnapshotObjects() this.sloganList = [] for (var i = 0;i < info.length; i++) { this.sloganList.push(info[i].Slogan) } }).catch((error) => { console.error('数据查询失败: ' + JSON.stringify(error)); }) } catch (error){ console.error('刷新异常: ' + JSON.stringify(error)); } } build(){ List(){ ListItem(){ Button('刷新文案') .onClick(()=>{ this.fresh() }) } ForEach(this.sloganList,(listInfo)=>{ ListItem(){ Column(){ Text(listInfo) } .width('90%') .height('20%') .backgroundImage($r('app.media.background1')) .margin({top:5,bottom:5}) .justifyContent(FlexAlign.Center) .alignItems(HorizontalAlign.Center) .onClick(async()=>{//判断是否存在卡片并根据用户点击的文案来刷新这些卡片 if(AppStorage.Has('formId')){ let formIdList:Array<string>=AppStorage.Get('formId') let formData = { "slogan": listInfo }; let formMsg = formBindingData.createFormBindingData(formData) console.log('开始刷新') for(let i=0;i<formIdList.length;i++){ try { await formProvider.updateForm(formIdList[i], formMsg) } catch (error){ if(error.code==16501001){//formId都不存在的错误码 console.log('formID是'+formIdList[i]) formIdList[i]=' ' } } } let newFormIdList=[] for(var i=0;i<formIdList.length;i++){ if(formIdList[i]!=' '){ console.log('检测成功的formID是'+formIdList[i]) newFormIdList.push(formIdList[i]) }else{ console.log(i+'id值已清空'+formIdList[i]) } } if(newFormIdList.length==0){ console.log('卡片已被清空') AppStorage.Delete('formId') AlertDialog.show({ message:'卡片不存在,请重新添加卡片' }) } else{ AppStorage.SetOrCreate('formId',newFormIdList) AlertDialog.show({ message:'卡片刷新已完成' }) } }else { AlertDialog.show({ message:'请先添加卡片' }) } }) } },) } .width('100%') .height('100%') .alignListItem(ListItemAlign.Center) } }
import hilog from '@ohos.hilog'; import UIAbility from '@ohos.app.ability.UIAbility'; import Window from '@ohos.window'; import abilityAccessCtrl from '@ohos.abilityAccessCtrl'; import formInfo from '@ohos.app.form.formInfo'; import formBindingData from '@ohos.app.form.formBindingData'; import formProvider from '@ohos.app.form.formProvider'; function saveFormId(data){//保存新增的FormId的函数 console.log('监听事件已激活') let FormIdList=[] if(AppStorage.Has('formId')){ FormIdList=AppStorage.Get('formId') } let params = JSON.parse(data.readString()) if (params.formId !== undefined) { let FormId = params.formId; console.log('读取到formId') FormIdList.push(FormId) AppStorage.SetOrCreate('formId', FormIdList) for(var i=0;i<FormIdList.length;i++){ console.log('formID保存成功') console.log(FormIdList[i]) } } else { console.log('formId读取失败') } return null } export default class EntryAbility extends UIAbility { onCreate() { this.callee.on('createFormId',saveFormId);//应用启动时监听call事件 let AtManager = abilityAccessCtrl.createAtManager(); AtManager.requestPermissionsFromUser(this.context, ['ohos.permission.READ_MEDIA', 'ohos.permission.MEDIA_LOCATION']).then((data) => { hilog.info(0x0000, 'testTag', '%{public}s', 'request permissions from user success' + data); }).catch((err) => { hilog.error(0x0000, 'testTag', 'Failed to request permissions from user. Cause: %{public}s', JSON.stringify(err) ?? ''); }); hilog.info(0x0000, 'testTag', '%{public}s', 'Ability onCreate'); } onDestroy() { hilog.info(0x0000, 'testTag', '%{public}s', 'Ability onDestroy'); this.callee.off('createFormId');//应用销毁时解除监听call事件 } }
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。