随着HarmonyOS NEXT崭露头角,越来越多的公司开始探索鸿蒙应用开发。虽然目前HarmonyOS NEXT尚未向个人开发者开放,但我们可以体验并使用最新的API9和开发工具,迎接未来,体验鸿蒙新的应用形态——元服务。

元服务,作为HarmonyOS提供的创新服务形式,具备独立入口、免安装的特性,支持多种呈现方式,如灵活的卡片式展示等。本文将深入剖析元服务的核心概念,介绍基于最新API9的开发流程,以及使用的开发工具,为读者提供全方位的元服务开发体验。
本文将为您揭示未来鸿蒙生态的精彩面貌。跟随我们一起踏上HarmonyOS的元服务开发之旅,体验鸿蒙的创新力量!
本文将基于活动召集令元服务的开发案例带大家一起学习:

元服务内部功能:

  1. 提供页面供用户创建一个集结活动,如周末轰趴,输入时间,地点,人数,等活动信息
  2. 提供一个列表页面,供浏览所有公开活动
  3. 登录用户可以查看活动详情,可以报名参与

元服务卡片:

  1. 卡片界面展示所有活动列表。
  2. 卡片界面展示最新未报名的活动列表,支持直接在卡片上报名活动。

项目实现演示效果如下:

活动召集令元服务视频

一.元服务和ArkTS语言简介

元服务 (原为原子化服务) 是一种基于HarmonyOS API的全新服务提供方式,元服务仅需开发一次,便可以运行在多种类型的终端设备上,以鸿蒙万能卡片等多种呈现形态, 向用户提供更轻量化的服务。

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

1.1 学习元服务

在万物互联时代,人均持有设备量不断攀升,设备和场景的多样性,使服务开发变得更加复杂、服务入口更加丰富。在此趋势下,应用提供方和用户迫切需要一种新的服务提供方式,使应用开发更简单、服务的获取和使用更便捷。为此,HarmonyOS 提供了基于元服务和应用的便捷服务。

元服务是 HarmonyOS 提供的一种面向未来的服务提供方式,是有独立入口的(用户可通过点击、碰一碰、扫一扫等方式直接触发)、免安装的(无需显式安装,由系统程序框架后台安装后即可使用)、可为用户提供一个或多个便捷服务的用户程序形态。元服务基于 HarmonyOS API 开发,支持运行在 1+8+N 设备上,供用户在合适的场景、合适的设备上便捷使用。

元服务具有随处可及、服务免安装直达、分布式流转等特性。日常生活中,用户可以通过扫描 HarmonyOS Connect 标签、“碰一碰”设备来快速启动元服务,也可以在设备的服务中心和桌面上轻松找到他。
在这里插入图片描述

1.2 元服务带来的变革

(1) 免安装,更轻量化地将服务带给用户
(2) 一键服务直达,将用户感兴趣的内容前置、外显
(3) 跨端转移,多终端设备间无缝流转
(4) 情景智能卡片推荐,随心定制、更懂用户

1.3 元服务全场景流量入口

  1. 应用市场
    用户可以点击应用市场-应用页-元服务入口体验元服务。
  2. 负一屏/服务中心
    点击即可快速直达对应服务页面,用户还可以自主在常驻服务栏设置喜爱或常用的服务。
  3. 桌面
    用户可以将服务添加到桌面,随时随地查看信息、获取服务。
  4. 智慧搜索
    用户在搜索到相关关键词时,关联的元服务则会出现在搜索结果中,点击后可以跳转到服务详情页面。
  5. 小艺建议
    通过桌面打开小艺建议发现服务卡片,点击服务卡片触发元服务。
  6. 小艺语音
    通过语音唤醒词,如“小艺小艺”进入智慧语音,使用语音对话方式触发元服务。
  7. 碰扫感知
    用户拿出华为手机“碰一碰”或“扫一扫”,可识别其他设备上的NFC标签,手机页面将自动跳转到相应的元服务内容,用户在手机侧即可操作设备。

1.4 ArkTS学习

ArkTS是鸿蒙生态的应用开发语言。它在保持TypeScript(简称TS)基本语法风格的基础上,对TS的动态类型特性施加更严格的约束,引入静态类型。同时,提供了声明式UI、状态管理等相应的能力,让开发者可以以更简洁、更自然的方式开发高性能应用。

同时,提供了声明式UI、状态管理等相应的能力,让开发者可以以更简洁、更自然的方式开发高性能应用。

面向万物互联时代,华为提出一次开发多端部署、可分可合自由流转、统一生态原生智能三大应用与服务开发理念,针对多设备、多入口、服务可分可合等特性,提供多种能力协助开发者降低开发门槛,同时HarmonyOS与OpenHarmony统一生态。HarmonyOS基于JS/TS语言体系,构建了全新的声明式开发语言ArkTS。除了兼容JS/TS语言生态,ArkTS扩展了声明式UI语法和轻量化并发机制。

1.5 ArkTS特点

  • 自然简洁语法
    ArkTS提供了简洁自然的声明式语法、组件化机制、数据-UI自动关联等能力,实现了贴近自然语言,书写效率更高的编程方式,为开发者带来易学、易懂、极简开发的优质体验。
  • 轻量化并发机制
    ArkCompiler运行时在HarmonyOS上提供了Worker API支持并发编程。在运行时实例内存隔离的基础上,ArkCompiler通过共享运行实例中的不可变或者不易变的对象、内建代码块、方法字节码等技术手段,优化了并发运行实例的启动性能和内存开销。
    在这里插入图片描述

二.DevEco Studio开发工具

DevEco Studio是面向全场景多设备,提供一站式的分布式应用开发平台,支持分布式多端开发、分布式多端调测、多端模拟仿真,全方位的质量与安全保障。

2.1 DevEco Studio学习

DevEco Studio是一个功能强大的移动应用开发工具,基于IntelliJ IDEA Community开源版本打造,面向华为终端全场景多设备的一站式集成开发环境(IDE),为开发者提供工程模板创建、开发、编译、调试、发布等端到端的HarmonyOS应用开发服务。

2.2 DevEco Studio的主要特性

DevEco Studio的主要特性包括:

  1. 工程模板创建:提供多种工程模板,帮助开发者快速创建项目。
  2. 界面可视化编辑:支持通过可视化布局编辑器构建界面,简化了界面开发过程。
  3. 开发:提供代码提示、语法高亮等功能,帮助开发者高效编写代码。
  4. 编译:支持编译构建,并根据指定的Compile API版本进行编译打包。
  5. 调试:提供调试功能,帮助开发者快速定位和解决问题。
  6. 上架:提供应用上架功能,帮助开发者将应用发布到华为应用市场等平台。

2.3 端云一体化开发

DevEco Studio的端云一体化开发是指通过集成AppGallery Connect的认证服务和云函数服务,开发者可以在创建工程时选择云开发模板,实现端云一体化协同开发。这使得开发者可以在DevEco Studio中进行认证服务的集成,以及云函数的开发和管理。

为丰富HarmonyOS对云端开发的支持、实现端云联动,DevEco Studio推出了云开发功能,开发者在创建工程时选择云开发模板,即可在DevEco Studio内同时完成HarmonyOS应用/元服务的端侧与云侧开发,体验端云一体化协同开发。

2.3.1端云一体化开发特点

DevEco Studio的端云一体化开发包括以下方面:

  1. 认证服务集成:开发者可以在DevEco Studio中集成AppGallery Connect的认证服务,以实现应用程序的登录入口。通过使用端云一体化的认证服务,开发者可以方便地实现用户的注册、登录和授权等功能。
  2. 云函数开发:DevEco Studio集成了AppGallery Connect的Serverless云服务开放接口,开发者可以在创建工程时选择云开发模板,并通过云函数进行业务逻辑的处理和数据的存储。这使得开发者可以更加专注于业务逻辑的实现,而不需要关心底层的技术细节。
  3. 端云数据同步:通过端云一体化开发,开发者可以实现端云数据同步,保证数据的一致性和实时性。例如,当用户在应用程序中进行操作时,相关的数据可以被实时同步到云端,从而保证数据的实时性和一致性。
  4. 端云协同调试:DevEco Studio支持端云协同调试功能,开发者可以在开发过程中进行本地调试和远程调试。当应用程序在本地进行调试时,开发者可以通过DevEco Studio与远程设备进行通信,从而进行远程调试。这使得开发者可以更加方便地进行应用程序的调试和优化。

相比于传统开发模式,云开发模式具备成本低、效率高、门槛低等优势,具体区别见下表。
在这里插入图片描述

2.4 低代码开发

DevEco Studio的低代码开发是指通过使用预定义的组件和模板,以及可视化的界面编辑方式,开发者可以以较少的编码工作量快速构建应用程序。低代码开发可以大大提高开发效率,降低开发门槛,使非专业的程序员也能进行应用程序的开发。

2.4.1 开发方式

使用低代码开发应用或服务有以下两种开发方式:

  • 创建一个支持低代码开发的新工程,开发应用或服务的UI界面。
  • 在已有工程中,创建Visual文件来开发应用或服务的UI界面。
    在这里插入图片描述

2.4.2 支持低代码开发的工程

项目构成目录如下:
在这里插入图片描述

2.4.3 创建Visual文件构建UI

在已有的HarmonyOS工程中,可以通过创建Visual文件的方式,使用低代码开发应用或服务的UI界面,要求compileSdkVersion必须为7或以上。ArkTS低代码要求compileSdkVersion必须为8或以上。
在打开的工程中,选中模块的pages文件夹,单击鼠标右键,选择New > Visual > Page。

在这里插入图片描述

三.活动召集令元服务开发实战

3.1案例背景

在当今繁忙而快节奏的生活中,人们渴望有更多机会聚在一起,共度美好时光。然而,组织和参与活动往往面临着诸多挑战,例如信息不透明、难以找到合适的活动,以及活动宣传不足。为了解决这些问题,我们开发了一款全新的创意应用——活动召集令元服务。

在城市化进程加速的今天,越来越多的人发现,社交活动是缓解生活压力、建立社交网络以及增进人际关系的重要途径。然而,传统的社交方式难以满足现代人的需求。于是,我萌生了为用户提供一个便捷、高效、社交化的活动发布平台的创意。

在寻找活动信息的过程中,用户常常面临信息碎片化和不一致的情况。有些活动信息散落在不同的社交媒体或活动平台上,用户需要花费大量时间和精力来收集和筛选。我们意识到,整合和统一这些信息对提高用户体验至关重要。

随着移动互联技术的迅速发展,我们看到了整合社交元素和活动信息的机会。通过开发一款创新的应用,用户可以轻松创建、查找和参与各种社交活动。这个应用将成为人们社交生活的数字化扩展,使活动的组织和参与变得更加简单而愉快。

在这个创意应用的背后,我们追求的是连接人与人之间的纽带,让社交活动更加简单而有趣。我们相信,通过这个活动发布平台,人们可以更轻松地发现、创建和参与各种精彩活动,为生活增添更多色彩。

3.2 创建项目-前期准备

如下图,注册AppGallery Connect并配置相应的配置。进入我的元服务创建项目。

在这里插入图片描述
创建应用,填写对应的相关配置。

在这里插入图片描述
到DevEco Studio创建新的项目工程。

在这里插入图片描述
创建项目之后就可以进行开发,情况如图。

在这里插入图片描述

3.3元服务内部功能详解

3.3.1 登录页面

在这里插入图片描述
核心代码如下:

build() {
  Row() {
    Column({space:17}) {
      Image($r("app.media.logo")).width(80)
      Text("活动召集令")
      TextInput({ placeholder: '输入用户名' })
        .width(300)
        .height(60)
        .fontSize(20)
        .onChange((value: string) => {
          this.username = value
        })
      TextInput({ placeholder: '输入密码' })
        .width(300)
        .height(60)
        .fontSize(20)
        .type(InputType.Password)
        .onChange((value: string) => {
          this.password = value
        })
      Button('登录')
        .width(300)
        .height(60)
        .fontSize(20)
        .backgroundColor('#0F40F5')
        .onClick(() => {
          this.S_login();
        })
    }
    .width('100%')
  }
  .height('100%')
}
  1. @Component是用来定义组件的装饰器,@Entry是页面入口组件。
  2. @State 被用来定义组件内部的状态。在这里,activitiesusername`和`password`都是组件的状态。当被@State`修饰的变量发生变化,页面会立即刷新。
  3. S_login 方法用于处理登录逻辑。如果用户名和密码是"admin"和"admin",则使用`router.replaceUrl`方法重定向用户到指定页面。否则,通过`promptAction.showToast`显示错误消息。
  4. build 方法定义了组件的结构,包括一个包含Logo、文本输入框、按钮等的界面。

页面心得:

  • 清晰的结构:
    代码结构清晰,使用了ArkTs提供的组件和装饰器,使得组件的声明和状态管理更加简洁。
  • 用户界面(UI):
    界面设计简单直观,包括Logo、用户名、密码输入框以及登录按钮。
    使用了Ark Ts的UI组件,使得构建用户界面的过程更加方便。
  • 登录逻辑:
    登录逻辑通过`S_login`方法实现,检查用户名和密码是否匹配,并使用`router.replaceUrl`进行页面重定向。
    错误处理使用了`promptAction.showToast`提供用户友好的错误提示。
  • 状态管理(State):
    使用`@State`来管理组件的状态,这样可以在状态变化时触发UI的更新。

3.3.2 活动列表页面

在这里插入图片描述

核心代码

// 搜索框
Search({
  value: this.filterText, // 将搜索框的值与 filterText 关联
  placeholder: '输入搜索关键字...',
  // controller: this.controller
})
  .onChange((value: string) => {
    this.filterText = value; // 设置搜索框的值到 filterText
  })
  .width('90%')
// .margin(20);
  Scroll(this.scroller) 
   Column()
     // 列出活动信息
     ForEach(this.activities, (activity, index) => {
       // 根据 filterText 过滤活动
       if (
         this.filterText === '' || // 如果搜索框为空,显示所有活动
         activity.title.includes(this.filterText) || // 活动标题包含搜索关键字
         activity.type.includes(this.filterText) // 活动类型包含搜索关键字
       ) {
         // 如果满足搜索条件,显示活动
         Row() {
           Column() {
             Row() {
               Text(`${index + 1}.)
                 .width("10%")
                 .fontSize("20fp");
               Text(activity.title) // 活动标题
                 // .width("30%")
                 .fontSize("20fp")

             }

             Image('images/'+String(activity.type)+".png")
               .margin({left:40,top:10})
               .width("80%")
               .height("300px")
               .onClick(() => {
                 const secondaryButton = {
                   value: '我要报名',
                   fontColor: '#ffffff', // 可选,文字颜色
                   backgroundColor: '#007aff', // 可选,背景颜色
                   action: () => {
                     // @ts-ignore
                     this.activities[index].flag='1'
                     // @ts-ignore
                     console.log(this.activities[index].flag)
                     AlertDialog.show({
                       title: "报名成功",
                       message:"您已成功报名此活动"
                     });
                   } // 按钮被点击后执行的函数
                 };
                 const primaryButton = {
                   value: '取消',
                   fontColor: '#ffffff', // 可选,文字颜色
                   backgroundColor: 'red', // 可选,背景颜色
                   action: () => {

                   } // 按钮被点击后执行的函数
                 };
                 // 处理查看活动详情的逻辑
                 AlertDialog.show({
                   title: "活动详情",
                   message: `标题: ${activity.title}\n时间 ${activity.time}\n地点:${activity.where}\n详细信息: ${activity.description}\n\n`
                   secondaryButton: secondaryButton,
                   primaryButton: primaryButton,
                 });
               });


           }
         }
         .width("95%")
         .margin({ top: '10vp' })
         .margin(10)
       }
     });

这段代码是一个带有搜索框的活动列表页面,用户可以在搜索框中输入关键字,然后根据关键字过滤和显示相关的活动信息。以下是代码的一些关键点:

  • 搜索框:
    使用`Search`组件创建了一个搜索框,其值与`this.filterText`关联。
    通过`onChange`事件监听搜索框值的变化,并将新的值设置给`this.filterText`
  • 活动列表:
    使用`Scroll`组件创建了一个可滚动的视图。
    使用`ForEach`遍历`this.activities`数组中的每个活动,根据搜索框的值过滤活动信息。
  • 过滤逻辑:
    根据搜索框的值(`this.filterText`),过滤显示符合条件的活动信息。
    如果搜索框为空,显示所有活动;否则,只显示标题或类型包含搜索关键字的活动。
  • 活动信息显示:
    对于每个满足条件的活动,创建一个`Row`组件,显示活动的标题、序号、图片等信息。
    图片点击事件:点击活动的图片,弹出对话框显示活动详情,并提供报名和取消按钮。
  • 报名逻辑:
    点击图片时,弹出包含报名和取消按钮的对话框。
    如果点击了报名按钮,将活动的`flag`属性设置为’1’,并显示报名成功的对话框。
  • 对话框:
    使用`AlertDialog`组件显示活动详情和报名结果对话框。
    对话框包括标题、时间、地点、详细信息,并可包含报名和取消按钮。

3.3.3 个人中心页面

在这里插入图片描述
本页面个人中心是一个简单的UI页面。

核心代码:

Column() {
  Column() {
    Stack() {
      Image($r('app.media.toxiang'))
        .width("131.1vp")
        .height("139.21vp")
        .offset({ x: "-0.33vp", y: "-20.98vp" })

    }
    .width("99.7%")
    // .height("105.66vp")
    .offset({ x: "0.46vp", y: "-292.54vp" })
    .margin(20)
    // .backgroundColor("#8adff5")
    Stack() {

      Stack() {
        Row(){
          Image($r('app.media.exid'))
            .width("40.68vp")
            .height("40.79vp")
          Text("  退出登录")
            .width("262.68vp")
            .height("43.79vp")
            .fontSize("20fp")
        }


      }
      // .backgroundColor("#a0d9f6")
      .width("90%")
      .height("62.73vp")
      .offset({ x: "1.33vp", y: "-36.77vp" })
      .onClick(()=>{
        router.replaceUrl({
          url: "pages/Index"
          // this.paramsFromIndex?['name']
        })

      })
      Stack() {
        Row(){
          Image($r('app.media.huodong'))
            .width("40.68vp")
            .height("40.79vp")
          Text("  新增活动")
            .width("262.68vp")
            .height("43.79vp")
              // .offset({ x: "34.29vp", y: "-0.17vp" })
            .fontSize("20fp")
        }


      }
      // .backgroundColor("#a0d9f6")
      .width("90%")
      .height("62.73vp")
      .offset({ x: "1.33vp", y: "-115.67vp" })
      .onClick(()=>{
        router.replaceUrl({
          url: "pages/demo2",
          params: {
            activities:this.activities
          }
        })
      })


      Stack() {
        Row(){
          Image($r('app.media.canyu'))
            .width("40.68vp")
            .height("40.79vp")
          Text("  已参与的活动")
            .width("262.68vp")
            .height("43.79vp")
              // .offset({ x: "34.29vp", y: "-0.17vp" })
            .fontSize("20fp")
        }
      }
      .width("90%")
      .height("62.73vp")
      // .backgroundColor("#a0d9f6")
      .offset({ x: "1.33vp", y: "-192.39vp" })
      .onClick(() => {
        router.replaceUrl({
          url: "pages/canyu",
          params: {
            activities:this.activities
          }
        })
      })
    }
    .width("99.4%")
    .height("465.88vp")
    .offset({ x: "0.92vp", y: "-286.87vp" })
  }
  .width("100%")
  .height("100%")
  .offset({ x: "0vp", y: "311.31vp" })
  .justifyContent(FlexAlign.Center)
}
.width("100%")
.height("100%")

3.3.4 已参与的活动和新增活动页面

在这里插入图片描述
在这里插入图片描述
核心代码:

    Column() {
      Row(){
        Button("返回")
          .margin({left:-90})
          // .width("71.45vp")
          .height("47.01vp")
            // .offset({ x: "-126.85vp", y: "-289.57vp" })
          .onClick(() => {
            router.replaceUrl({
              url: "pages/one",
              params: {
                activities:this.activities
              }
            })
          });
        Text("我的参与")
          .margin({left:"30"})
          // .width("200vp")
          .height("60vp")
            // .offset({ x: "73.54vp", y: "-331.74vp" })
          .fontSize("24fp")
          // .margin({left:"50%"})
}
      // 搜索框
      Search({
        value: this.filterText, // 将搜索框的值与 filterText 关联
        placeholder: '输入搜索关键字...',
        // controller: this.controller
      })
        .onChange((value: string) => {
          this.filterText = value; // 设置搜索框的值到 filterText
        })
        .width('90%')
      // .margin(20);
      Row() {
        Text("序号")
          .width("20%")
          .fontSize("20fp")
          .fontColor(Color.Blue) // 可以调整表头的样式
        .margin({left:"5%"})
        Text("活动")
          .width("50%")
          .fontSize("20fp")
          .fontColor(Color.Blue);
        Text("报名情况")
          .width("40%")
          .fontSize("20fp")
          .fontColor(Color.Blue);
      }
      // 列出活动信息
      ForEach(this.activities, (activity, index) => {
        // 根据 filterText 过滤活动
        if (
          this.filterText === '' || // 如果搜索框为空,显示所有活动
          activity.title.includes(this.filterText) || // 活动标题包含搜索关键字
          activity.type.includes(this.filterText) // 活动类型包含搜索关键字
        ) {
          if(activity.flag==1){
            // 如果满足搜索条件,显示活动
            Row() {
              Column() {
                Row() {
                  Text(`${index + 1}.)
                    .margin({left:"5%"})
                    .width("20%")
                    .fontSize("20fp");
                  Text(activity.title) // 活动标题
                    .width("50%")
                    .fontSize("20fp")

                  Text("已报名") // 活动标题
                    .width("40%")
                    .fontSize("20fp")

                }
              }
            }
            .width("95%")
            // .height("200px")
            .margin({ top: '10vp' })
            .margin(10)
          }

        }
      });
    }
    .width('100%')

主要代码解释:

  • 注册了点击事件,点击按钮时会执行一个函数,利用路由(某个路由管理器,如router)跳转到 “pages/one” 页面,并传递了一个名为activities的参数。
  • 使用ForEach遍历activities数组,对每个活动进行处理。
  • 根据filterText过滤活动,只有当搜索框为空或者活动标题、活动类型包含搜索关键字时,才会显示。
  • 如果活动的flag等于1,表示活动已报名,就创建一个新的行布局,包含序号、活动标题和报名情况的文本标签。

页面心得:

  1. 组件化和模块化设计: 代码中使用了组件化的思想,通过创建按钮、文本、搜索框等组件,使得界面的各个部分独立、可重用。这样的设计能够提高代码的可维护性和可读性。
  2. 响应式设计:使用百分比和视口单位(vp)等,以及对搜索框变化的监听,表明代码是为不同屏幕尺寸和设备进行了响应式设计,确保在不同环境下有较好的用户体验。
  3. 条件渲染:通过条件判断,根据搜索框的值和活动的状态,选择性地渲染页面的不同部分。这样的方式使得页面的显示更加灵活,根据具体情况呈现不同的内容。
  4. 路由和导航:使用路由(某个路由管理器,如`router`)来处理页面之间的导航,通过跳转到不同的页面传递参数。这样的设计是典型的单页面应用(SPA)的做法,能够提升用户体验。
  5. 样式和排版:代码中使用了一些样式的设定,包括宽度、高度、字体大小等,通过这些样式的调整,使得页面呈现出一致的风格和排版。

3.4元服务卡片功能详解

3.4.1 元服务卡片-所有活动列表

在这里插入图片描述
部分核心代码:

onNextQuestion() {
  const nextIndex = this.currentQuestionIndex + 1;
  if (nextIndex < this.title.length) {
    this.currentQuestionIndex = nextIndex;
    this.showExplanation = false; // 清空显示解析状态
    this.ende=false;
  } else {
    this.ende=true;
  }
}

build() {
  Stack() {
    Image($r("app.media.img1"))
      .objectFit(ImageFit.Cover)
    Column() {
      Text("未报名的最新活动")
        .fontSize(20)
        .fontColor("#0076ff")
        .margin({bottom:20})
      Text(`${this.currentQuestionIndex+1}`+'.'+`${this.title[this.currentQuestionIndex]}`)
        .fontSize(18)
        .margin({bottom:5})
      Row() {
        Text(`${this.time[this.currentQuestionIndex]}`)
          .fontSize(15)
        Text(`${this.where[this.currentQuestionIndex]}`)
          .fontSize(15)
          // .margin(3)
          .margin({left:15})
      }

      // .margin({ top: '20vp' });
      Image('images/'+`${this.img[this.currentQuestionIndex]}`+".png")
        // .margin({left:10})
        .width("100%")
        .height("50%")
        .onClick(() => {
        });

      if(this.ende){
        Button('已全部报名')
          .margin(1)
          .fontSize(10)
          .fontColor(Color.White)
          .backgroundColor("#499c54")
          .padding({ left: '2vp', right: '2vp' }) // 调整按钮内边距
          .width("100%")
          .height("10%")
          .margin({ top: '4vp' })
      }else {

        Button('报名')
          .margin(1)
          .fontSize(10)
          .fontColor(Color.White)
          .backgroundColor("#499c54")
          .padding({ left: '2vp', right: '2vp' }) // 调整按钮内边距
          .width("100%")
          .height("10%")
          .margin({ top: '4vp' })
          .onClick(() => {
            this.flag[this.currentQuestionIndex]='1'
            this.onNextQuestion();
          });
      }


    }
    .alignItems(HorizontalAlign.Start)
    // .justifyContent(FlexAlign.End)
    .padding($r('app.float.column_padding'))
  }

}

下面是对主要功能的分析:

  1. onNextQuestion() 函数:
    当用户点击下一题时,该函数会被调用。
    计算下一个问题的索引(`nextIndex`),如果存在下一个问题,则更新`currentQuestionIndex`并重置显示解析状态(`showExplanation`)和结束状态(`ende`)。
    如果不存在下一个问题(即已经是最后一题),将结束状态(`ende`)设置为`true`
  2. build() 函数:
    构建用户界面的函数,是某个 UI 组件的构建方法。
    使用`Stack`组件,包含了一个图片、文本和按钮等元素。
    图片使用了`Image`组件,显示的图片路径是由`app.media.img1`动态生成的。
    `Column`组件包含了多个文本元素,显示了当前问题的标题、时间、地点和对应的图片。
    针对不同情况(是否已全部报名),显示不同的按钮:如果已全部报名,显示一个标签按钮,表示已经全部报名。如果还有问题需要回答,显示报名按钮。点击按钮时,会更新问题的标记(`flag`)为1,并调用`onNextQuestion`函数。
  3. 按钮部分:
    使用了`Button`组件构建按钮。
    按钮的文本为 “已全部报名” 或 “报名”,具体显示取决于问题是否已全部回答。
    如果是 “报名” 按钮,注册了一个点击事件(`onClick`),点击时会将当前问题的标记(`flag`)设置为1,并调用`onNextQuestion`函数。
    设置了按钮的样式,包括字体大小、字体颜色、背景颜色等。
  4. 布局和样式:
    使用了`alignItems`和`padding`来调整布局和样式。

这段代码实现了一个动态生成问题和相应按钮的用户界面,用户可以逐一回答问题,点击按钮进行报名。同时,通过控制`currentQuestionIndex`和`ende`可以在回答完所有问题后显示不同的界面状态。用于展示一系列活动,用户逐一报名。

3.4.2 元服务卡片-未报名最新活动列表

在这里插入图片描述

核心代码:(部分核心代码思想和所有活动卡片相似)

onNextQuestion() {
  const nextIndex = this.currentQuestionIndex + 1;
  if (nextIndex < this.title.length) {
    this.currentQuestionIndex = nextIndex;
    this.showExplanation = false; // 清空显示解析状态
    this.ende=false;
  } else {
    this.ende=true;
  }
}

//组会题目
@State title: string[] = [];
//组会地点
@State where: string[] = [];
//组会时间
@State time: string[] = [];
//组会图片
@State img: string[][] = [];
//是否报名'0', '0','0','0','0','0';
@State selectedscore: number = 0;
@State showExplanation: boolean = false; // 用于控制是否显示解析
@State ende: boolean = false; // 用于控制是否结束
@State currentQuestionIndex: number = 0;

代码的分析:

  1. 数据状态定义:
    `title`存储活动的标题数组。
    `where`存储活动地点的数组。
    `time`存储活动时间的数组。
    `img`存储活动图片路径的二维数组。
    `flag`存储每个活动是否报名的标志,用于判断按钮显示状态。
    `selectedscore`是用于记录用户选择的分数,但在这段代码中未被使用。
    `showExplanation`用于控制是否显示解析,但在这段代码中未被使用。
    `ende`用于控制是否结束,影响按钮的显示状态。
    `currentQuestionIndex`记录当前活动的索引。
  2. onNextQuestion() 函数:
    用于切换到下一个问题(活动)的函数。
    如果存在下一个问题,则更新当前问题的索引、清空解析状态,并将结束状态置为 `false`
    如果不存在下一个问题(即已经是最后一个问题),将结束状态置为 `true`
  3. build() 函数:
    构建用户界面的函数,是某个 UI 组件的构建方法。
    使用了 `Stack` 组件,包含了一个图片、文本和按钮等元素。
    图片使用了 `Image`组件,显示的图片路径是由 `app.media.img1` 动态生成的。
    `Column`组件包含了多个文本元素,显示了当前活动的标题、时间、地点和对应的图片。
    针对不同情况(是否已全部报名),显示不同的按钮:如果已全部报名,显示一个标签按钮,表示已经全部报名。如果还有问题需要回答,显示报名按钮;点击按钮时,会更新问题的报名状态,并调用 `onNextQuestion` 函数。

卡片效果图如下:
在这里插入图片描述

四.总结

本文简要概述了HarmonyOS NEXT和元服务的基本概念,引入了ArkTS语言的特性,为读者建立了一个全局的认识。文章详细介绍了DevEco Studio作为HarmonyOS开发工具的功能和使用方法,为读者提供了一个高效、便捷的开发环境。

在实际开发案例方面,文章以“活动召集令元服务”的开发为例,系统地呈现了整个开发过程。从项目准备、创建到具体功能开发,逐一阐述了登录页面、活动列表页面、个人中心页面等的设计和实现过程。这为读者提供了一个详尽的开发实践指南,展示了HarmonyOS元服务在解决实际问题中的强大功能。

此外,元服务卡片功能的详细解析也为读者提供了对这一特色功能的深入了解。通过核心代码分析,读者了解了如何构建动态生成问题和用户界面,以及如何通过状态管理实现用户逐一报名的流程。整个文章逻辑清晰,代码解释详尽,为读者提供了一份全方位的HarmonyOS元服务开发实践手册。

五.附录

Logo

讨论HarmonyOS开发技术,专注于API与组件、DevEco Studio、测试、元服务和应用上架分发等。

更多推荐