Harmony ArkTS语言

ArkTS语言

  • 前言
  • 正文
    • 一、声明式UI
    • 二、数据列表
      • ① 创建ArkTS文件
      • ② 添加资源
      • ③ 样式
      • ④ 组件
      • ⑤ 标题组件
      • ⑥ 列表头组件
      • ⑦ 列表Item组件
      • ⑧ 组件生命周期
      • ⑨ 渲染列表数据
      • ⑩ 单选
    • 三、源码

  随着华为宣布鸿蒙后续的版本不再兼容Android应用之后,对于现在的开发环境来说有一些冲击,一部分人想做鸿蒙应用开发,另一部分人觉得鸿蒙现在就想替换安卓还为之尚早,不管怎么说,学习是没有错的,哪怕是作为知识储备也是好的,今天就简单说一下鸿蒙应用开发支持的主流语言 ArkTS

前言

  说到ArkTS就得说一下DevEco Studio的演变过程,在我写一篇关于鸿蒙的文章时,DevEco Studio才刚推出不久,当时所支持的语言是Java、JS、C++等,在后续的版本中逐渐去掉了Java,C++,最终使用到了ArkTS,那么我们下面来了解一下ArkTS的由来。

正文

  ArkTS是HarmonyOS主力应用开发语言,它在TypeScript(简称TS)的基础上,匹配ArkUI框架,扩展了声明式UI、状态管理等相应的能力,让开发者以更简洁、更自然的方式开发跨端应用,如果你之前接触过FlutterDartKotlinCompose,那么你对于这个ArkTS的使用应该问题不大。

  ArkTS的构成如下图所示

在这里插入图片描述

  JavaScriptTypeScriptArkTS的关系:

  • JavaScript是一种属于网络的高级脚本语言,已经被广泛用于Web应用开发,常用来为网页添加各式各样的动态功能,为用户提供更流畅美观的浏览效果。
  • TypeScriptJavaScript的一个超集,它扩展了JavaScript的语法,通过在JavaScript的基础上添加静态类型定义构建而成,是一个开源的编程语言。
  • ArkTS基于TypeScript语言,拓展了声明式UI、状态管理、并发任务等能力。

一、声明式UI

  声明式UI有两个特征分别是:声明式描述状态驱动视图更新,那么怎么体现这一点呢?我们结合代码来说明,首先打开DevEco Studio,这里我使用的版本是DevEco Studio 3.1.1 Release,创建工程。

在这里插入图片描述

点击Next。

在这里插入图片描述

输入项目名称和包名,这里会说明使用的方式和语言及SDK版本和使用的设备类型,修改好之后点击Finish,完成项目的创建。

在这里插入图片描述

完成创建之后我们就可以看到index.ets的代码如图所示,下面我们对这段代码进行一个解析:

  • 装饰器,用来装饰类、结构体、方法以及变量,赋予其特殊的含义,如上述示例中 @Entry@Component@State 都是装饰器。具体而言, @Component 表示这是个自定义组件; @Entry 则表示这是个入口组件; @State 表示组件中的状态变量,此状态变化会引起 UI 变更。
  • 自定义组件,可复用的 UI 单元,可组合其它组件,如上述被 @Component 装饰的 struct Index
  • UI 描述,声明式的方式来描述 UI 的结构,如上述 build() 方法内部的代码块。
  • 内置组件,框架中默认内置的基础和布局组件,可直接被开发者调用,比如示例中的 Row、Column、Text
  • 事件方法,用于添加组件对事件的响应逻辑,统一通过事件方法进行设置,比如为组件添加onClick()
  • 属性方法,用于组件属性的配置,统一通过属性方法进行设置,如fontSize()、width()、height()、color() 等,可通过链式调用的方式设置多项属性。

下面我们预览看一下,点击右侧边栏的Previewer,等待一小会。

在这里插入图片描述

下面我们修改index.ets中的代码,然后Ctrl + S保存一下,右侧的预览画面就会更新。

在这里插入图片描述

预览更新后

在这里插入图片描述

点击Test按钮之后

在这里插入图片描述

  这里我们添加了一个按钮,同时添加了点击事件,事件中修改了message的值,而message是由@State修饰的,那么就会出发UI刷新,刷新后,Text组件所显示的内容就会从Hello World变成Hello ArkTS,这就是声明式UI的另一特征:状态驱动视图更新

二、数据列表

  上面的示例比较简单,下面我们做一个有点难度的示例,该示例源于鸿蒙学堂官网,感兴趣的可以去学习。

在这里插入图片描述

① 创建ArkTS文件

  首先我们在ets目录下创建一个model目录,model目录下创建一个RankData.ets文件,代码如下所示:

export class RankData {id: number | stringname: Resourcevote: stringconstructor(id: number | string, name: Resource, vote: string) {this.id = id;this.name = name;this.vote = vote;}
}

  这里的代码简单说明一下,export 表示可以在其他模块中使用,这里的含义就在于我们将RankData反正model目录下,如果我们pages下要使用这个RankData,则RankData本身就需要支持调用才行,因此需要export进行修饰,调用的地方则使用import作为插入。

而id属性的定义是一个联合类型,这属于TypeScript的基础数据类型,表示取值可以为多种类型中的一种。number表示数字,string就是字符串,Resource就表示资源,比如string.app_name这种方式。构造函数就没有什么好说的了,就是属性赋值而已。

② 添加资源

  我们看到和ets目录平级的是resources,该目录下毫无疑问就是资源目录,目录下有三个文件夹,base属于基础资源目录里面可以放置文字、颜色、音频、配置文件等,en_US就是英文下的文字资源,zh_CN就是中文下的文字资源,三个目录下的文字资源文件都是json格式的,下面我们修改base/element/string.jsonen_US/element/string.json中的代码:

{"string": [{"name": "module_desc","value": "module description"},{"name": "EntryAbility_desc","value": "description"},{"name": "EntryAbility_label","value": "label"},{"name": "page_type","value": "variety"},{"name": "page_number","value": "ranking"},{"name": "page_vote","value": "vote"},{"name": "prompt_text","value": "Press again to exit the app"},{"name": "title","value": "Ranking List"},{"name": "title_default","value": " "},{"name": "fruit_watermelon","value": "watermelon"},{"name": "fruit_apple","value": "apple"},{"name": "fruit_banana","value": "banana"},{"name": "fruit_grapes","value": "grapes"},{"name": "fruit_red_grape","value": "grape"},{"name": "fruit_pears","value": "pears"},{"name": "fruit_pineapple","value": "pineapple"},{"name": "fruit_durian","value": "durian"},{"name": "fruit_guava","value": "guava"},{"name": "fruit_carambola","value": "carambola"}]
}

再修改zh_CN/element/string.json中的代码:

{"string": [{"name": "module_desc","value": "模块描述"},{"name": "EntryAbility_desc","value": "description"},{"name": "EntryAbility_label","value": "label"},{"name": "page_type","value": "种类"},{"name": "page_number","value": "排名"},{"name": "page_vote","value": "得票数"},{"name": "prompt_text","value": "再按一次退出程序"},{"name": "title","value": "排行榜"},{"name": "title_default","value": " "},{"name": "fruit_watermelon","value": "西瓜"},{"name": "fruit_apple","value": "苹果"},{"name": "fruit_banana","value": "香蕉"},{"name": "fruit_grapes","value": "葡萄"},{"name": "fruit_red_grape","value": "红提"},{"name": "fruit_pears","value": "梨子"},{"name": "fruit_pineapple","value": "菠萝"},{"name": "fruit_durian","value": "榴莲"},{"name": "fruit_guava","value": "番石榴"},{"name": "fruit_carambola","value": "杨桃"}]
}

下面我们制造一些假数据,在model包下新建一个DataModel.ets文件,代码如下所示:

import { RankData } from './RankData'export {rankData1, rankData2}const rankData1: RankData[] = [new RankData(1, $r('app.string.fruit_apple'), "10000"),new RankData(2, $r('app.string.fruit_grapes'), '10320'),new RankData(3, $r('app.string.fruit_watermelon'), '9801'),new RankData(4, $r('app.string.fruit_banana'), '8431'),new RankData(5, $r('app.string.fruit_pineapple'), '7546'),new RankData(6, $r('app.string.fruit_durian'), '7431'),new RankData(7, $r('app.string.fruit_red_grape'), '7187'),new RankData(8, $r('app.string.fruit_pears'), '7003'),new RankData(9, $r('app.string.fruit_carambola'), '6794'),new RankData(10, $r('app.string.fruit_guava'), '6721')
]const rankData2: RankData[] = [new RankData('11', $r('app.string.fruit_watermelon'), '8836'),new RankData('12', $r('app.string.fruit_apple'), '8521'),new RankData('13', $r('app.string.fruit_banana'), '8431'),new RankData('14', $r('app.string.fruit_grapes'), '7909'),new RankData('15', $r('app.string.fruit_red_grape'), '7547'),new RankData('16', $r('app.string.fruit_pears'), '7433'),new RankData('17', $r('app.string.fruit_pineapple'), '7186'),new RankData('18', $r('app.string.fruit_durian'), '7023'),new RankData('19', $r('app.string.fruit_guava'), '6794'),new RankData('20', $r('app.string.fruit_carambola'), '6721')
];

  这里我们首先导入RankData,然后创建了两个数组,数组中通过RankData构建函数进行bean的构建,注意这里的id,我可以使用number也可以使用string,同时资源的引用是 $rr就表示resource,使用app.string引用文字资源,你还可以app.color等一些方式引用其他类型资源,构建了两个数组,然后导出这两个数组在其他文件中使用。

  现在有了模拟数据之后,我们可以再创建一个类去提供模拟数据,在model包下新建一个RankViewModel.ets文件,代码如下所示:

import { RankData } from './RankData';
import { rankData1, rankData2 } from './DataModel';export class RankViewModel {loadRankDataSource1(): RankData[] {return rankData1;}loadRankDataSource2(): RankData[] {return rankData2;}
}

  这里导入了RankData和DataModel,通过在RankViewModel中进行返回数据得到具体的数据数组。这个其实和Android的MVI架构差不多,下面我们再添加一些colors资源,在后面的样式上会用到,修改base/element/color.json文件,代码如下所示:

{"color": [{"name": "start_window_background","value": "#FFFFFF"},{"name": "white","value": "#FFFFFF"},{"name": "rank_first_gradient_start","value": "#FFFF9A"},{"name": "rank_first_gradient_end","value": "#CCA538"},{"name": "rank_first_border","value": "#9E8A24"},{"name": "rank_first_text","value": "#9E8A24"},{"name": "rank_secondary_gradient_start","value": "#B8B8B8"},{"name": "rank_secondary_gradient_end","value": "#9C9C9C"},{"name": "rank_secondary_border","value": "#7E7E7E"},{"name": "rank_secondary_text","value": "#FFFFFF"},{"name": "rank_third_gradient_start","value": "#B9A185"},{"name": "rank_third_gradient_end","value": "#AE8659"},{"name": "rank_third_border","value": "#775C3E"},{"name": "rank_third_text","value": "#FFFFFF"},{"name": "rank_view_color_holder","value": "#FFFFFF"},{"name": "item_color","value": "#007DFF"},{"name": "item_color_black","value": "#182431"},{"name": "background","value": "#F1F3F5"},{"name": "font_description","value": "#989A9C"},{"name": "circle_text_background","value": "#007dff"}]
}

除此之外还有三个图标,你可以在我的源码中获取,放在resources/base/media

在这里插入图片描述

  其中icon.png是创建工程时自带的图标,如果你觉得Project模式下文件过多,你可以切换为Ohos模式。

在这里插入图片描述

这样看起来比较简洁,只不过你需要熟悉文件结构才行。

③ 样式

  在进行鸿蒙应用开发时,通常会将样式和代码进行分离,这一点是很常见了,我们在ets目录下新建一个constants文件夹,该目录下新建一个Constants.ets文件,代码如下:

/*** 字体大小*/
export enum FontSize {SMALL = 14,MIDDLE = 16,LARGE = 20,
};/*** 字体粗细*/
export enum FontWeight {BOLD = '400',BOLDER = '500',
};/*** 权重是组件大小的全局默认值。*/
export const WEIGHT = '100%';/*** Toast 出现的时间*/
export const TIME = 1000;/*** App退出的间隔时间*/
export const APP_EXIT_INTERVAL: number = 4500;/*** 页面TAG*/
export const TAG: string = 'Index';/*** 标题内容*/
export const TITLE: Resource = $r('app.string.title');class style {RANK_PADDING: number = 15;        // 排名填充CONTENT_WIDTH: string = '90%';    // 内容的宽度BORDER_RADIUS: number = 20;       // 边界半径STROKE_WIDTH: number = 1;         // 描边宽度HEADER_MARGIN_TOP: number = 20;   // 距离上边距HEADER_MARGIN_BOTTOM: number = 15;// 距离下边距LIST_HEIGHT: string = '65%';      // List高度
}/*** 页面样式*/
export const Style: style = {RANK_PADDING: 15,CONTENT_WIDTH: '90%',BORDER_RADIUS: 20,STROKE_WIDTH: 1,HEADER_MARGIN_TOP: 20,HEADER_MARGIN_BOTTOM: 15,LIST_HEIGHT: '65%'
};class listHeaderStyle {FONT_WEIGHT: number = 400;            // 字体粗细LAYOUT_WEIGHT_LEFT: string = '30%';   // 左边的布局权重LAYOUT_WEIGHT_CENTER: string = '50%'; // 中间的布局权重LAYOUT_WEIGHT_RIGHT: string = '20%';  // 右边的布局权重
}/*** 列表标题样式*/
export const ListHeaderStyle: listHeaderStyle = {FONT_WEIGHT: 400,LAYOUT_WEIGHT_LEFT: '30%',LAYOUT_WEIGHT_CENTER: '50%',LAYOUT_WEIGHT_RIGHT: '20%',
};class itemStyle {TEXT_LAYOUT_SIZE: number = 24;          // 文本的行高CIRCLE_TEXT_BORDER_RADIUS: number = 24; // 圆形文本的边框半径CIRCLE_TEXT_SIZE: number = 24;          // 圆圈文本的大小CIRCLE_TEXT_COLOR_STOP_1: number = 0.5; // 渐变色比例1CIRCLE_TEXT_COLOR_STOP_2: number = 1.0; // 渐变色比例2BAR_HEIGHT: number = 48;                // item高度LAYOUT_WEIGHT_LEFT: string = '30%';     // 左边的布局权重LAYOUT_WEIGHT_CENTER: string = '50%';   // 中间的布局权重LAYOUT_WEIGHT_RIGHT: string = '20%';    // 右边的布局权重BORDER_WIDTH: number = 1;               // 边框宽度COLOR_BLUE: Resource = $r('app.color.item_color');     // 文字蓝色COLOR_BLACK: Resource = $r('app.color.item_color_black');    // 文字黑色
}/*** 列表Item样式*/
export const ItemStyle: itemStyle = {TEXT_LAYOUT_SIZE: 24,CIRCLE_TEXT_BORDER_RADIUS: 24,CIRCLE_TEXT_SIZE: 24,CIRCLE_TEXT_COLOR_STOP_1: 0.5,CIRCLE_TEXT_COLOR_STOP_2: 1.0,BAR_HEIGHT: 48,LAYOUT_WEIGHT_LEFT: '30%',LAYOUT_WEIGHT_CENTER: '50%',LAYOUT_WEIGHT_RIGHT: '20%',BORDER_WIDTH: 1,COLOR_BLUE: $r('app.color.item_color'),COLOR_BLACK: $r('app.color.item_color_black')
};class titleBarStyle {IMAGE_BACK_SIZE: number = 21;         // 后退按钮的图像大小IMAGE_BACK_MARGIN_RIGHT: number = 18; // 后退按钮的右边距IMAGE_LOADING_SIZE: number = 22;      // 刷新按钮的图像大小BAR_HEIGHT: number = 47;              // 标题栏的高度BAR_MARGIN_HORIZONTAL: number = 26;   // 标题组件的水平边距BAR_MARGIN_TOP: number = 10;          // 标题组件的上边距WEIGHT: string = '50%';               // 行布局的权重
}/*** 标题栏样式*/
export const TitleBarStyle: titleBarStyle = {IMAGE_BACK_SIZE: 21,IMAGE_BACK_MARGIN_RIGHT: 18,IMAGE_LOADING_SIZE: 22,BAR_HEIGHT: 47,BAR_MARGIN_HORIZONTAL: 26,BAR_MARGIN_TOP: 10,WEIGHT: '50%',
};

  这里的代码乍一看很多,不好理解,其实我们分析一下就知道是写什么属性,首先我们定义了页面字体大小和粗细的枚举类型,用于设置标题文字和其他文字,然后就是页面的权重、退出App的提示时间等、接着就是定义页面样式、标题栏样式、列表头样式、列表Item样式,通过注释你可以你知道每一个样式是什么意思,熟能生巧,你现在觉得不适应是因为不熟悉的缘故。

④ 组件

  在ArkTS中组件是一个比较重要的知识点,组件也分为三个类型,基础组件、容器组件和自定义组件。

  • 基础组件,比如Text、Button、Image、TextInput等。
  • 容器组件,比如Column、Row、Stack、List等。
  • 自定义组件,则是根据实际的功能需求,由开发者自己组合使用基础组件和容器组件变成新的功能组件。比如页面的标题栏,左侧是返回按钮,中间是标题文字,可能还会有副标题,右侧是功能按钮,这种就是自定义组件。

⑤ 标题组件

  下面我们来自定义一个组件,做一个标题栏组件,效果如下图所示:

在这里插入图片描述

首先我们在ets目录下新建一个view文件夹,该目录下新建一个TitleComponent.ets文件,代码如下:

/*** 自定义页面标题组件*/
import AppContext from '@ohos.app.ability.common'
import { FontSize, TitleBarStyle, WEIGHT } from '../constants/Constants'@Component
export struct TitleComponent {@Link isRefreshData: boolean //是否刷新数据@State title: Resource = $r('app.string.title_default')build() {Row() {Row() {//返回图标Image($r('app.media.ic_public_back')).height(TitleBarStyle.IMAGE_BACK_SIZE).width(TitleBarStyle.IMAGE_BACK_SIZE).margin({ right: TitleBarStyle.IMAGE_BACK_MARGIN_RIGHT }).onClick(() => {let handler = getContext(this) as AppContext.UIAbilityContexthandler.terminateSelf() //杀死程序})//标题文字Text(this.title).fontSize(FontSize.LARGE)}.width(TitleBarStyle.WEIGHT).height(WEIGHT).justifyContent(FlexAlign.Start) //内容左对齐Row() {//刷新图标Image($r('app.media.loading')).height(TitleBarStyle.IMAGE_LOADING_SIZE).width(TitleBarStyle.IMAGE_LOADING_SIZE).onClick(() => {this.isRefreshData = !this.isRefreshData //修改刷新状态})}.width(TitleBarStyle.WEIGHT).height(WEIGHT).justifyContent(FlexAlign.End) //内容右对齐}.width(WEIGHT).padding({ left: TitleBarStyle.BAR_MARGIN_HORIZONTAL,right: TitleBarStyle.BAR_MARGIN_HORIZONTAL }).margin({ top: TitleBarStyle.BAR_MARGIN_TOP }).height(TitleBarStyle.BAR_HEIGHT).justifyContent(FlexAlign.SpaceAround)  // 占满剩余空间}
}

  下面我们来分析一下这些代码,首先我们导入一些需要用到的样式和App上下文,因为点击返回键需要退出App,然后就是通过@Component装饰的struct表示TitleComponent结构体具有组件化能力,能够成为一个独立的组件。

  然后我们使用到了@Link 修饰isRefreshData,作为刷新数据的标识,但是在标题组件中并没有对此变量进行初始化,需要父组件在创建标题组件时对isRefreshData进行赋值,在DevEco Studio中如果你对一个修饰符或者一个API不了解,你可以将鼠标放在上面,例如将鼠标放在@Link上面,会出现一个弹窗。

在这里插入图片描述

我们点击Show in API Reference,编辑器右侧就会出现API的说明。

在这里插入图片描述

  这个功能还是很Nice的,好了,我们接着来看,isRefreshData变量在点击刷新图标时会进行更改,通过@Link装饰的变量可以和父组件的@State变量建立双向数据绑定,就会将对应该的值传递到父组件,父组件会更新UI,更新UI的时候根据状态切换渲染的数据源。同时定义了一个title,其实我们可以简单的来看,你就把isRefreshData,title当成标题组件的两个参数,父组件要使用子组件,则必须要传两个值进来。自定义组件必须定义build()方法,在其中进行UI描述。

  接下来就是一个Row表示横向布局,Row里面放了两个Row,第一个左对齐,装载返回图标和标题,第二个Row放刷新图标,标题组件就介绍完了,下面我们可以将它装载的父组件中使用了,修改Index.ets中的代码,如下所示:

import { TITLE, WEIGHT } from '../constants/Constants';
import { TitleComponent } from '../view/TitleComponent';@Entry
@Component
struct Index {// 是否切换RankList的数据@State isSwitchDataSource: boolean = truebuild() {Column() {TitleComponent({ isRefreshData: $isSwitchDataSource, title: TITLE })}.backgroundColor($r('app.color.background')).height(WEIGHT).width(WEIGHT)}
}

  这里我们就是在Index父组件中进行使用标题组件,通过 $ 操作符来创建引用,使子组件中isRefreshData和父组件中的isSwitchDataSource建立双向数据绑定,当isRefreshData值变化时,父组件Index中的isSwitchDataSource值也会随着改变,修改代码之后保存一下,然后可以看到预览页面发生了变化

在这里插入图片描述

⑥ 列表头组件

下面我们来写列表头组件,在view包下新建一个ListHeaderComponent.ets文件,里面的代码如下所示:

/*** 列表头自定义组件*/
import { FontSize, ListHeaderStyle } from '../constants/Constants'
@Component
export struct ListHeaderComponent {paddingValue: Padding | Length = 0widthValue: Length = 0build() {Row() {Text($r('app.string.page_number')).fontSize(FontSize.SMALL).width(ListHeaderStyle.LAYOUT_WEIGHT_LEFT).fontWeight(ListHeaderStyle.FONT_WEIGHT).fontColor($r('app.color.font_description'))Text($r('app.string.page_type')).fontSize(FontSize.SMALL).width(ListHeaderStyle.LAYOUT_WEIGHT_CENTER).fontWeight(ListHeaderStyle.FONT_WEIGHT).fontColor($r('app.color.font_description'))Text($r('app.string.page_vote')).fontSize(FontSize.SMALL).width(ListHeaderStyle.LAYOUT_WEIGHT_RIGHT).fontWeight(ListHeaderStyle.FONT_WEIGHT).fontColor($r('app.color.font_description'))}.width(this.widthValue).padding(this.paddingValue)}
}

  这里的代码就相对来说简单很多了,就是三个文字描述,就没有什么好说的,下面我们直接在Index.ets中使用,

import { Style, TITLE, WEIGHT } from '../constants/Constants';
import { ListHeaderComponent } from '../view/ListHeaderComponent';
import { TitleComponent } from '../view/TitleComponent';@Entry
@Component
struct Index {// 是否切换RankList的数据@State isSwitchDataSource: boolean = truebuild() {Column() {//标题栏TitleComponent({ isRefreshData: $isSwitchDataSource, title: TITLE })//列表头ListHeaderComponent({paddingValue: {left: Style.RANK_PADDING,right: Style.RANK_PADDING},widthValue: Style.CONTENT_WIDTH}).margin({top: Style.HEADER_MARGIN_TOP,bottom: Style.HEADER_MARGIN_BOTTOM})}.backgroundColor($r('app.color.background')).height(WEIGHT).width(WEIGHT)}
}

然后保存一下再看预览效果:

在这里插入图片描述

⑦ 列表Item组件

最后我们来看列表item组件,在view包下新建一个ListItemComponent.ets文件,代码如下所示:

import { FontSize, FontWeight, ItemStyle, WEIGHT } from '../constants/Constants';
/*** 列表Item组件*/
@Component
export struct ListItemComponent {index: number;name: Resource;vote: string;// 是否切换数据源isSwitchDataSource: boolean = false;// 是否改变文字选中文字颜色@State isChange: boolean = false;build() {Row() {//排名Column() {if (this.isRenderCircleText()) {//渲染if (this.index !== undefined) {this.CircleText(this.index);}} else {//不渲染Text(this.index?.toString()).lineHeight(ItemStyle.TEXT_LAYOUT_SIZE).textAlign(TextAlign.Center).width(ItemStyle.TEXT_LAYOUT_SIZE).fontWeight(FontWeight.BOLD).fontSize(FontSize.SMALL)}}.width(ItemStyle.LAYOUT_WEIGHT_LEFT).alignItems(HorizontalAlign.Start)//种类Text(this.name).width(ItemStyle.LAYOUT_WEIGHT_CENTER).fontWeight(FontWeight.BOLDER).fontSize(FontSize.MIDDLE).fontColor(this.isChange ? ItemStyle.COLOR_BLUE : ItemStyle.COLOR_BLACK) //根据选中状态修改文字颜色//得票数Text(this.vote).width(ItemStyle.LAYOUT_WEIGHT_RIGHT).fontWeight(FontWeight.BOLD).fontSize(FontSize.SMALL).fontColor(this.isChange ? ItemStyle.COLOR_BLUE : ItemStyle.COLOR_BLACK) //根据选中状态修改文字颜色}.height(ItemStyle.BAR_HEIGHT).width(WEIGHT).onClick(() => { //item 点击事件this.isChange = !this.isChange;})}/*** 圆形背景文字* @param index*/@Builder CircleText(index: number) {Row() {Text(index.toString()).fontWeight(FontWeight.BOLD).fontSize(FontSize.SMALL).fontColor(Color.White);}.justifyContent(FlexAlign.Center).borderRadius(ItemStyle.CIRCLE_TEXT_BORDER_RADIUS).size({ width: ItemStyle.CIRCLE_TEXT_SIZE,height: ItemStyle.CIRCLE_TEXT_SIZE }).backgroundColor($r('app.color.circle_text_background'))}/*** 是否渲染圆圈文本* @returns*/isRenderCircleText(): boolean {// 列表中第三个元素的渲染圆圈文本return this.index === 1 || this.index === 2 || this.index === 3;}
}

  这个列表Item组件里面的代码比较多,我们来分析一下,首先导入的样式就没有什么好说的,然后我们看ListItemComponent组件里面定义的5个参数,前三个是Item显示的内容,而isChange是用来控制item中种类和得票数点击效果的,然后看到build()方法里面,首先是横向布局,然后处理第一个数据,排名,因为我们希望前3个数据标注一下,所以在ListItemComponent组件中写了一个isRenderCircleText()函数,用于判断是否需要进行样式渲染,这里你会看到这里index判断的是1、2和3,但是下标是从0开始的,因此在传index进来的时候,index就是+1的,你不会看到那个排行榜从0开始,然后就是写了一个CircleText()函数,通过这个函数传递index进去创建一个圆形背景,白色文字的样式UI。再往下走就是种类、得票数的渲染,在设置fontColor(this.isChange ? ItemStyle.COLOR_BLUE : ItemStyle.COLOR_BLACK)中对isChange 进行判断从而设置不同的文字颜色,最后就是当前item的点击事件,在点击事件中,更改isChange的值,因为是@State装饰的,所以会触发UI更新,从而修改文字颜色,那么相信列表Item组件你都了解了,下面我们回到Index父组件。

⑧ 组件生命周期

  在父组件使用子组件之前我们再来了解一些关于组建的知识点,通过@Entry装饰的自定义组件用作页面的默认入口组件,加载页面是,将首先创建并呈现@Entry装饰的自定义组件,比如当前的Index,一个页面有且仅能有一个@Entry,这一点很重要,只有被@Entry修饰的组件或者其子组件才会在页面上显示,为什么要说这么多呢?

  这是因为@Entry@Component所修饰的组件的生命周期有所不同。

  通过@Component所修饰组件,生命周期如下图所示:

在这里插入图片描述
  这是自定义组件创建到销毁的过程,在这个过程中系统提供了生命周期回调函数:aboutToAppear()aboutToDisappear(),用于通知开发者该自定义组件所处的阶段,aboutToAppear()在创建自定义组件实例后到执行起build()函数之前执行,你可以在aboutToAppear()函数中对UI需要展示的数据进行初始化或者申请定时器资源等操作,这样在后续build()函数中可以使用这些数据和资源来进行UI展示。可以在aboutToDisappear()函数中释放不再使用的资源,避免资源泄露。

  还需要注意一点,由于这些回调函数是私有的,系统会在特定的时间下自动调用,是无法手动调用这些回调函数的。

  通过@Entry所修饰的页面入口组件,生命周期如下图所示:

在这里插入图片描述

  可以看到相对于自定义组件,页面入口组件多了onPageShow()onBackPress()onPageHide()三个生命周期函数,当用户从手机桌面打开应用,应用进入前台时页面显示,触发onPageShow()函数,当用户点击home键回到桌面时,应用进入后台时,页面消失,触发onPageHide()函数,而当通过系统方式执行返回操作时,触发onBackPress()函数。这里提到了生命周期,是因为下面我们需要用到生命周期。

⑨ 渲染列表数据

我们回到Index.ets,然后修改一些代码,修改后如下所示:

import promptAction from '@ohos.promptAction';
import { APP_EXIT_INTERVAL, Style, TIME, TITLE, WEIGHT } from '../constants/Constants';
import { RankData } from '../model/RankData';
import { RankViewModel } from '../model/RankViewModel';
import { ListHeaderComponent } from '../view/ListHeaderComponent';
import { ListItemComponent } from '../view/ListItemComponent';
import { TitleComponent } from '../view/TitleComponent';let rankModel: RankViewModel = new RankViewModel()@Entry
@Component
struct Index {@State dataSource1: RankData[] = []@State dataSource2: RankData[] = []// 是否切换RankList的数据@State isSwitchDataSource: boolean = true// 记录点击系统导航返回按钮的时间private clickBackTimeRecord: number = 0;/*** 是否显示Toast* @returns*/isShowToast(): boolean {return new Date().getTime() - this.clickBackTimeRecord > APP_EXIT_INTERVAL}/*** 页面显示回调 - 生命周期*/aboutToAppear() {this.dataSource1 = rankModel.loadRankDataSource1()this.dataSource2 = rankModel.loadRankDataSource2()}/*** 页面返回回调* @returns*/onBackPress() {if (this.isShowToast()) {promptAction.showToast({message: $r('app.string.prompt_text'), duration: TIME})this.clickBackTimeRecord = new Date().getTime();return false}return false}build() {Column() {//标题栏TitleComponent({ isRefreshData: $isSwitchDataSource, title: TITLE })//列表头ListHeaderComponent({paddingValue: {left: Style.RANK_PADDING,right: Style.RANK_PADDING},widthValue: Style.CONTENT_WIDTH}).margin({top: Style.HEADER_MARGIN_TOP,bottom: Style.HEADER_MARGIN_BOTTOM})//列表this.RankList(Style.CONTENT_WIDTH)}.backgroundColor($r('app.color.background')).height(WEIGHT).width(WEIGHT)}/*** 配置列表* @param widthValue*/@Builder RankList(widthValue: Length) {Column() {List() {ForEach(this.isSwitchDataSource ? this.dataSource1 : this.dataSource2,(item: RankData, index?: number) => {ListItem() {// 加载ItemListItemComponent({ index: (Number(index) + 1), name: item.name, vote: item.vote })}}, (item: RankData) => JSON.stringify(item))}.width(WEIGHT).height(Style.LIST_HEIGHT).divider({ strokeWidth: Style.STROKE_WIDTH})}.padding({left: Style.RANK_PADDING,right: Style.RANK_PADDING}).borderRadius(Style.BORDER_RADIUS).width(widthValue).alignItems(HorizontalAlign.Center).backgroundColor(Color.White)}
}

  下面我们进行解析,首先是初始化一个rankModel,这里我们前面写好的一个类,用于提供数据源,然后在Index中,创建两个数组,在回调函数aboutToAppear()中进行初始化,然后在onBackPress()回调函数中,处理是否需要显示退出应用时的Toast,return false表示系统处理返回事件,return true表示用户自己处理。接下来最重要的就是我们在Index中增加了RankList()函数,函数中就是通过List()组件装载ListItem(),ForEach遍历当前的数据源,再通过调用ListItemComponent()组件,构建每一个列表Item,注意这里index + 1,所以0,1,2就变成了1,2,3,列表就写好了。最后在build()函数中调用RankList()函数,即可完成整个页面功能。下面我们运行一下,看看效果。

在这里插入图片描述

⑩ 单选

  在上面的处理中我们是通过改变Item的状态来达到选中之后的文字颜色改变,当选了其他的Item之后,之前的Item并没有什么变化,那么如果我想做单选的效果呢?

  从UI上来看,单选我们首先要记录一个选中位置,然后在点击Item的时候更新选中位置,修改文字颜色,同时要更新整个列表,更新列表的时候自然也会更新Item,那么这里就需要使用到@Link来装饰选中位置,下面我们修改一下列表Item组件中的代码:

在这里插入图片描述

  首先增加一个属性,然后根据值匹配当前Item的Index来设置文字颜色,并在点击Item的时候对选中位置重新赋值。

在这里插入图片描述

然后回到Index,这里我们增加一个selectedIndex,

在这里插入图片描述
再构建Item时,将这个值传进去

在这里插入图片描述
  这样就实现了单选功能,我就不贴动图了,因为没有真机,这个动图制作起来太麻烦了,你保存一下,在预览效果中也可以测试出来。

三、源码

如果对你有所帮助的话,不妨 StarFork,山高水长,后会有期~

源码地址:MyApplication

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

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

相关文章

Java BIO模型分析(提供单线程和多线程服务端代码示例)

目录 一、BIO特点介绍二、BIO代码实现2.1、客户端代码准备2.2、服务端单线程处理2.2.1、服务端代码2.2.2、阻塞代码分析2.2.3、存在问题 2.3、服务端多线程处理2.3.1、服务端代码2.3.2、存在问题 一、BIO特点介绍 BIO(blocking I/O):同步阻塞IO,在每个I…

【UnityUGUI】复合控件详解,你还记得多少

👨‍💻个人主页:元宇宙-秩沅 👨‍💻 hallo 欢迎 点赞👍 收藏⭐ 留言📝 加关注✅! 👨‍💻 本文由 秩沅 原创 👨‍💻 收录于专栏:UI_…

linux总结

cat -n filename 查看文件,-n用来给每一行标行号,可以省略 cat /var/log/mysqld.log | grep password 我们可以通过上述指令,查询日志文件内容中包含password的行信息。 more 作用: 以分页的形式显示文件内容 语法: more fileName 操作说明: 回车键 …

企架布道:中电金信应邀出席2023佛山敏捷之旅暨DevOps Meetup

近日,2023佛山敏捷之旅暨DevOps Meetup活动顺利举行,本次活动以助力大湾区金融和互联网企业敏捷DevOps实施和效能提升为主题,共设立 2个会场,16个话题分享,200余位金融、互联网企业相关从业人员齐聚一堂,共…

代码随想录Day15 二叉树 LeetCodeT513 找树左下角的值 T112路径总和 T106 从中序和后序遍历构造二叉树

以上思路来自于:代码随想录 (programmercarl.com) LeetCode T513 找树左下角的值 题目思路: 本题思路:这题我们使用递归法和迭代法解决问题 注意:左下角的值不一定就是一直向左遍历的叶子结点的值,首先可以确定是最后一行的第一个叶子结点的值,也就是最大深度的叶子结点的值 定…

如何打造一个网络框架模块对接服务器

一、了解网络框架的基本原理 在开始打造网络框架模块之前,首先需要了解网络框架的基本原理。网络框架是一个软件模块,用于处理网络通信的各种细节,包括数据传输、协议解析、错误处理等。常见的网络框架有HTTP、TCP/IP、WebSocket等。 对啦&…

【error】root - Exception during pool initialization

报错提示:root - Exception during pool initialization. 错误原因: 配置数据库出错 我的错误配置: spring.datasource.urljdbc:mysql://localhost:3306/springboot?serverTimezoneGMT spring.datasource.nameroot spring.datasource.pass…

Flink---11、状态管理(按键分区状态(值状态、列表状态、Map状态、归约状态、聚合状态)算子状态(列表状态、广播状态))

星光下的赶路人star的个人主页 这世上唯一扛得住岁月摧残的就是才华 文章目录 1、状态管理1.1 Flink中的状态1.1.1 概述1.1.2 状态的分类 1.2 按键分区状态(Keyed State)1.2.1 值状态(ValueState)1.2.2 列表状态(ListS…

基于NLopt的C语言非线性优化案例

以官方给的例程,重新梳理,以供理解NLopt的使用。 问题被定义为: min ⁡ x ∈ R 2 x 2 s u b j e c t t o x 2 ≥ 0 , x 2 ≥ ( a 1 x 1 b 1 ) 3 , a n d x 2 ≥ ( a 2 x 1 b 2 ) 3 f o r p a r a m e t e r s a 1 2 , b 1 0 , a 2 − 1…

JavaScript使用类-模态窗口

**上节课我们为这个项目获取了一些DOM元素,现在我们可以继续;**这个模态窗口有一个hidden类,这个类上文我们讲了,他的display为none;如果我们去除这个hidden的话,就可以让这个模态窗口展现出来。如下 cons…

【Debian系统】:安装debian系统之后,很多命令找不到,需要添加sudo之后才能使用,以下解决方法

项目场景: 问题描述 解决方案: 1.临时解决方案 2.永久解决方案 1.首先打开编辑: 2.打开之后最后一行添加代码: 3.最后运行一遍 .bashrc 4.已经可以了,可以试试reboot,重启一下机子 一点一滴才能成长 …

windows的最佳选项卡式窗口管理器软件TidyTabs

下载: https://jmj.cc/s/z1t3kt?pucodeS1wc https://download.csdn.net/download/mo3408/88420433 TidyTabs是一款Windows应用程序,它可以将多个打开的窗口整理成一个选项卡式的界面,使得用户可以更加方便地切换和管理不同的窗口。 Tidy…

京东商品数据:8月京东环境电器行业数据分析

8月份,环境电器大盘市场整体下滑。鲸参谋数据显示,8月京东平台环境电器的大盘将近570万,环比下滑约29%,同比下滑约10%;销售额为25亿,环比下滑约23%,同比下滑约8%。 *数据源于鲸参谋-行业趋势分析…

网工内推 | 技术支持工程师,厂商公司,HCIA即可,有带薪年假

01 华为终端有限公司 招聘岗位:初级技术支持 职责描述: 1、通过远程方式处理华为用户在产品使用过程中各种售后问题; 2、收集并整理消费者声音,提供服务持续优化建议; 3、对服务中发现的热点、难点问题及其他有可能造…

iMazing2023免费版苹果iPhone手机备份应用软件

iMazing是一款功能强大的苹果手机备份软件,它可通过备份功能将通讯录备份到电脑上,并在电脑端iMazing“通讯录”功能中随时查看和导出联系人信息。它自带Wi-Fi自动备份功能,能够保证通讯录备份数据是一直在动态更新的,防止手机中新…

收银系统商品定价设计思考

一、背景 因为门店系统里商品总共也就几万款,一直以来都是根据条码由总部统一定价销售,现在有加盟店,各门店也有进行各自促销活动的需求,这就需要放开门店自主定价权,所以近段时间系统在商品定价上做了扩展。 二、商…

【SCS-CN】SCS-CN模型中CN值的确定

目录 一、说明二、SWAT三、HEC-HMS四、CN值转换公式五、确定CN25.1 ArcSWAT 2009用户指南5.2 SWAT plus Document5.3 National Engineering Handbook5.4 HEC-HMS水文建模系统原理方法应用5.5 Technical Release 55 (TR-55) 六、确定水文土壤单元(HSG)6.1…

移动应用-Android开发基础\核心知识点

Android开发基础 知识点 1 介绍了解2 系统体系架构3 四大应用组件4 移动操作系统优缺点5 开发工具6 配置工具7 下载相关资源8JDK下载安装流程9配置好SDK和JDK环境10 第一个Hello word11 AS开发前常用设置12模拟器使用运行13 真机调试14 AndroidUI基础布局15 加载展示XML布局16…

JS VUE 用 canvas 给图片加水印

最近写需求,遇到要给图片加水印的需求。 刚开始想的方案是给图片上覆盖一层水印照片,但是这样的话用户直接下载图片水印也会消失。 后来查资料发现用 canvas 就可以给图片加水印,下面是处理过程。 首先我们要确认图片的格式,我们通…

华为云云耀云服务器L实例评测|使用redis事务和lua脚本

文章目录 云服务器的类型云服务优点redis一,关系型数据库(sqlserver,mysql,oracle)的事务隔离机制说明:redis事务机制 lualua脚本好处:一,怎么在redis中使用lua脚本二,脚…