OpenHarmony开发案例:【电影卡片】

embedded/2024/9/24 23:21:42/

 介绍

本篇Codelab基于元服务卡片的能力,实现带有卡片的电影应用,介绍卡片的开发过程和生命周期实现。需要完成以下功能:

  1. 元服务卡片,用于在桌面上添加2x2或2x4规格元服务卡片。
  2. 关系型数据库,用于创建、查询、添加、删除卡片数据。

相关概念

  • [关系型数据库]:关系型数据库基于SQLite组件提供了一套完整的对本地数据库进行管理的机制,对外提供了一系列的增、删、改、查等接口,也可以直接运行用户输入的SQL语句来满足复杂的场景需要。

  • [元服务卡片]:卡片是一种界面展示形式,可以将应用的重要信息或操作前置到卡片,以达到服务直达、减少体验层级的目的。

    • 卡片提供方:显示卡片内容,控制卡片布局以及控件点击事件。
    • 卡片使用方:显示卡片内容的宿主应用,控制卡片在宿主中展示的位置。
    • 卡片管理服务:用于管理系统中所添加卡片的常驻代理服务,包括卡片对象的管理与使用,以及卡片周期性刷新等。

环境搭建

软件要求

  • [DevEco Studio]版本:DevEco Studio 3.1 Release。
  • OpenHarmony SDK版本:API version 9。
  • 鸿蒙开发>鸿蒙开发参考文档:qr23.cn/AKFP8k点击或者复制转到。

硬件要求

  • 开发板类型:[润和RK3568开发板]。
  • OpenHarmony系统:3.2 Release。

环境搭建

  1. [获取OpenHarmony系统版本]:标准系统解决方案(二进制)。以3.2 Release版本为例:

  2. 搭建烧录环境。

    1. [完成DevEco Device Tool的安装]
    2. [完成RK3568开发板的烧录]
  3. 搭建开发环境。

    1. 开始前请参考[工具准备],完成DevEco Studio的安装和开发环境配置。
    2. 开发环境配置完成后,请参考[使用工程向导]创建工程(模板选择“Empty Ability”)。
    3. 工程创建完成后,选择使用[真机进行调测]。

代码结构解读

本篇Codelab只对核心代码进行讲解,对于完整代码,我们会在gitee中提供。

├──entry/src/main/ets            // 代码区     
│  ├──common  
│  │  ├──constants
│  │  │  ├──CommonConstants.ets  // 常量类
│  │  │  └──StyleConstants.ets   // 格式常量类
│  │  ├──datasource
│  │  │  ├──DataSource.ets       // 懒加载数据源
│  │  │  └──MovieListData.ets    // 电影列表数据 
│  │  └──utils
│  │     ├──CommonUtils.ets      // 数据操作工具类  
│  │     ├──GlobalContext.ets    // 全局上下文工具类
│  │     └──Logger.ets           // 日志打印工具类
│  ├──detailsability
│  │  └──EntryDetailsAbility.ets // 电影详情入口类
│  ├──entryability
│  │  └──EntryAbility.ets        // 程序入口类
│  ├──entryformability
│  │  └──EntryFormAbility.ets    // 卡片创建,更新,删除操作类
│  ├──pages
│  │  ├──MovieDetailsPage.ets    // 电影详情页
│  │  └──MovieListPage.ets       // 主页面
│  ├──view
│  │  ├──MovieDetailsTitle.ets   // 电影详情头部组件
│  │  ├──MovieItem.ets           // 列表item组件
│  │  ├──MovieStarring.ets       // 电影主演组件
│  │  ├──MovieStills.ets         // 电影剧照组件
│  │  ├──StarsWidget.ets         // 电影评分组件
│  │  └──StoryIntroduce.ets      // 电影简介组件
│  └──viewmodel
│     ├──FormBean.ets            // 卡片对象
│     ├──FormDataBean.ets        // 卡片数据对象
│     └──MovieDataBean.ets       // 电影数据对象
├──entry/src/main/js             // js代码区
│  ├──card2x2                    // 2x2卡片目录
│  ├──card2x4                    // 2x4卡片目录
│  └──common                     // 卡片资源目录
└──entry/src/main/resources      // 资源文件目录

搜狗高速浏览器截图20240326151344.png

关系型数据库

元服务卡片需要用数据库保存不同卡片数据,而且在添加多张卡片情况下,需要保持数据同步刷新。因此需要创建一张表,用于保存卡片信息。

  1. 数据库创建使用的SQLite。

    // CommonConstants.ets
    // 创建数据库表结构
    static readonly CREATE_TABLE_FORM: string = 'CREATE TABLE IF NOT EXISTS Form ' +'(id INTEGER PRIMARY KEY AUTOINCREMENT, formId TEXT NOT NULL, formName TEXT NOT NULL, dimension INTEGER)';

  2. 在EntryAbility的onCreate方法通过CommonUtils.createRdbStore方法创建数据库,并创建相应的表。

    // EntryAbility.ets
    export default class EntryAbility extends UIAbility {onCreate(want: Want, launchParam: AbilityConstant.LaunchParam) {...// 创建数据库CommonUtil.createRdbStore(this.context);}
    }// CommonUtils.ets
    import relationalStore from '@ohos.data.relationalStore';async createRdbStore(context: Context) {let rdbStore = GlobalContext.getContext().getObject('rdbStore') as relationalStore.RdbStore;if (this.isEmpty(rdbStore)) {rdbStore = await relationalStore.getRdbStore(context, CommonConstants.STORE_CONFIG);if (!this.isEmpty(rdbStore)) {rdbStore.executeSql(CommonConstants.CREATE_TABLE_FORM).catch((error: Error) => {Logger.error(CommonConstants.TAG_COMMON_UTILS, 'executeSql error ' + JSON.stringify(error));});GlobalContext.getContext().setObject('rdbStore', rdbStore);}}return rdbStore;
    }

构建应用页面

电影卡片应用有两个页面,分别是电影列表和电影详情。

电影列表

电影列表采用Column容器嵌套List和自定义组件MovieItem形式完成页面整体布局,效果如图所示:

 
// MovieListPage.ets
build() {Column() {...List({ space: StyleConstants.LIST_COMPONENT_SPACE }) {LazyForEach(this.dataSource, (item: MovieDataBean) => {ListItem() {// 电影itemMovieItem({ movieItem: item });}}, (item: MovieDataBean) => JSON.stringify(item))}...}...
}// MovieItem.ets
aboutToAppear() {if (CommonUtils.isEmpty(this.movieItem)) {Logger.error(CommonConstants.TAG_MOVIE_ITEM, 'movieItem is null');return;}// 获取电影索引this.sort = this.movieItem.sort;...
}build() {Row(){...Text($r('app.string.want_to_see'))....onClick(() => {router.pushUrl({url: CommonConstants.SEE_BUTTON_PUSH,params: {index: this.sort}}).catch((error: Error) => {...});})}...
}

电影详情

电影详情采用Column容器嵌套自定义组件MovieDetailsTitle、StoryIntroduce、MovieStarring和MovieStills形式完成页面整体布局,效果如图所示:

 
// MovieDetailPage.ets
aboutToAppear() {let index: number = 0;let params = router.getParams() as Record<string, Object>;if (!CommonUtils.isEmpty(params)) {index = params.index as number;} else {let position = GlobalContext.getContext().getObject('position') as number;index = position ?? 0;}let listData: MovieDataBean[] = CommonUtils.getListData();if (CommonUtils.isEmptyArr(listData)) {Logger.error(CommonConstants.TAG_DETAILS_PAGE, 'listData is 0');return;}this.movieData = listData[index];this.introduction = listData[index].introduction;
}build() {Column() {...Column() {// 电影详情头部组件MovieDetailsTitle({movieDetail: this.movieData})// 剧情简介组件StoryIntroduce({introduction: this.introduction})}...// 电影主演组件MovieStarring()// 电影剧照组件MovieStills()}...
}

元服务卡片

使用元服务卡片分为四步:创建、初始化、更新、删除。

创建元服务卡片目录

  1. 在main目录下,点击鼠标右键 > New > Service Widget。

  2. 然后选择第一个选项下面带有Hello World字样,点击下一步Next。

  3. 填写卡片名字(Service widget name)、卡片介绍(Description)、是否开启低代码开发(Enable Super Visual)、开发语言(ArkTS和JS)、支持卡片规格(Support dimension)、关联表单(Ability name)点击Finish完成创建。如需创建多个卡片目录重新按照步骤1执行。

  4. 创建完卡片后,同级目录出现js目录,然后开发者在js目录下使用hml+css+json开发js卡片页面。

初始化元服务卡片

应用选择添加元服务卡片到桌面后,在EntryFormAbility的onAddForm方法进行卡片初始化操作,效果如图所示:

 
// EntryFormAbility.ets
onAddForm(want: Want) {if (want.parameters === undefined) {return formBindingData.createFormBindingData();}let formId: string = want.parameters[CommonConstants.IDENTITY_KEY] as string;let formName: string = want.parameters[CommonConstants.NAME_KEY] as string;let dimensionFlag: number = want.parameters[CommonConstants.DIMENSION_KEY] as number;CommonUtils.createRdbStore(this.context).then((rdbStore: relationalStore.RdbStore) => {let form: FormBean = new FormBean();form.formId = formId;form.formName = formName;form.dimension = dimensionFlag;CommonUtils.insertForm(form, rdbStore);}).catch((error: Error) => {Logger.error(CommonConstants.TAG_FORM_ABILITY, 'onAddForm create rdb error ' + JSON.stringify(error));});let listData: MovieDataBean[] = CommonUtils.getListData();let formData = CommonUtils.getFormData(listData);return formBindingData.createFormBindingData(formData);
}

更新元服务卡片

  1. 初始化加载电影列表布局之前,在MovieListPage的aboutToAppear方法中,通过CommonUtils.startTimer方法开启定时器,时间到则调用updateMovieCardData方法更新电影卡片数据。

    // MovieListPage.ets
    aboutToAppear() {...// 启动定时器,每5分钟更新一次电影卡片数据。CommonUtils.startTimer();
    }// CommonUtils.ets
    startTimer() {let intervalId = GlobalContext.getContext().getObject('intervalId') as number;if (this.isEmpty(intervalId)) {intervalId = setInterval(() => {let rdbStore = GlobalContext.getContext().getObject('rdbStore') as relationalStore.RdbStore;this.updateMovieCardData(rdbStore);}, CommonConstants.INTERVAL_DELAY_TIME);}GlobalContext.getContext().setObject('intervalId', intervalId);
    }// 更新电影卡片数据
    updateMovieCardData(rdbStore: relationalStore.RdbStore) {if (this.isEmpty(rdbStore)) {Logger.error(CommonConstants.TAG_COMMON_UTILS, 'rdbStore is null');return;}let predicates: relationalStore.RdbPredicates = new relationalStore.RdbPredicates(CommonConstants.TABLE_NAME);rdbStore.query(predicates).then((resultSet: relationalStore.ResultSet) => {if (resultSet.rowCount <= 0) {Logger.error(CommonConstants.TAG_COMMON_UTILS, 'updateCardMovieData rowCount <= 0');return;}let listData: MovieDataBean[] = this.getListData();resultSet.goToFirstRow();do {let formData = this.getFormData(listData);let formId: string = resultSet.getString(resultSet.getColumnIndex(CommonConstants.FORM_ID));formProvider.updateForm(formId, formBindingData.createFormBindingData(formData)).catch((error: Error) => {Logger.error(CommonConstants.TAG_COMMON_UTILS, 'updateForm error ' + JSON.stringify(error));});} while (resultSet.goToNextRow());resultSet.close();}).catch((error: Error) => {Logger.error(CommonConstants.TAG_COMMON_UTILS, 'updateCardMovieData error ' + JSON.stringify(error));});

  2. 通过src/main/resources/base/profile/form_config.json配置文件,根据updateDuration或者scheduledUpdateTime字段配置刷新时间。updateDuration优先级高于scheduledUpdateTime,两者同时配置时,以updateDuration配置的刷新时间为准。当配置的刷新时间到了,系统调用onUpdateForm方法进行更新。

    // form_config.json
    {// 卡片的类名"name": "card2x2",// 卡片的描述"description": "This is a service widget.",// 卡片对应完整路径 "src": "./js/card2x2/pages/index/index",// 定义与显示窗口相关的配置"window": {"designWidth": 720,"autoDesignWidth": true},// 卡片的主题样式"colorMode": "auto",// 是否为默认卡片"isDefault": true,// 卡片是否支持周期性刷新"updateEnabled": true,// 采用24小时制,精确到分钟"scheduledUpdateTime": "00:00",// 当取值为0时,表示该参数不生效,当取值为正整数N时,表示刷新周期为30*N分钟。"updateDuration": 1,// 卡片默认外观规格"defaultDimension": "2*2",// 卡片支持外观规格"supportDimensions": ["2*2"]
    }
    ...// EntryFormAbility.ets
    onUpdateForm(formId: string) {CommonUtils.createRdbStore(this.context).then((rdbStore: relationalStore.RdbStore) => {CommonUtils.updateMovieCardData(rdbStore);}).catch((error: Error) => {...});...
    }

删除元服务卡片

当用户需要删除元服务卡片时,可以在EntryFormAbility的onRemoveForm方法中,通过CommonUtils.deleteFormData方法删除数据库中对应的卡片信息。

// EntryFormAbility.ets
onRemoveForm(formId: string) {CommonUtils.createRdbStore(this.context).then((rdbStore: relationalStore.RdbStore) => {// 从数据库中删除电影卡片信息CommonUtils.deleteFormData(formId, rdbStore);}).catch((error: Error) => {...});
}// CommonUtils.ets
deleteFormData(formId: string, rdbStore: relationalStore.RdbStore) {...let predicates: relationalStore.RdbPredicates = new relationalStore.RdbPredicates(CommonConstants.TABLE_NAME);predicates.equalTo(CommonConstants.FORM_ID, formId);rdbStore.delete(predicates).catch((error: Error) => {...});
}

鸿蒙语言有TS、ArkTS等语法,那么除了这些基础知识之外,其核心技术点有那些呢?下面就用一张整理出的鸿蒙学习路线图表示:

从上面的OpenHarmony技术梳理来看,鸿蒙的学习内容也是很多的。现在全网的鸿蒙学习文档也是非常的少,下面推荐一些:完整内容可在头像页保存,或这qr23.cn/AKFP8k甲助力

内容包含:《鸿蒙NEXT星河版开发学习文档》

  • ArkTS
  • 声明式ArkUI
  • 多媒体
  • 通信问题
  • 系统移植
  • 系统裁剪
  • FW层的原理
  • 各种开发调试工具
  • 智能设备开发
  • 分布式开发等等。

这些就是对往后开发者的分享,希望大家多多点赞关注喔!


http://www.ppmy.cn/embedded/9159.html

相关文章

前端实用插件-日期处理工具Moment.js

安装和引入Moment.js 在开始使用Moment.js之前&#xff0c;我们需要将其引入到项目中。可以通过以下两种方式来安装和引入Moment.js&#xff1a; 使用CDN 在HTML文件的<head>标签中添加以下代码&#xff1a; <script src"https://cdn.jsdelivr.net/momentjs/…

设计模式-代理模式

代理模式 代理模式&#xff0c;为其他对象提供一种代理以控制对这个对象的访问。 在一些开源框架或中间件产品中&#xff0c;代理模式会非常常见。使用的时候越简便&#xff0c;框架在背后做的事就可能越复杂。这里面往往都体现着代理模式的应用&#xff0c;颇有移花接木的味…

《Spring》系列文章目录

Spring Framework是一个为基于Java的现代企业应用程序提供全面编程和配置模型的开源框架。它集成了控制反转&#xff08;IOC&#xff09;、依赖注入&#xff08;DI&#xff09;和面向切面编程&#xff08;AOP&#xff09;等容器技术。Spring框架的设计理念是面向Bean编程&#…

Github账号注册

天行健&#xff0c;君子以自强不息&#xff1b;地势坤&#xff0c;君子以厚德载物。 每个人都有惰性&#xff0c;但不断学习是好好生活的根本&#xff0c;共勉&#xff01; 文章均为学习整理笔记&#xff0c;分享记录为主&#xff0c;如有错误请指正&#xff0c;共同学习进步。…

Android 自定义SwitchPreference

1. 为SwitchPreference 添加背景&#xff1a;custom_preference_background.xml <?xml version"1.0" encoding"utf-8"?> <selector xmlns:android"http://schemas.android.com/apk/res/android"><item><shape android:s…

直播回顾|6个实例带你解读TinyVue 组件库跨框架技术

在本期《手把手教你实现mini版TinyVue组件库》的主题直播中&#xff0c;华为云前端开发DTSE技术布道师阿健老师给开发者们展开了组件库跨框架的讨论&#xff0c;同时针对TinyVue组件库的关键技术进行了剖析&#xff0c;并通过项目实战演示了一份源码编译出2个不同Vue 框架的组件…

UUPSUpgradeable部署合约和升级合约

文章目录 写一个合约1. 使用代理部署 并添加拥有者2. 没有name number为103. 使用代理升级部署 填写上面代理的合约地址4. 合约地址没有变&#xff0c;但是添加了name&#xff0c;并且保存了number的属性值 写一个合约 // SPDX-License-Identifier: MIT // Compatible with Op…

Vue 常用修饰符

目录 定义 事件修饰符 v-on&#xff08;缩写&#xff09; 鼠标按键修饰符 v-bind 修饰符&#xff08;缩写:&#xff09; 键值修饰符 表单修饰符 定义 修饰符是用于限定类型以及类型成员的声明的一种符号&#xff0c;vue 中修饰符分为以下五种&#xff1a;事件修饰符、鼠…