HarmonyOS 基于eTS高效开发HarmonyOS课程类应用

2024-01-21

随着HarmonyOS 3.0 Beta版的发布,API Version 8新增了大批JS/eTS API接口,相信很多开发者已经迫不及待想体验基于eTS的HamronyOS应用开发。本期Codelab,我们将基于API Version 8实现一个HarmonyOS课程类应用,帮助大家学习eTS的声明式UI描述、循环渲染、状态数据管理等机制,体验基于eTS的极简高效开发。

一、整体介绍

在课程类应用界面中,左侧为课程分类导航栏,右侧为各个类别的课程内容。当用户上下滑动右侧课程内容时,左侧导航栏会跳转至对应的课程分类。当用户点击左侧导航栏的课程分类时,会高亮显示点击的内容,且右侧课程内容会跳转至对应类别的课程列表。
那么如何基于eTS高效实现这样一个HarmonyOS课程类应用?下面我们将从声明式UI描述、循环渲染数据、状态数据管理三个维度来解析。
1. 声明式UI描述
界面布局是UI界面的骨架,决定了应用界面的交互和视觉风格。本示例中我们将通过eTS的一系列基础组件以声明式方式进行组合和扩展,并采用接近自然语义的编程语法直观地描述UI界面,包括参数构造配置、属性配置、事件配置以及子组件配置等。
相较于基于Java的命令式开发,eTS采用更接近自然语义的声明式编程语法,让开发者可以更直观地描述UI界面,有效地降低了开发者的上手成本,极大程度地提升了UI界面的构建效率,实现极简高效开发。
2. 循环渲染数据
数据模型是UI界面的肉体,描述了界面中数据的静态特征、动态行为和约束条件。本示例中我们将通过eTS直观地定义界面中各个模块的数据模型,包含名称、标识、图片链接等,并根据数据模型写入对应的数据。最后使用eTS提供的循环渲染机制(ForEach)将写入的数据循环渲染至对应的界面中。
相较于基于Java的命令式开发,eTS在渲染数据时UI的更新不需要开发者使用代码主动刷新,而是交给框架层自动处理,开发者不必关心框架如何实现UI绘制和渲染,实现界面数据的高效渲染。
3. 状态数据管理
联动效果是UI界面的灵魂,实现了界面中布局与数据的动态交互,本示例中我们将使用eTS提供的状态数据管理机制通过装饰组件拥有的状态属性,当装饰的变量更改时,组件会重新渲染更新UI界面数据,从而实现联动效果。
相较于基于Java的命令式开发,eTS通过功能不同的装饰器给开发者提供了清晰的页面更新渲染流程和管道。开发者要做的就是定义数据与UI的映射关系,后面只需要通过状态装饰器监听数据的状态,UI即可自动刷新,这极大地减轻了程序员对UI的维护工作。
以上就是实现课程类应用的核心原理,下面我们将为大家带来各部分的具体实现。

二、搭建界面布局

eTS提供了多种布局方式,不仅保留了经典的弹性布局能力,也提供了列表、宫格、栅格布局和适应多分辨率场景开发的原子布局能力。如图1所示,本示例中整体布局使用Row沿水平方向布局容器,并设置背景颜色为白色,Row内部嵌套Scroll及List容器分别作为应用界面的导航栏布局和课程内容布局,下面我们将为你一一道来。
img

                                   图1 整体布局

1. 导航栏布局
应用界面的导航栏使用可滚动的容器组件Scroll来实现,Scroll内嵌Text组件用于显示“课程分类”名称,如图2所示:
img

                                  图2 导航栏布局

Scroll容器必须内置一个子组件,我们使用了垂直方向的布局容器Column,并设置填充高度为height(‘100%’)。“课程分类”名称使用Text组件实现,并设置文字颜色为fontColor(0x696969)、文字大小为fontSize(16)、文字对齐方式为textAlign(TextAlign.Center)居中显示、高度为height(60)、宽度为width(‘100%’)。
相关代码如下:

Scroll() {
  Column() {
    ForEach(this.tabArray.map((item1, index1) => {
      return { index: index1, data: item1 };
    }), item => {
      Text(item.data.superName)
        .fontColor(0x696969)
        .backgroundColor(this.index == item.index ? 0xffffff : null)
        .fontSize(16)
        .width('100%')
        .height(60)
        .textAlign(TextAlign.Center)
        .onClick(() => {
          if (this.index != item.index) {
            this.index = item.index
            this.scroller.scrollToIndex(item.data.position)
          }
        })
    }, item => '' + item.data)
  }.height('100%')
}.width(100).height('100%').backgroundColor(0xdddddd).scrollBar(BarState.Off)

2. 课程内容布局
应用的课程内容部分的布局使用List列表容器来实现,并使用ForEach循环渲染listArray(课程内容)数据,如图3所示,课程内容布局包含头部和课程信息两部分(头部和左边的导航栏对应),下面我们将分别介绍头部及课程信息的布局的实现。
img

                                 图3 课程内容布局

(1) 头部布局
头部使用Text组件实现,并设置了文字颜色为fontColor(0x696969)、文字大小为fontSize(20)、高度为height(40)、宽度为width(‘100%’)填充、内边距为padding({ left: 10 })、背景颜色为backgroundColor(0xefefef)。同时,头部的ListItem设置了sticky(Sticky.Normal)属性,使其滑动到顶部时可以呈现固定的效果。代码如下:

if (item.tag) {
  ListItem() {
    Text(item.courseName)
      .fontColor(0x696969)
      .fontSize(20)
      .height(40)
      .width('100%')
      .padding({ left: 10 })
      .backgroundColor(0xefefef)
  }.sticky(Sticky.Normal)

(2) 课程信息布局
课程信息部分使用Stack堆叠容器,高度设置为height(120),设置子组件在容器内的对齐方式为Alignment.TopStart。Stack组件内嵌Image、Text、Divider组件,用于呈现课程图片、课程标题、课程价格及分割线等信息。具体如下:
●课程的图片使用Image组件呈现,设置宽度为width(130),高度为height(100),图片的缩放类型为objectFit(ImageFit.Fill),使图片填充满组件,并设置左边、顶部边距均10,使其组件居中对齐。代码如下:

Image(item.imageUrl)
  .objectFit(ImageFit.Cover)
  .width(130)
  .height(100)
  .margin({ left: 10, top: 10 })

●课程名称使用Text组件,设置文字颜色为fontColor(0x363636)、文字大小为fontSize(14),最大显示行数为maxLines(2)两行,文本超长时的显示方式为TextOverflow.Clip,意为进行裁剪显示。代码如下:

Text(item.courseName)
  .fontColor(0x363636)
  .fontSize(14)
  .margin({ left: 150, top: 12 })
  .maxLines(2)
  .textOverflow({ overflow: TextOverflow.Clip })

●课程的价格使用Text组件,设置文字颜色为fontColor(0xff6600)、文字大小为fontSize(24),并使用了绝对定位position({ x: 0, y: ‘100%’ })先让组件显示在Stack容器最底部的外边界下,然后使用锚点定位markAnchor({ x: 0, y: ‘100%’ })使组件以自身高度向上偏移,让组件显示在Stack容器最底部。代码如下:

Text(item.price == 0 ? '免费' : '¥' + item.price)
  .fontColor(0xff6600)
  .fontSize(24)
  .position({ x: 0, y: '100%' })
  .markAnchor({ x: 0, y: '100%' })
  .margin({ bottom: 18, left: 150 })

●分割线使用Divider组件实现,并设置了分割线颜色为color(0xefefef)、分割线宽度为strokeWidth(0.7)、左右边距margin({ left: 10, right: 10 })均为10,并使用绝对定位position({ x: 0, y: ‘100%’ })和锚点定位markAnchor({ x: 0, y: ‘100%’ })使分割线呈现在Stack容器最底部。代码如下:

Divider()
  .margin({ left: 10, right: 10 })
  .color(0xefefef)
  .strokeWidth(0.7)
  .position({ x: 0, y: '100%' })
  .markAnchor({ x: 0, y: '100%' })

至此,我们已经实现了界面布局,此时界面只是一个没有任何内容的骨架。接下来我们将为应用界面添加数据模型。

三、构建数据模型

本章节我们将为大家介绍本示例中数据模型的定义、数据的预置以及数据的加载。
1. 定义数据模型
本示例需定义两个数据模型,分别是应用界面中左侧导航栏的“课程分类”和右侧的“课程内容”。其中:
● 导航栏“课程分类”定义了名称(superName)和位置(position)属性。数据模型定义如下:

export class TabItem {
  position: number; // 点击该分类时课程内容滑动到的位置
  superName: string; // 课程分类标题


  constructor(position: number, superName: string) {
    this.position = position;
    this.superName = superName;
  }
}

● “课程内容”定义了课程的名称(courseName)、课程的图片地址(imageUrl)、课程的价格(price)、判断是否为此类别课程头部的变量(tag)、课程所对应课程类别的索引位置(index),数据模型定义如下:

export class CourseItem {
  tag: boolean; // 是否此类别课程的头部
  index: number; // 课程所对应课程类别的索引位置
  courseName: string; // 课程名称
  imageUrl: string; // 图片地址
  price: number; // 价格
  constructor(tag: boolean, index: number, courseName: string, imageUrl: string, price: number) {
    this.tag = tag;
    this.index = index;
    this.courseName = courseName;
    this.imageUrl = imageUrl;
    this.price = price;
  }
}

2. 预置数据
在entry/src/main/ets/MainAbility/Model.ets文件中,放入准备好的模拟数据,其中,superId是课程分类的唯一标识、id是课程内容的唯一标识。格式如下:

const LinkData: any[] = [
  { 
    "superId": 1, 
    "superName": "热门课程", 
    "id": 1, 
    "courseName": "应用市场介绍", 
    "imageUrl": "/image/image1.jpg", 
    "price": 0 
  }, 
{
    "superId": 1,
    "superName": "热门课程",
    "id": 2,
    "courseName": "上架流程",
    "imageUrl": "/image/image2.jpg",
    "price": 100
  },
  ... 
]

3. 加载数据更新UI
本章节将介绍如何加载本地构造的模拟数据并呈现到界面。
(1) 加载数据
在index.ets文件中,通过getLinkData()获取预置数据。

aboutToAppear() {
  // 延时数据加载
  setTimeout(() => {
    let linkDataItem = getLinkData();
    this.tabArray = linkDataItem.tabArray;
    this.listArray = linkDataItem.listArray;
    this.requestSuccess = true;
  }, 2000)
}

(2) 渲染
● 导航栏使用ForEach循环渲染tabArray(课程分类)数据。这里的ForEach的第一个参数需要使用数组的map()方法遍历。代码如下:

let superId: number = 0
model.forEach((item) => {
  if (superId != item.superId) {
    let tabItem = new TabItem(this.listArray.length, item.superName);
    this.tabArray.push(tabItem)


    let courseItem = new CourseItem(true, this.tabArray.length - 1, item.superName, '', 0);
    this.listArray.push(courseItem)
  }
})

● 课程内容使用ForEach循环渲染listArray(课程内容)数据。代码如下:

ForEach(this.listArray, item => {
  if (item.tag) {
    ListItem() {
    ......
    }.sticky(Sticky.Normal)
  } else {
    ListItem() {
      Stack({ alignContent: Alignment.TopStart }) {
      ......
      }.height(120)
    }
  }
}, item => '' + item)

通过上文的介绍,我们已经实现了应用界面的布局以及界面数据的呈现,此时界面就像没有灵魂的躯壳。下面我们将为大家介绍应用中联动效果的实现。

四、实现界面联动

本示例中的联动效果包括导航栏高亮显示、滑动课程内容对应导航栏变化、点击导航栏课程分类跳转到对应课程内容,下面我们将一一道来。
img
1. 导航栏高亮显示
实现导航栏高亮显示,主要通过@State装饰器监听导航栏课程分类的索引值的状态变化,当用户滑动课程内容或点击导航栏,对应课程分类的索引值发生变化,此时将调用build方法进行UI刷新,从而实现导航栏背景色的修改。相关代码如下:

@State index: number= 0; // 导航栏课程分类的索引
Text(item.data.superName)
  .backgroundColor(this.index == item.index ? 0xffffff : null)

2. 右边滑动,左边变化
滑动右边课程内容,对应左边导航栏变化,主要通过onScrollIndex()事件来判断当前课程内容所属的课程分类,当用户滑动课程内容时,对应课程内容的索引值发生变化,导航栏的课程分类的索引值也随之变化。相关代码如下:

private scroller: Scroller = new Scroller()
List({ scroller: this.scroller }) {
  ......
  }
.onScrollIndex((firstIndex: number) => {
  if (this.index != this.listArray[firstIndex].index) {
    this.index = this.listArray[firstIndex].index
  }
})

3. 左边点击,右边跳转
点击左边导航栏跳转到对应课程内容,主要通过onClick()事件监听用户点击,当用户点击导航栏,scrollToIndex()方法会根据点击的导航栏课程分类的索引值跳转到对应的课程内容界面。相关代码如下:

Text(item.data.superName)
  .onClick(() => {
    if (this.index != item.index) {
      this.index = item.index
      this.scroller.scrollToIndex(item.data.position)
    }
  })

以上就是本期全部内容,通过本期Codelab的学习,相信你已经掌握了eTS的声明式UI描述、循环渲染、状态数据管理等机制,赶快趁热打铁,开发更多有趣的应用吧!

最后在这里分享一份《鸿蒙(HarmonyOS)开发学习指南》,需要的朋友可以扫码免费领取!!!

《鸿蒙(HarmonyOS)开发学习指南》

第一章 快速入门

1、开发准备

2、构建第一个ArkTS应用(Stage模型)

3、构建第一个ArkTS应用(FA模型)

4、构建第一个JS应用(FA模型)

5、…

图片

第二章 开发基础知识

1、应用程序包基础知识

2、应用配置文件(Stage模型)

3、应用配置文件概述(FA模型)

4、…

图片

第三章 资源分类与访问

1、 资源分类与访问

2、 创建资源目录和资源文件

3、 资源访问

4、…

图片

第四章 学习ArkTs语言

1、初识ArkTS语言

2、基本语法

3、状态管理

4、其他状态管理

5、渲染控制

6、…

图片

第五章 UI开发

1.方舟开发框架(ArkUI)概述

2.基于ArkTS声明式开发范式

3.兼容JS的类Web开发范式

4…

图片

第六章 Web开发

1.Web组件概述

2.使用Web组件加载页面

3.设置基本属性和事件

4.在应用中使用前端页面JavaScript

5.ArkTS语言基础类库概述

6.并发

7…

图片

11.网络与连接

12.电话服务

13.数据管理

14.文件管理

15.后台任务管理

16.设备管理

17…

图片

第七章 应用模型

1.应用模型概述

2.Stage模型开发指导

3.FA模型开发指导

4…

图片

扫描下方二维码免费领取,《鸿蒙(HarmonyOS)开发学习指南》

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

HarmonyOS 基于eTS高效开发HarmonyOS课程类应用 的相关文章

  • SpinnerAdapter 中 getView 和 getDropDownView 的区别

    当你实现 SpinnerAdapter 时 你会得到获取下拉视图 http developer android com reference android widget SpinnerAdapter html getDropDownView
  • 我可以为 Android Activity 分配“默认”OnClickListener() 吗?

    我有一个 Activity 对于布局中的每个小部件 我调用 setOnClickListener 来分配我的 OnClick 处理程序 在我的 OnClick 处理程序中 我使用 switch 语句根据 View 参数的 ID 为每个按钮执
  • 图像识别后如何在vuforia sdk ImageTarget中显示布局而不是茶壶模型

    如果图像在 qualcomm vuforia sdk 中被识别 我们如何在布局中显示简单的文本 即 Hello 我正在使用 ImageTarget 的 qualcomm vuforia sdk 示例 现在它在识别图像后显示一个茶壶 我是增强
  • Android 堆栈大小

    我如何获取和更改 Android 应用程序的堆栈大小 即使是主线程 主线程堆栈大小是在固件中设置的 无法修改 除非修改您自己手机的固件 正如斯特朗先生指出的那样 对于您分叉的线程 您可以设置自己的堆栈大小
  • 我的手机设备上的 adb shell:出现奇怪的字符(终端颜色问题)

    我有一台配备 DarkyRom 10 4 2 XWJW1 Android 2 3 6 和 root 访问权限的 Samsung Galaxy S 我正在实现一个应用程序 我想使用 eclipse 在手机中执行它 但出现错误 Activity
  • Android:如何根据视图模型实时数据属性为片段编写单元测试?

    我的片段 UI 中有一个列表视图 其元素集取决于来自视图模型 LiveData 属性的值的状态 我想为片段创建工具测试 该片段包含与该属性的值集相关的 3 个场景测试用例 但我不知道从哪里开始 我的代码应该如下所示 class MyView
  • 删除所有(子)片段的正确方法

    我在父级片段线性布局 fragmentContainer 中动态加载一堆子级片段 然后当用户单击按钮时 我需要将它们全部删除并添加新的 我不知道每次会添加多少碎片 这是我一次性删除所有碎片的方法 LinearLayout ll Linear
  • Android - 在通知栏中使用外部个人资料图像,如 Facebook

    我知道您可以在推送通知参数中发送信息 例如消息 标题 图像 URL 等 Facebook 如何在通知区域中显示您的个人资料图片和消息 我想在通知区域中使用外部图像 因此当您将其下拉时 您会看到带有消息的个人资料图像 现在 我的仅显示可绘制文
  • 这个错误从何而来?错误:com.facebook.FacebookException:无法获取应用程序名称

    我无法弄清楚这一点 我已将我的密钥哈希和所有内容添加到 Facebook 网页 但我无法找出此错误 11 12 19 51 27 744 D HelloFacebook 5188 Error com facebook FacebookExc
  • Android 操作系统上的 NFC 堆栈

    有人可以帮助我了解 NFC Android 堆栈的当前状态吗 随着OS 2 3发布了小型 NFC 支持 仅限于 NXP 标签读取 后来 Google 增强了 API 所以在OS 2 3 3支持更广泛的标签 并且还可以使用 p2p 我的问题是
  • 使用动画来滑动视图

    我有一个可以识别滑动手势 向上和向下 的 FrameLayout 例如 如果执行向上滑动 我应该对当前视图 即 MATCH PARENT x MATCH PARENT 进行动画处理 使其向上移动 同时新视图来自底部 我可以用动画来实现这一点
  • 如何在Android上获取角度中的按键事件?

    我们如何在 Android 上的 Angular 中获取按键事件及其值 我使用phonegap Cordova Angular JS
  • setOnTouchListener() 给我一个错误

    button setOnTouchListener new OnTouchListener public void onClick View v Toast makeText MainActivity this YOUR TEXT 5000
  • Android Studio 3.0 - 设置未保存

    我已将 文件 gt 设置 gt 编辑器 gt 代码样式 中的 右边距 列 从默认的 100 增加到 140 不幸的是 每次重新启动 Android Studio 后 该边距都会重置 我还尝试导出和导入我的设置 但这并不能阻止重置右边距 希望
  • 如何在 Google 地图中创建自定义地图?

    我正在尝试创建一个包含我家地图的 Google 地图应用程序 卧室 浴室 厨房等 使用 GPS 我会找到我现在在家里的位置 并尝试获取到我卧室的方向 步行距离 您可以使用Google的API来获取方向 我需要知道的是 如何添加我家的自定义地
  • 如何从 SD 卡中删除文件

    我正在创建一个文件作为电子邮件的附件发送 现在我想在发送电子邮件后删除图像 有没有办法删除文件 我努力了myFile delete 但它没有删除该文件 我在 Android 上使用此代码 因此编程语言是 Java 使用通常的 Android
  • SWIG C 函数指针和 JAVA

    我有一些 C 代码 其中一个方法有一个函数指针作为参数 我正在尝试在我的 Android 应用程序中使用 C 代码 我决定使用 SWIG 来完成生成我需要的 java 文件的所有工作 一切都适用于常规函数 没有函数指针作为参数的函数 但我不
  • Android:列表视图崩溃

    我正在使用 android listview 并且它工作得很好 我的实现如下 ListView listview ListView findViewById R id list setListAdapter new ArrayAdapter
  • 如何创建克隆重复视图?

    在我的 Android 应用程序中 我想创建重复的ImageButton已经创建的Imagebutton 我想创造新的Imagebutton以编程方式与 XML 文件中已创建的按钮具有相同的宽度 高度 背景 图像源 边距等 简而言之 我想创
  • 如何在给定的纬度和经度处使用标记/覆盖项目启动地图意图?

    我有一个纬度和经度 我想打开以该点为中心的谷歌地图 所以我使用以下代码 Intent intent new Intent android content Intent ACTION VIEW Uri parse geo lat lng st

随机推荐