随着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容器分别作为应用界面的导航栏布局和课程内容布局,下面我们将为你一一道来。
图1 整体布局
1. 导航栏布局
应用界面的导航栏使用可滚动的容器组件Scroll来实现,Scroll内嵌Text组件用于显示“课程分类”名称,如图2所示:
图2 导航栏布局
Scroll容器必须内置一个子组件,我们使用了垂直方向的布局容器Column,并设置填充高度为height(\'100%\')。“课程分类”名称使用Text组件实现,并设置文字颜色为fontColor(0x696969)、文字大小为fontSize(16)、文字对齐方式为textAlign(TextAlign.Center)居中显示、高度为height(60)、宽度为width(\'100%\')。
相关代码如下:
- <pre><code>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)
- </code></pre><p></p><p class="\"paragraph"></p>
复制代码 2. 课程内容布局应用的课程内容部分的布局使用List列表容器来实现,并使用ForEach循环渲染listArray(课程内容)数据,如图3所示,课程内容布局包含头部和课程信息两部分(头部和左边的导航栏对应),下面我们将分别介绍头部及课程信息的布局的实现。
图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)
复制代码通过上文的介绍,我们已经实现了应用界面的布局以及界面数据的呈现,此时界面就像没有灵魂的躯壳。下面我们将为大家介绍应用中联动效果的实现。
四、实现界面联动本示例中的联动效果包括导航栏高亮显示、滑动课程内容对应导航栏变化、点击导航栏课程分类跳转到对应课程内容,下面我们将一一道来。
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描述、循环渲染、状态数据管理等机制,赶快趁热打铁,开发更多有趣的应用吧!