HarmonyOS实战开发-实现带有卡片的电影应用

news2024/11/14 6:04:26

介绍

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

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

相关概念

  • 关系型数据库:关系型数据库基于SQLite组件提供了一套完整的对本地数据库进行管理的机制,对外提供了一系列的增、删、改、查等接口,也可以直接运行用户输入的SQL语句来满足复杂的场景需要。
  • 元服务卡片:卡片是一种界面展示形式,可以将应用的重要信息或操作前置到卡片,以达到服务直达、减少体验层级的目的。卡片提供方:显示卡片内容,控制卡片布局以及控件点击事件。卡片使用方:显示卡片内容的宿主应用,控制卡片在宿主中展示的位置。卡片管理服务:用于管理系统中所添加卡片的常驻代理服务,包括卡片对象的管理与使用,以及卡片周期性刷新等。

环境搭建

软件要求

  • DevEco Studio版本:DevEco Studio 3.1 Release。
  • OpenHarmony SDK版本:API version 9。

硬件要求

  • 开发板类型:润和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只对核心代码进行讲解。

├──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      // 资源文件目录

关系型数据库

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

  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() {
          // 电影item
          MovieItem({ 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) => {
    ...
  });
}

总结

您已经完成了本次Codelab的学习,并了解到以下知识点:

  1. 使用关系型数据库插入、更新、删除卡片数据。
  2. 使用FormExtensionAbility创建、更新、删除元服务卡片。

为了帮助大家更深入有效的学习到鸿蒙开发知识点,小编特意给大家准备了一份全套最新版的HarmonyOS NEXT学习资源,获取完整版方式请点击→《HarmonyOS教学视频

HarmonyOS教学视频:语法ArkTS、TypeScript、ArkUI等.....视频教程

鸿蒙生态应用开发白皮书V2.0PDF:

获取完整版白皮书方式请点击→《鸿蒙生态应用开发白皮书V2.0PDF

鸿蒙 (Harmony OS)开发学习手册

一、入门必看

  1. 应用开发导读(ArkTS)
  2. ……

二、HarmonyOS 概念

  1. 系统定义
  2. 技术架构
  3. 技术特性
  4. 系统安全
  5. ........

三、如何快速入门?《做鸿蒙应用开发到底学习些啥?》

  1. 基本概念
  2. 构建第一个ArkTS应用
  3. ……

四、开发基础知识

  1. 应用基础知识
  2. 配置文件
  3. 应用数据管理
  4. 应用安全管理
  5. 应用隐私保护
  6. 三方应用调用管控机制
  7. 资源分类与访问
  8. 学习ArkTS语言
  9. ……

五、基于ArkTS 开发

  1. Ability开发
  2. UI开发
  3. 公共事件与通知
  4. 窗口管理
  5. 媒体
  6. 安全
  7. 网络与链接
  8. 电话服务
  9. 数据管理
  10. 后台任务(Background Task)管理
  11. 设备管理
  12. 设备使用信息统计
  13. DFX
  14. 国际化开发
  15. 折叠屏系列
  16. ……

更多了解更多鸿蒙开发的相关知识可以参考:《鸿蒙 (Harmony OS)开发学习手册

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1551931.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

LLMOps与传统的MLOps有何不同?

引言&#xff1a; 随着AI技术的日新月异&#xff0c;大语言模型&#xff08;LLM&#xff09;已经成为推动企业增长和创新的关键驱动力。然而&#xff0c;在实际应用中&#xff0c;要想充分发挥大模型的潜力&#xff0c;还需要克服众多挑战&#xff0c;包括语料的精准标注与处理…

如何利用生成式人工智能挑选合适的候选人?

在当今激烈的商业竞争中&#xff0c;招聘合适的人才是构建企业成功的基石。筛选和面试候选人是一个复杂且精细的过程&#xff0c;它不仅关系到职位的有效填补&#xff0c;更影响到企业的长期发展和团队建设。 选择合适候选人的重要性 选择合适的候选人就像寻找一片沙滩上的珍…

洛谷_P1223 排队接水_python写法

P1223 排队接水 - 洛谷 | 计算机科学教育新生态 (luogu.com.cn) n int(input()) data list(map(int,input().split())) data.insert(0,0)li [] for i, elem in enumerate(data):li.append([i,elem]) li.sort(keylambda x:x[1])for i in range(1,n1):print(li[i][0],end )pri…

【Leetcode】单链表常见题

&#x1f525;个人主页&#xff1a;Quitecoder &#x1f525;专栏&#xff1a;Leetcode刷题 本节内容我们来讲解常见的几道单链表的题型&#xff0c;文末会赋上单链表增删查&#xff0c;初始化等代码 目录 1.移除链表元素2.链表的中间节点3.返回倒数第K个节点&#xff1a;4.环…

物理查询优化(二):两表连接算法(附具体案例及代码分析)

前言 关系代数的一项重要操作是连接运算&#xff0c;多个表连接是建立在两表之间连接的基础上的。研究两表连接的方式&#xff0c;对连接效率的提高有着直接的影响。 连接方式是一个什么样的概念&#xff0c;或者说我们为何要有而且有好几种&#xff0c;对于不太了解数据库的人…

19. 变量

文章目录 一、变量二、变量的定义格式 一、变量 变量&#xff1a;程序中临时存储数据的容器&#xff0c;在程序执行过程中&#xff0c;其值有可能发生改变的量&#xff08;数据&#xff09;。但是这个容器中只能存一个值。 应用场景&#xff1a;在我们登录页面的时候&#xf…

java注解的实现原理

首先我们常用的注解是通过元注解去编写的&#xff0c; 比如&#xff1a; 元注解有Target 用来限定目标注解所能标注的java结构&#xff0c;比如标注方法&#xff0c;标注类&#xff1b; Retention则用来标注当前注解的生命周期&#xff1b;比如source&#xff0c;class&…

开源 | 电动汽车充换电解决方案,从智能硬件到软件系统,全部自主研发

文章目录 一、产品功能部分截图1.手机端&#xff08;小程序、安卓、ios&#xff09;2.PC端 二、小程序体验账号以及PC后台体验账号1.小程序体验账号2.PC后台体验账号关注公众号获取最新资讯 三、产品简介&#xff1f;1. 充电桩云平台&#xff08;含硬件充电桩&#xff09;&…

Java项目:78 springboot学生宿舍管理系统的设计与开发

作者主页&#xff1a;源码空间codegym 简介&#xff1a;Java领域优质创作者、Java项目、学习资料、技术互助 文中获取源码 项目介绍 系统的角色&#xff1a;管理员、宿管、学生 管理员管理宿管员&#xff0c;管理学生&#xff0c;修改密码&#xff0c;维护个人信息。 宿管员…

Python 垃圾回收和弱引用(Weakref)

Python中的赋值语句是建立变量名与对象的引用关系&#xff0c;多个变量可以引用同一个对象&#xff0c;当对象的引用数归零时&#xff0c;可能会被当作垃圾回收。而弱引用即可以引用对象&#xff0c;又不会阻止对象被当作垃圾回收&#xff0c;因此这个特性非常适合用在缓存场景…

精灵传信系统 匿名性系统 支持网站+小程序双端源码

精灵传信支持在线提交发送短信&#xff0c;查看回复短信&#xff0c;在线购买额度&#xff0c;自定义对接易支付&#xff0c;设置违禁词&#xff0c;支持网站小程序双端。 项目 地 址 &#xff1a; runruncode.com/php/19720.html 环境要求: PHP > 73 MySQL>5.6 Ngi…

网上兼职赚钱攻略:六种方式让你轻松上手

在互联网时代&#xff0c;网上兼职已经成为一种非常流行的赚钱方式。对于许多想要在家里挣钱的人来说&#xff0c;网上兼职不仅可以提供灵活的工作时间&#xff0c;还可以让他们在自己的兴趣领域中寻求机会&#xff0c;实现自己的财务自由。 在这里&#xff0c;我将为您介绍六…

基于Java仓库管理系统设计与实现(源码+部署文档+论文)

博主介绍&#xff1a; ✌至今服务客户已经1000、专注于Java技术领域、项目定制、技术答疑、开发工具、毕业项目实战 ✌ &#x1f345; 文末获取源码联系 &#x1f345; &#x1f447;&#x1f3fb; 精彩专栏 推荐订阅 &#x1f447;&#x1f3fb; 不然下次找不到 Java项目精品实…

【每日跟读】常用英语500句(200~300)

【每日跟读】常用英语500句 Home sweet home. 到家了 show it to me. 给我看看 Come on sit. 过来坐 That should do nicely. 这样就很好了 Get dressed now. 现在就穿衣服 If I were you. 我要是你 Close your eyes. 闭上眼睛 I don’t remember. 我忘了 I’m not su…

排序C++

题目 法1 sort升序排序&#xff0c;再逆序输出 #include<iostream> #include<algorithm> using namespace std;const int N 5e53;//注意const&#xff0c;全局 int a[N]; int main() {//错误int N5e53;//错误const int a[N];int n;cin >> n;for (int i 1;…

用Python机器学习模型预测世界杯结果靠谱吗?

看到kaggle、medium上有不少人用球队的历史数据来进行建模预测&#xff0c;比如用到泊松分布、决策树、逻辑回归等算法&#xff0c;很大程度上能反映强者恒强的现象&#xff0c;比如巴西、英格兰等大概率能进8强&#xff0c;就像高考模拟考试成绩越好&#xff0c;大概率高考也会…

内网穿透_ICMP_icmpsh

目录 一、ICMP协议详解 二、ICMP隧道 (一) 为什么会使用ICMP (二) 实验环境 (三) 操作流程 1. 下载icmpsh 2. 下载并安装依赖 3. 关闭本地icmp响应 4. 攻击机启动服务端开始监听 5. 靶机启动工具客户端 6. 攻击机接受到靶机传来的数据 三、郑重声明 一、ICMP协议详…

论文《Exploring to Prompt for Vision-Language Models》阅读

论文《Exploring to Prompt for Vision-Language Models》阅读 论文概况论文动机&#xff08;Intro&#xff09;MethodologyPreliminaryCoOp[CLASS]位置Context 是否跨 class 共享表示和训练 ExperimentsOverall ComparisonDomain GeneralizationContext Length (M) 和 backbon…

RAFT:让大型语言模型更擅长特定领域的 RAG 任务

RAFT&#xff08;检索增强的微调&#xff09;代表了一种全新的训练大语言模型&#xff08;LLMs&#xff09;以提升其在检索增强生成&#xff08;RAG&#xff09;任务上表现的方法。“检索增强的微调”技术融合了检索增强生成和微调的优点&#xff0c;目标是更好地适应各个特定领…

解决 vue activited 无效问题

当对页面APP.vue组件router-view标签使用了keep-alive之后在组件activated状态时不会发送请求&#xff0c;这时需要使用 keep-alive标签的 exclude属性排除需要重新发送请求的组件。需要注意exclude的值要和组件本身的name值要一致&#xff0c;如果不一致就会不生效。目前我出现…