前言

最近HarmonyOS NEXT大火,这个纯血鸿蒙吸引力了大家的关注。虽然现在还没面向个人开发者开放,但我们可以基于最新的API9及开发工具来尝试开发鸿蒙新的应用形态——元服务。来体验下未来在HarmonyOS NEXT上实现的应用开发。

HarmonyOS是华为公司开发的操作系统,它的设计理念是面向未来的全场景智慧体验,可在各种设备上运行,包括手机、平板电脑、智能手表、智能音箱等。HarmonyOS采用分布式技术,可以将不同设备之间的计算资源连接起来,实现设备间的协同工作,提高系统的性能和稳定性。此外,HarmonyOS还拥有高度自适应的界面、多屏协同等特性,使用户能够在不同设备上实现无缝的体验。

本文主要是基于手语学习元服务的开发案例:主要的功能有

  • 元服务内部功能:

1、提供专业手语翻译老师示范视频,包括基本手势、字母表、常用短语等。用户可以点击观看示范并模仿。
2、提供类似答题模块,帮助用户巩固所学的手语知识,用户可以完成答题模块测试自己的进步。

  • 元服务卡片:

1、卡片界面展示每日一题,并可以在卡片上进行答题学习

项目实现效果如下:

元服务内部功能视频:

元服务

元服务卡片功能视频:

新手语视频demo人卡片展示

一、HarmonyOS元服务简介

1.什么是元服务

元服务(原名为原子化服务)是HarmonyOS提供的一种面向未来的服务提供方式。它是一种新型应用程序形态,具有独立入口、免安装的特点,可以为用户提供一个或多个便捷服务。

以线上购物为例,传统购物应用需要先安装应用,打开应用查找商品,加入购物车,然后完成支付。而通过元服务的方式,可以将购物过程拆分为多个服务,例如“商品浏览”、“购物车”、“支付”等,无需安装应用,通过丰富的入口直达服务页面。例如,将心仪的商品页添加到桌面,可以实时掌握商品价格的变动。在秒杀时间点,可以直接进入购物车进行结算。

元服务基于HarmonyOS API开发,为用户在合适的场景、合适的设备上提供便捷的使用体验。相比传统的需要安装的应用形态,元服务更加轻量,同时提供更丰富的入口和更精准的分发。它为用户提供了更灵活、更高效的服务方式,提升了用户体验和便利性。

2.元服务的独特价值

元服务的呈现形态之一是鸿蒙万能卡片,它是元服务最主要的展示方式之一(其他形态包括语音、图标等)。每一个万能卡片都是一个在桌面上“永远打开的”元服务/应用,以卡片的形式展示元服务/应用的重要信息,并通过轻量级的交互行为实现服务的直达。

元服务带来了体验上的变革,具体表现在以下几个方面:

1.免安装:元服务以更轻量化的方式将服务提供给用户,无需进行繁琐的应用安装过程,节省了用户的时间和存储空间。
2.一键服务直达:元服务将用户感兴趣的内容前置、外显,通过一键操作即可直接进入所需的服务页面,提供了更快捷、便利的服务体验。
3.跨端转移:元服务支持多终端设备间的无缝流转,用户可以在不同的设备上无缝切换使用元服务,提供了更连贯、一致的服务体验。
4.情景智能卡片推荐:元服务可以根据用户的需求和偏好进行情景智能卡片推荐,用户可以随心定制自己的服务卡片,并根据个人喜好进行个性化设置,从而更好地满足用户的需求。
通过以上的变革,元服务为用户带来了更轻便、更直观、更智能的服务体验,提升了用户的便利性和满意度。

3.元服务的应用场景

🦋3.1 负一屏

负一屏旨在提供更快速便捷的信息和服务,采用了宫格设计,将常用服务进行分类,涵盖了本地生活、智慧出行、购物娱乐、金融理财等多个场景。用户通过右滑进入发现页,即可轻松地获取所需的服务,无需下载繁琐的App。此外,负一屏还具备实时状态的新体验,用户使用服务后可以随时查看进度,掌握关键节点信息。

在这里插入图片描述

🦋3.2 应用市场

打开华为应用市场,点击“应用”页签,进入“元服务”专区发现并使用元服务。

在这里插入图片描述

🦋3.3 桌面

用户可以将元服务的卡片添加到桌面,便可在桌面随时随地查看元服务的重要信息,点击卡片即可直达所需服务。

在这里插入图片描述

🦋3.4 碰一碰/扫一扫

用户首次“碰一碰”或者“扫一扫”识别设备上的NFC标签,系统引导用户连接设备,连接成功后,再次“碰一碰”或者“扫一扫”即可直接使用相应的元服务。

在这里插入图片描述
在这里插入图片描述

二、开发环境搭建

1.DevEco Studio

DevEco Studio是一款专门为鸿蒙(HarmonyOS)系统开发而设计的综合性开发工具IDE,开发者可以利用该工具进行鸿蒙应用的设计、开发、调试和发布。DevEco Studio集成了代码编辑器、模拟器、调试工具、图形用户界面设计器和应用管理工具等多个功能,方便开发者进行鸿蒙应用的开发与管理。

DevEco Studio支持多语言开发,包括Java、ArkTS、JavaScript等,同时还支持多种开发模式和框架。此外,它还支持多平台开发,包括手机、平板、智能手表、电视等不同终端设备。

DevEco Studio下载地址:https://developer.harmonyos.com/cn/develop/deveco-studio?utm_source=product&utm_medium=link&utm_campaign=DS&utm_content=3.0#download

在这里插入图片描述

安装完成之后界面:

在这里插入图片描述

2.配置环境变量

本文以window系统为例,具体操作步骤如下:

1、通过“设置 > 系统 > 系统信息 > 高级系统设置”进入“系统属性”页面的“高级”页签,点击“环境变量”
在这里插入图片描述

2、在“系统变量”中添加 HDC_SERVER_PORT和OHOS_HDC_SERVER_PORT 两个变量,变量值设置为未被占用的端口,例如7036和7037
在这里插入图片描述
在这里插入图片描述

3、在用户或者系统的path变量中,添加HDC工具的路径。
HDC工具路径为:HarmonyOS SDK安装目录/hmscore/{版本号}/toolchains。例如:C:\Users\XXXXX\AppData\Local\Huawei\Sdk\hmscore\3.1.0\toolchains

在这里插入图片描述

环境变量配置完成,重启DevEco Studio。

3.诊断开发环境

1、打开项目,从欢迎页进入:底部菜单选择“Help > Diagnose Development Environment”

在这里插入图片描述

2、待自动检查完成。如果有检查未通过的项目,请根据检查项的描述和修复建议进行处理

在这里插入图片描述

4.下载SDK

1、打开项目,从欢迎页进入:底部菜单选择“Configure > Settings”

在这里插入图片描述

三、创建元服务项目

1.创建元服务项目的步骤

1、登录AppGallery Connect, 点击“我的应用”。
首次进入需要签协议
在这里插入图片描述
在这里插入图片描述

2、在“HarmonyOS”页签,“类型”选择“元服务”,可以查看创建的元服务。

在这里插入图片描述

2.选择模板和配置项目属性

1、打开DevEco Studio,菜单选择“File > New > Create Project”,创建一个新工程。
在这里插入图片描述

2、选择“Atomic Service”,选择“Empty Ability”模板,点击“Next”。
在这里插入图片描述

3、配置工程基本信息。
•Project name:设置“myProject”。
•Bundle name:本样例以“com.huawei.myproject”为例。
•Save location:选择工程存放路径。
•Compile SDK:支持API 4~9,本样例选择“API 9”。
•Model:应用支持的模式,API Version 4~8只支持FA模式。
•Enable Super Visual:是否使用低代码开发模式,本样例不打开此开关。
•Language:开发语言。
•Device type:该工程模板支持的设备类型。本样例以手机设备为例。
在这里插入图片描述

点击“Finish”,等待工程创建完成,即可进行代码编写。

3.编写代码和调试运行

1、点击右侧的Previewer工具,预览页面效果。
在这里插入图片描述

2、新建details.ets页面

details.ets页面

@Entry
@Component
struct Details {
  build() {
    //Flex容器组件
    Flex({ direction: FlexDirection.Column, alignItems: ItemAlign.Center, justifyContent: FlexAlign.Center }) {
      //Text组件
      Text('我是跳转页面')
        .fontSize(60)
        .fontWeight(500)
    }
    //容器整体宽高
    .width('100%')
    .height('100%')
  }
}
main_pages.json页面
{
  "src": [
    "pages/Index",
    "pages/details"
  ]
}

在这里插入图片描述3、添加跳转按钮实现页面跳转

index.ets页面
import router from '@ohos.router'
@Entry
@Component
struct Index {
  @State message: string = '愚公搬代码'

  build() {
    //Flex容器组件
    Flex({ direction: FlexDirection.Column, alignItems: ItemAlign.Center, justifyContent: FlexAlign.Center }) {
      //Text组件
      Text('愚公搬代码')
        .fontSize(60)
        .fontWeight(500)
      //Button组件
      Button('跳转下一页')
        .fontSize(40)
        .fontWeight(500)
        .width(280)
        .height(60)
        //点击Button实现页面跳转
        .onClick(() => {
          console.info("跳转开始")
          router.pushUrl({
            url: 'pages/details' // 目标url
          }, (err) => {
            if (err) {
              console.error(`Invoke pushUrl failed, code is ${err.code}, message is ${err.message}`);
              return;
            }
            console.info('Invoke pushUrl succeeded.');
          });
        })
    }
    //容器整体宽高
    .width('100%')
    .height('100%')
  }
}

在这里插入图片描述

四、元服务代码分析

1.元服务代码结构介绍

在这里插入图片描述

•AppScope中存放应用全局所需要的资源文件。
•entry是应用的主模块,存放HarmonyOS应用的代码、资源等。
•oh_modules是工程的依赖包,存放工程依赖的源文件。
•build-profile.json5是工程级配置信息,包括签名、产品配置等。
•hvigorfile.ts是工程级编译构建任务脚本,hvigor是基于任务管理机制实现的一款全新的自动化构建工具,主要提供任务注册编排,工程模型管理、配置管理等核心能力。
•oh-package.json5是工程级依赖配置文件,用于记录引入包的配置信息。
🦋1.1 AppScope
在这里插入图片描述
•element文件夹主要存放公共的字符串、布局文件等资源。
•media存放全局公共的多媒体资源文件。
🦋1.2 entry
在这里插入图片描述
•entryability用于当前ability应用逻辑和生命周期管理。
•entryformability用于当前卡片生命周期管理。
•pages存放UI界面相关代码文件,初始会生成一个Index页面。
•ohosTest是单元测试目录。
•build-profile.json5是模块级配置信息,包括编译构建配置项。
•hvigorfile.ts文件是模块级构建脚本。
•oh-package.json5是模块级依赖配置信息文件。
•resources目录下存放模块公共的多媒体、字符串及布局文件等资源,分别存放在element、media文件夹中。

2.开发选型

HarmonyOS 3.1及以上版本支持ArkTS、JS语言和Stage模型和FA模型两种模型,其中Stage模型为从HarmonyOS 3.1开始新增的模型,将是长期演进的模型。
HarmonyOS中的Stage模型是指应用程序的窗口管理器和图形引擎。Stage模型包含一个场景(Scene)和多个舞台(Stage),每个舞台表示一个窗口。在舞台上,可以添加多个UI元素(如Button、Label、Image等),通过对UI元素进行布局、组合和交互,实现应用程序的功能。
在HarmonyOS中,通过Stage模型,可以实现窗口管理、多任务处理、图形渲染和事件处理等功能。同时,Stage与HarmonyOS系统的分层架构紧密结合,可以自动适配不同屏幕、不同分辨率的设备,保证应用程序在各种设备上的兼容性和可用性。
一个应用包含一个或者多个Module,以下是Module与UIAbility组件关系示意图:
在这里插入图片描述
编译后的示意图:
在这里插入图片描述

3.元服务中常用的API和类

1.OHOS::AAFwk::Ability: 这是Ability的基类,提供了启动、停止和生命周期管理等能力。
2.OHOS::AAFwk::Want: 跨应用程序组件之间传递的信息,可以指定Intent和Bundle等参数。
3.OHOS::AppExecFwk::EventHandler: 用于在Ability生命周期内处理异步任务,支持延迟执行和定时执行。
4.OHOS::EventFwk::EventRunner: 用于实现异步事件的执行和处理。
5.OHOS::AGP::Window: 应用程序窗口,用于显示UI界面。
6.OHOS::AGP::View: UI界面中的基本控件,如Button、TextView等。
7.OHOS::DataAbility::DataAbilityHelper: 数据能力的基础类,用于管理数据的增删改查操作。
8.OHOS::DistributedSchedule::DistributedSchedulPolicy: 分布式调度策略,用于实现分布式系统的任务调度。
9.OHOS::Media::MediaPlayer: 用于播放音频和视频文件。
10.OHOS::Security::Permission: 权限管理类,用于管理应用程序对系统资源的访问权限。

五、案例展示:手语猜一猜

1.案例背景

手语学习元服务的案例背景主要是为了帮助人们学习手语,提高对聋人的理解和尊重。聋人由于听力障碍,无法通过声音语言进行交流,而手语是聋人交流的主要方式。手语学习元服务通过数字化的方式提供了学习手语的机会,让更多的人能够了解手语,学习手语,以便更好地与聋人交流和理解聋人的需求。
随着社会的不断发展,聋人的教育和融入社会的问题日益重要。手语学习应用可以提供数字化的手语课程,让更多的人能够学习手语,提高对聋人的理解和尊重,促进聋人的教育和融入社会。
手语是一种非常有意义的语言,它不仅可以让聋人与人进行有效的交流,还可以帮助听力正常的人了解和尊重聋人的文化和生活习惯。手语学习应用也可以成为一个重要的手语文化传播平台,让更多的人了解和学习手语,促进手语文化的传播和发展。

2.功能简介

元服务:
1、提供专业手语翻译老师示范视频,包括基本手势、字母表、常用短语等。用户可以点击观看示范并模仿。
2、提供类似答题模块,帮助用户巩固所学的手语知识,用户可以完成答题模块测试自己的进步。
元服务卡片:
1、卡片界面展示每日一题,并可以在卡片上进行答题学习

3.案例实现流程

3.1 内部应用功能
☀️3.1.1 首页功能
首页功能主要包含了4块功能:每日挑战、课程、测试、学习记录
在这里插入图片描述
🌈3.1.1.1 每日挑战
每日挑战主要的交互有2块功能:换一换和答题
换一换:主要实现题目和资源的切换(替换题目、替换视频链接)
在这里插入图片描述
答题:主要是根据选项遍历出Button按钮,在根据按钮点击事件判断点击的按钮type等于1则答题正确
在这里插入图片描述
源码片段如下:
在这里插入图片描述
Flex({ justifyContent: FlexAlign.SpaceBetween }){
Text(‘每日挑战’)
.fontSize(20)
.fontColor(‘#fff’)
.width(‘50%’)
Column() {
Flex({ justifyContent: FlexAlign.End }) {
Image(this.ic_new)
.width(20).height(20)
.margin({
right:5
})
Text(‘换一批’)
.fontSize(20)
.fontColor(‘#fff’)
.onClick(()=>{
this.anyArray=[{
name:‘吃’,
type:1
},{
name:‘厲害’,
type:0
}]
this.videoSrc= r a w f i l e ( ′ q u i c k . m p 4 ′ ) t h i s . p r e v i e w U r i = rawfile('quick.mp4') this.previewUri= rawfile(quick.mp4)this.previewUri=r(‘app.media.quick’)
})
}.width(‘50%’)
}
}.padding(10)
.backgroundColor(‘#938cf4’)
Column(){
Column(){
Flex({justifyContent:FlexAlign.SpaceBetween}){
Text(‘根据视频所示,选择正确的答案’)
.fontSize(12)
.fontColor(‘#fff’)
Text(‘40%的人答错’)
.fontSize(12)
.fontColor(‘#fff’)
}.padding({
top:10,
left:10,
right:10,
bottom:10
})
.margin({
top:0
})

 Video({
   src: this.videoSrc,
   previewUri: this.previewUri,
   currentProgressRate: this.curRate,
   controller: this.controller
 }).width('100%').height(180)
   .padding(5)
   .borderRadius(1)
   .margin({
     top:0,
     left:10,
     right:10,
     bottom:10,
   })
 Flex({justifyContent:FlexAlign.SpaceBetween}){
   ForEach(this.anyArray,(item)=>{
     Button(item.name,{ type: ButtonType.Normal,})
       .borderRadius(0)
       .width('45%')
       .backgroundColor('#ff986ec8')
       .onClick(()=>{
           if(item.type==1){
             if (this.dialogController != undefined) {
               this.dialogController.open()
               this.textValue='答对了'
             }
           }else{
             if (this.dialogController != undefined) {
               this.dialogController.open()
               this.textValue='答错了'
             }
           }
       })
   })
 }.margin({
   top:0,
   left:10,
   right:10,
   bottom:10,
 })

}.backgroundColor(‘#ffa49fea’)
.margin({
top:0,
left:10,
right:10,
bottom:10,
})
.borderRadius(10)
}.backgroundColor(‘#938cf4’)
🌈3.1.1.2 课程
这边主要是点击按钮跳转到课程页面一个功能
源码片段如下:
在这里插入图片描述
Column() {
Flex({ alignItems: this.alignItems }) {
Text(‘课程’).width(‘50%’)
.fontSize(20)
.fontWeight(FontWeight.Bold)
}
.size({width: ‘90%’,})
.padding({
top:20,
left:10,
bottom:0
})
Flex({ justifyContent: FlexAlign.SpaceBetween }) {
Text(‘初级课程,易上手’).width(‘70%’)
.fontSize(16)
.fontWeight(FontWeight.Bold)
.fontColor(‘#c4c2cf’)
Button(‘学习’).width(‘30%’)
.backgroundColor(‘#fecc5b’)
.margin({
top:-25,
})
.onClick(()=>{
router.push({ url: ‘pages/list’ })
})
}
.size({width: ‘100%’, })
.padding(
{ left: 30 ,top:10,right:30,bottom:20},
)
.border({
radius:{bottomLeft: 15, bottomRight: 15}
})
}.width(‘90%’)
.backgroundColor(‘#fff’)
.borderRadius(15)
.margin(20)
🌈3.1.1.3 测试
这边主要是点击按钮跳转到测试页面一个功能
源码片段如下:
在这里插入图片描述
Column() {
Flex({ alignItems: this.alignItems }) {
Text(‘课程’).width(‘50%’)
.fontSize(20)
.fontWeight(FontWeight.Bold)
}
.size({width: ‘90%’,})
.padding({
top:20,
left:10,
bottom:0
})
Flex({ justifyContent: FlexAlign.SpaceBetween }) {
Text(‘初级课程,易上手’).width(‘70%’)
.fontSize(16)
.fontWeight(FontWeight.Bold)
.fontColor(‘#c4c2cf’)
Button(‘学习’).width(‘30%’)
.backgroundColor(‘#fecc5b’)
.margin({
top:-25,
})
.onClick(()=>{
router.push({ url: ‘pages/list’ })
})
}
.size({width: ‘100%’, })
.padding(
{ left: 30 ,top:10,right:30,bottom:20},
)
.border({
radius:{bottomLeft: 15, bottomRight: 15}
})
}.width(‘90%’)
.backgroundColor(‘#fff’)
.borderRadius(15)
.margin(20)
🌈3.1.1.4 学习记录
这边主要是基于组件的形式在界面展示,功能点主要有:时间数据、学习课程数
•时间数据:已当前时间自动更新
•学习课程数:来源于已学课程的本地存储数据(存储来源在课程学习界面)
源码片段如下:
在这里插入图片描述
PersistentStorage.PersistProp(‘course’, 0);

let date = new Date()
let time = date.getFullYear() + “年” + (date.getMonth() + 1) + “月” + date.getDate() + “日”
console.log(time);
@Component
export default struct studyCollect {
alignItems : number = 0
// @State timeDate:string = date
@StorageLink(‘course’) course: number = 0
@State dayTime:string=time;
build() {
Column() {
Flex({ alignItems: this.alignItems }) {
Text(‘学习记录’).width(‘50%’)
.fontSize(20)
.fontWeight(FontWeight.Bold)
Text( ${this.dayTime}).width(‘50%’).height(30).textAlign(TextAlign.End)
}
.size({width: ‘90%’, height: 50})
.margin({
top:10
})
.padding(10)
Flex({ alignItems: this.alignItems }) {
Text(已学${this.course}课程).width(‘50%’).height(20).fontSize(16)
.fontWeight(FontWeight.Bold)
.fontColor(‘#fff’)
Text(${this.course}分钟).width(‘50%’).height(20).textAlign(TextAlign.End)
.fontColor(‘#fff’)
}
.size({width: ‘100%’, height: 50})
.padding(
{ left: 40 ,top:15,right:40,},
)
.border({
radius:{bottomLeft: 15, bottomRight: 15}
})
.backgroundColor(‘#938cf4’)
}.width(‘90%’)
.backgroundColor(‘#fff’)
.borderRadius(15)
.margin({
top:5
})

}
}
☀️3.1.2 课程功能
课程主页面主要是课程信息展示和跳转到具体详情页面
源码片段如下:
在这里插入图片描述
import router from ‘@ohos.router’;

import { arrImage } from ‘…/common/json’
@Entry
@Component
struct list {
scroller: Scroller = new Scroller();
@State arrImage: Object[]=arrImage

build() {
Scroll(this.scroller) {
Column() {
Column({ space: 5 }) {
Text(‘初级课程’).fontSize(20).fontColor(‘#fff’).width(‘90%’)
.padding(10)
.backgroundColor(‘’)
Flex({ wrap: FlexWrap.Wrap, justifyContent: FlexAlign.SpaceBetween }) {
ForEach(this.arrImage, (item) => {
Column() {
Image(item.image).width(‘100%’).height(120)
Text(item.name).lineHeight(30)
}.width(‘45%’).height(150).backgroundColor(0xF5DEB3)
.margin(5)
.onClick(()=>{
router.pushUrl({
url:‘pages/details’,
params:{
text:item.name,
image:item.image,
video:item.video
}
})
})
})

      }
      .width('100%')
      .padding(10)
      .backgroundColor(0xAFEEEE)
    }.width('100%').margin({ top: 5 })
  }.width('100%')
  .backgroundColor('#938cf4')
}
.backgroundColor(0xDCDCDC)
.scrollable(ScrollDirection.Vertical) // 滚动方向纵向
.scrollBar(BarState.On) // 滚动条常驻显示
.scrollBarColor(Color.Gray) // 滚动条颜色
.scrollBarWidth(5) // 滚动条宽度
.edgeEffect(EdgeEffect.Spring) // 滚动到边沿后回弹

}
}
🌈3.1.2.1 课程详情页
功能点主要有:路由数据、客户对课程的状态信息
•路由数据:手语答案、手语视频地址
•客户对课程的状态信息:点赞、收藏、转发相关状态变更
源码片段如下:
在这里插入图片描述
import router from ‘@ohos.router’
import promptAction from ‘@ohos.promptAction’

import { CommonTitleBar } from ‘…/common/CommonTitleBar’;

@Entry
@Component
struct Index {
@StorageLink(‘course’) course: number = 0

// @State good:Resource=$r(‘app.media.icon_good’);
@State goodStatus:boolean=true
@State goodNumber:number=0

@State startStatus:boolean=true
@State startNumber:number=0

@State shareStatus:boolean=true
@State shareNumber:number=0

@State text: string = router.getParams()[‘text’]
@State previewUri: Resource = router.getParams()[‘image’]
@State videoSrc: Resource = router.getParams()[‘video’]
@State curRate: PlaybackSpeed = PlaybackSpeed.Speed_Forward_1_00_X
controller: VideoController = new VideoController()
alignItems : number = 0
build() {
Column(){
// CommonTitleBar({attribute: {
// bg_color: ‘#ff2ad4b2’,
// close_text: ‘返回’,
// closeCallback: () => {
//
// },
// title_text: ‘标题’,
// menuCallback: () => {
//
// }
// }})
Column(){
Column(){
Video({
src: this.videoSrc,
previewUri: this.previewUri,
currentProgressRate: this.curRate,
controller: this.controller
}).width(‘100%’).height(180)
.padding(10)
.borderRadius(10)
.onFinish(() => {
this.course += 1;
console.info(‘onFinish’)
})
}
Flex(){
Text(‘当前示例:’)
.fontSize(24)
Text(${this.text})
.fontSize(24)
}.padding(10)
Flex({}){
Flex(){
Image(this.goodStatus ? r ( " a p p . m e d i a . i c o n g o o d " ) : r("app.media.icon_good"): r("app.media.icongood"):r(“app.media.active_icon_good”)
).width(24).height(24)
Text(${this.goodNumber}).height(25)
}.width(‘20%’)
.onClick(()=>{
this.goodStatus=!this.goodStatus;
this.goodStatus ? this.goodNumber–:this.goodNumber++;
promptAction.showToast({
message: this.goodStatus ? ‘取消点赞’ :‘点赞成功’,
duration: 2000,
});
})
Flex(){
Image(this.startStatus ? r ( " a p p . m e d i a . i c o n s t a r " ) : r("app.media.icon_star"): r("app.media.iconstar"):r(“app.media.active_icon_star”)).width(24).height(24)
Text(${this.startNumber}).height(25)
}.width(‘20%’)
.onClick(()=>{
this.startStatus=!this.startStatus;
this.startStatus ? this.startNumber–:this.startNumber++;
promptAction.showToast({
message: this.startStatus ? ‘取消收藏’ :‘收藏成功’,
duration: 2000,
});
})
Flex(){
Image(this.shareStatus ? r ( " a p p . m e d i a . i c o n s h a r e " ) : r("app.media.icon_share"): r("app.media.iconshare"):r(“app.media.active_icon_share”)).width(24).height(24)
Text(${this.shareNumber}).height(25)
}.width(‘20%’)
.onClick(()=>{
this.shareStatus=!this.shareStatus;
this.shareStatus ? this.shareNumber–:this.shareNumber++;
promptAction.showToast({
message: this.shareStatus ? ‘取消转发’ :‘转发成功’,
duration: 2000,
});
})
}.padding(10)
}
}
//容器整体宽高
.width(‘100%’)
.height(‘100%’)
.backgroundColor(‘#ff0f2ff’)
}
}
☀️3.1.3 测试功能
测试功能页面主要功能是从json的题库取出10题,如果答题正确会自动切换到下一题。
源码片段如下:
在这里插入图片描述
在这里插入图片描述
import router from ‘@ohos.router’

import promptAction from ‘@ohos.promptAction’
import { arrImage } from ‘…/common/json’
@Entry
@Component
struct Index {
@State onActive : number = 1

@State arrImage: Object[]=arrImage

@State videoSrc: Resource = $rawfile(‘hello.mp4’)
@State previewUri: Resource = $r(“app.media.hello”)
@State curRate: PlaybackSpeed = PlaybackSpeed.Speed_Forward_1_00_X
controller: VideoController = new VideoController()
alignItems : number = 0
@State message: string = ‘手语猜猜看’
@State studyName: string = ‘手语阅览’
@State collectName: string = ‘手语挑战’
@State testName: string = ‘测试’

@State arr: string[] =[‘你好’,‘出生’,‘爱’,‘晚饭’,‘中午’,‘明天’]

build() {
Column(){
Column(){
Flex({alignItems:ItemAlign.Center,justifyContent:FlexAlign.Center}){
// Text(${this.arrImage[this.onActive]['name']})
Text(${this.onActive})
.fontSize(28)
Text(‘/’)
.fontSize(28)
Text(${this.arrImage.length})
.fontSize(28)
}
.margin(10)
Column(){
Video({
src: this.arrImage[this.onActive-1][‘video’],
previewUri: this.arrImage[this.onActive-1][‘image’],
currentProgressRate: this.curRate,
controller: this.controller
}).width(‘100%’).height(180)
.margin({
top:0,
bottom:0,
})
}
Column({}){
Text(‘在下列选项中选择正确答案?’)
.width(‘100%’)
.padding(10)
.fontSize(20)
}.backgroundColor(‘#fff’)
Flex({wrap: FlexWrap.Wrap }){
ForEach(this.arrImage[this.onActive-1][‘answer’],(item:string)=>{
Button(item,{ type: ButtonType.Normal,})
.margin(5)
.borderRadius(5)
.onClick(() => {
console.log(item)
if(this.onActive>=this.arrImage.length){
promptAction.showToast({
message: ‘闯关已结束’,
duration: 2000,
});
router.back({url:‘pages/index’});
return false;
}
if(item==this.arrImage[this.onActive-1][‘name’]){
promptAction.showToast({
message: ‘答对了,请继续下一题’,
duration: 2000,
});
this.onActive=this.onActive+1;
}else{
promptAction.showToast({
message: ‘答错了’,
duration: 2000,
});
}

        })
      })
    }.padding(10)
  }
}
//容器整体宽高
.width('100%')
.height('100%')
.backgroundColor('#ff0f2ff')

}
}
🦋3.2 卡片功能
这边和每日挑战功能类似,主要多了些动画效果,卡片主要的交互有2块功能:换一换和答题
•换一换:主要实现题目和资源的切换(替换题目、替换视频链接)
•答题:主要是根据选项遍历出Button按钮,在根据按钮点击事件判断点击的按钮type等于1则答题正确
源码片段如下:
在这里插入图片描述
在这里插入图片描述
@Entry
@Component
struct WidgetCard {

@State onActive:number=0
@State arrImage: Object[] = [
{
name:‘你好’,
image: r ( " a p p . m e d i a . h e l l o " ) , c o n t e n t : ′ 一手食指指向对方。一手握拳,向上伸出拇指 。 ′ , v i d e o : r("app.media.hello"), content:'一手食指指向对方。一手握拳,向上伸出拇指。', video: r("app.media.hello"),content:一手食指指向对方。一手握拳,向上伸出拇指,video:rawfile(‘hello.mp4’),
anyArray:[{
name:‘你好’,
type:1
},{
name:‘谢谢’,
type:0
}]
},
{
name:‘谢谢’,
image: r ( ′ a p p . m e d i a . t h a n k ′ ) , c o n t e n t : ′ 一手伸出拇指,弯曲两下,表示向人感谢 。 ′ , v i d e o : r('app.media.thank'), content:'一手伸出拇指,弯曲两下,表示向人感谢。', video: r(app.media.thank),content:一手伸出拇指,弯曲两下,表示向人感谢,video:rawfile(‘thank.mp4’),
anyArray:[{
name:‘不好’,
type:0
},{
name:‘谢谢’,
type:1
}]
},
{
name:‘爱’,
image: r ( ′ a p p . m e d i a . l o v e ′ ) , c o n t e n t : ′ 一手轻轻抚摩另一手拇指指背,表示一种“怜爱”的感 情 ′ , v i d e o : r('app.media.love'), content:'一手轻轻抚摩另一手拇指指背,表示一种“怜爱”的感情', video: r(app.media.love),content:一手轻轻抚摩另一手拇指指背,表示一种怜爱的感,video:rawfile(‘love.mp4’),
anyArray:[{
name:‘没有’,
type:0
},{
name:‘爱’,
type:1
}]
},
{
name:‘喜欢’,
image: r ( ′ a p p . m e d i a . l i v e ′ ) , v i d e o : r('app.media.live'), video: r(app.media.live),video:rawfile(‘like.mp4’),
content:'一手拇、食指微曲,指尖抵于颌下,头微微点动一下。 ',
anyArray:[{
name:‘没有’,
type:0
},{
name:‘喜欢’,
type:1
}]
},
{
name:‘不喜欢’,
image: r ( ′ a p p . m e d i a . d i s l i k e ′ ) , v i d e o : r('app.media.dislike'), video: r(app.media.dislike),video:rawfile(‘dislike.mp4’),
content:‘一手伸直,左右摆动几下。 一手拇、食指微曲,指尖抵于颌下,头微微点动一下。 ‘,
anyArray:[{
name:‘好晕’,
type:0
},{
name:‘不喜欢’,
type:1
}]
},
{
name:‘饭’,
image: r ( ′ a p p . m e d i a . e a t ′ ) , v i d e o : r('app.media.eat'), video: r(app.media.eat),video:rawfile(‘eat.mp4’),
content:’(一)一手拇、食指相对,中间留有米粒大小距离。(二)一手伸食、中指象征筷子,作吃饭动作。’,
anyArray:[{
name:‘可爱’,
type:0
},{
name:‘饭’,
type:1
}]
},
{
name:‘快’,
image: r ( ′ a p p . m e d i a . q u i c k ′ ) , v i d e o : r('app.media.quick'), video: r(app.media.quick),video:rawfile(‘quick.mp4’),
content:‘一手拇、食指相捏,很快地从一侧向另一侧作快速挥动,象征物体运动速度很快。’,
anyArray:[{
name:‘慢’,
type:0
},{
name:‘快’,
type:1
}]
},
{
name:‘慢’,
image: r ( ′ a p p . m e d i a . s l o w ′ ) , v i d e o : r('app.media.slow'), video: r(app.media.slow),video:rawfile(‘show.mp4’),
content:‘一手掌心向下,慢慢地上下微动几下,象征物体运动速度缓慢。’,
anyArray:[{
name:‘去’,
type:0
},{
name:‘慢’,
type:1
}]
},
{
name:‘没关系’,
image: r ( ′ a p p . m e d i a . m a t t e r ′ ) , v i d e o : r('app.media.matter'), video: r(app.media.matter),video:rawfile(‘matter.mp4’),
content:'一手拇、食、中指捻动,连续几次。 两手拇、食指搭成圆圈,互相套环。 ',
anyArray:[{
name:‘人类’,
type:0
},{
name:‘没关系’,
type:1
}]
},
{
name:‘厉害’,
image: r ( ′ a p p . m e d i a . p o w e r f u l ′ ) , v i d e o : r('app.media.powerful'), video: r(app.media.powerful),video:rawfile(‘powerful.mp4’),
content:'一手打手指字母“L”的指式,并绕脸部转一圈。同时面部作出严厉的表情。 ',
anyArray:[{
name:‘好帅’,
type:0
},{
name:‘厉害’,
type:1
}]
},
];

/*

  • The max lines.
    */
    readonly MAX_LINES: number = 1;

/*

  • The action type.
    */
    readonly ACTION_TYPE: string = ‘router’;

/*

  • The message.
    */
    readonly MESSAGE: string = ‘add detail’;

/*

  • The ability name.
    */
    readonly ABILITY_NAME: string = ‘EntryAbility’;

/*

  • The with percentage setting.
    */
    readonly FULL_WIDTH_PERCENT: string = ‘100%’;

/*

  • The height percentage setting.
    */
    readonly FULL_HEIGHT_PERCENT: string = ‘100%’;

@State opacityAngle: number = 0.8
@State mainFlag: boolean = false;
@State flag: boolean = true;
build() {
Stack() {
if(this.flag){
Image(this.arrImage[this.onActive][‘image’])
.width(this.FULL_WIDTH_PERCENT)
.height(this.FULL_HEIGHT_PERCENT)
.objectFit(ImageFit.Cover)
.opacity(this.opacityAngle)
.transition({ type: TransitionType.Insert, translate: { x: 0, y: 0 } })
.transition({ type: TransitionType.Delete, opacity: 0, scale: { x: 0, y: 0 } })
}else{
Image(this.arrImage[this.onActive][‘image’])
.width(this.FULL_WIDTH_PERCENT)
.height(this.FULL_HEIGHT_PERCENT)
.objectFit(ImageFit.Cover)
.opacity(this.opacityAngle)
.transition({ type: TransitionType.Insert, translate: { x: 0, y: 0 } })
.transition({ type: TransitionType.Delete, opacity: 0, scale: { x: 0, y: 0 } })
}
Flex({ direction: FlexDirection.Row, justifyContent: FlexAlign.Center }){
Image($r(“app.media.ic_new”)).width(11).height(11)
.margin({
left:10,
top:3,
right:5
})
Text(“换一批”).fontSize(‘8fp’).width(40)
.fontColor(‘#fff’)
.margin({
top:3
})
}.backgroundColor(‘#fecc5b’)
.width(50)
.height(15)
.borderRadius(10)
.onClick(()=>{
animateTo({ duration: 1000 }, () => {
this.flag = !this.flag;
this.onActive=(this.onActive+1)%this.arrImage.length;
})
})
.position({ x: 10, y: 10 })
Column(){
Flex({justifyContent:FlexAlign.SpaceBetween}){
ForEach(this.arrImage[this.onActive][‘anyArray’],(item)=>{
Button(item.name,{ type: ButtonType.Normal,})
.fontSize(‘8fp’)
.borderRadius(10)
.height(20)
.width(‘45%’)
.backgroundColor(‘#fecc5b’)
.onClick(()=>{
console.log(‘44’)
if(item.type==1){
animateTo({ duration: 1000 }, () => {
this.flag = !this.flag;
this.onActive=(this.onActive+1)%this.arrImage.length;
})
}else{

            }
          })
      })
    }
  }.margin({
    top:110,
    left:10,
    right:10
  })
  Text(this.arrImage[this.onActive]['content'])
    .fontSize('7fp')
    .opacity($r('app.float.detail_immersive_opacity'))
    .margin({ top: '70vp',left:'10vp',right:'10vp' })
    .textOverflow({ overflow: TextOverflow.Ellipsis })
    .fontColor('#000')
    .fontWeight('900')
    .maxLines(this.MAX_LINES)
}
.width(this.FULL_WIDTH_PERCENT)
.height(this.FULL_HEIGHT_PERCENT)
.onClick(() => {
  postCardAction(this, {
    "action": this.ACTION_TYPE,
    "abilityName": this.ABILITY_NAME,
    "params": {
      "message": this.MESSAGE
    }
  });
})

}
}
元服务项目源码

总结

手语猜一猜元服务的实现借助了HarmonyOS的跨应用数据共享和功能交互特性,让用户可以通过手势输入手语,并进行识别和猜词游戏。这种应用方式不仅提供了娱乐和互动的体验,还促进了手语的学习和交流。通过使用该应用,我深刻体会到了HarmonyOS元服务的便利和灵活性。
开发使用HarmonyOS元服务可以带来许多好处,包括跨设备互联、数据共享和交互、灵活性和扩展性,以及性能优化。我鼓励大家积极尝试使用HarmonyOS元服务,开发出更好属于自己的应用。

附录

元服务介绍
ArkTS语言介绍
端云一体化开发介绍
低代码开发介绍

Logo

苏州本地的技术开发者社区,在这里可以交流本地的好吃好玩的,可以交流技术,可以交流招聘等等,没啥限制。

更多推荐