赞
踩
List列表组件,是一个非常常用的组件。可以说在一个应用中,它的身影无处不在。它包含一系列相同宽度的列表项,适合连续、多行呈现同类数据,如商品列表、图片列表和和文本列表等。ArkUI 框架采用 List 容器组件创建列表(类似 Android 的 RecycleView、Compose 的 LazyColumn)。
之所以称List列表组件比较重磅,一方面是因为它很常用,另一方面是因为一旦学会了它,其他组件也自然不在话下。有了它配合数据的加持,可以让你的应用有模有样。类比下 Android 中的 RecycleView,它的地位足够重要吧。
网上介绍 ArkUI 的List组件知识都太零碎,且不够深入和系统。这里以一个任务列表页的完整实现为例,详细介绍下List组件的使用、组件间的值的传递,及项目代码的MVVM结构划分。分享给有需要的小伙伴,喜欢的可以点下关注并收藏。
List容器组件是一种常用的布局容器,它主要用于展示一系列数据项,这些数据项可以是同类型或不同类型的数据集合。List组件能够自动管理其内部子元素的复用和滚动行为,非常适合构建列表界面,例如商品列表,联系人列表、消息列表等,可以轻松高效地显示结构化、可滚动的信息。
通过在 List 组件中按垂直或者水平方向线性排列子组件 ListItemGroup 或 ListItem,为列表中的行或列提供单个视图。或使用ForEach 迭代一组行或列,或混合任意数量的单个视图和 ForEach 结构,构建一个列表。注意List的子组件必须是 ListItemGroup 或 ListItem,ListItem 和 ListItemGroup 也必须配合 List 来使用。
如下图所示的两个页面:
第一个页面中使用了Swiper容器组件实现的轮播图,紧接着往下是使用的Grid网格容器组件,最下方是Tabs组件。右侧图片则是List容器组件,配合Scroll组件以及LazyForEach组件实现一个商品列表的页面,并且拥有下拉刷新、懒加载和到底提示的效果。
一个简单的示例
List下用ForEach循环数据,列表子项用ListItem组件,组件中再设置布局。divider属性设置列表分割线,listDirection属性设置列表是横向排列还是纵向排列(默认纵向)。
- import router from '@ohos.router'
-
- @Entry
- @Component
- struct Index {
- private arr: number[] = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
-
- build() {
- Row() {
- Column() {
- List({ space: 10 }) {
- ForEach(this.arr, (item: number) => {
- ListItem() {
- Text(`${item}`)
- .width('100%')
- .height(100)
- .fontSize(20)
- .fontColor(Color.White)
- .textAlign(TextAlign.Center)
- .borderRadius(10)
- .backgroundColor(0x007DFF)
- .onClick(() => {
- if (item === 0) {
- //跳转到GridPage页面
- router.push({
- url: 'pages/GridPage'
- })
- }
- })
- }
- }, item => item)
- }
- //strokeWidth: 分割线的线宽。
- //color: 分割线的颜色。
- //startMargin:分割线距离列表侧边起始端的距离。
- //endMargin: 分割线距离列表侧边结束端的距离。
- // .divider({
- // strokeWidth: 1,
- // color: Color.Gray,
- // startMargin: 10,
- // endMargin: 10
- // })
- //Vertical(默认值):子组件ListItem在List容器组件中呈纵向排列。
- //子组件ListItem在List容器组件中呈横向排列。
- .listDirection(Axis.Vertical)
- }
- .padding(12)
- .height('100%')
- .backgroundColor(0xF1F3F5)
- }
- .height('100%')
- }
- }
通过 ForEach 提供了组件的循环渲染能力。我们可以使用 ForEach,在其中以嵌套 ListItem 的形式来代替多个平铺的、内容相似的 ListItem,从而减少重复代码。
上述示例显示的有些单调,但是够基础。我们可以丰富一下 ListItem,例如给它加上图标:
- @Component
- struct ListTest {
-
- build() {
- List() {
- ListItem() {
- Row() {
- Image($r('app.media.icon')).width(20).height(20).margin(10)
- Text("Kotlin").fontSize(10)
- }
- }
- ListItem() {
- Row() {
- Image($r('app.media.icon')).width(20).height(20).margin(10)
- Text("TypeScript").fontSize(10)
- }
- }
- ListItem() {
- Row() {
- Image($r('app.media.icon')).width(20).height(20).margin(10)
- Text("ArkTS").fontSize(10)
- }
- }
- }
- .backgroundColor('#FFF1F3F5')
- .alignListItem(ListItemAlign.Start)
- }
- }
List列表滚动事件监听
List组件提供了一系列事件方法用来监听列表的滚动,您可以根据需要,监听这些事件来做一些操作:
使用示例代码如下:
- List({ space: 10 }) {
- ForEach(this.arr, (item) => {
- ListItem() {
- Text(`${item}`)
- ...
- }
- }, item => item)
- }
- .onScrollIndex((firstIndex: number, lastIndex: number) => {
- console.info('first' + firstIndex)
- console.info('last' + lastIndex)
- })
- .onScroll((scrollOffset: number, scrollState: ScrollState) => {
- console.info('scrollOffset' + scrollOffset)
- console.info('scrollState' + scrollState)
- })
- .onReachStart(() => {
- console.info('onReachStart')
- })
- .onReachEnd(() => {
- console.info('onReachEnd')
- })
- .onScrollStop(() => {
- console.info('onScrollStop')
- })
以下内容举例实现下图中的任务列表展示,详细介绍下List组件的使用。
在pages目录下,创建TaskListPage.ets文件。任务列表页由上部分的标题、返回按钮以及正中间的任务列表组成。使用Navigation以及List组件构成元素,使用ForEach遍历生成具体列表。大致内容如下:
- // TaskListPage.ets
- Navigation() {
- Column() {
- // 页面中间的列表
- TaskList()
- }
- .width(Const.THOUSANDTH_1000)
- .justifyContent(FlexAlign.Center)
- }
- .size({ width: Const.THOUSANDTH_1000, height: Const.THOUSANDTH_1000 })
- .title(Const.ADD_TASK_TITLE)
- .titleMode(NavigationTitleMode.Mini)
TaskListPage中,使用了自定义的TaskList()列表组件。由于TaskList是一个自定义的视图组件,所以放在view目录里最合适。在TaskList列表的右侧有一个判断是否开启的文字标识,点击某个列表需要跳转到对应的任务编辑页里。列表组件实现如下:
- // TaskListComponent.ets
- List({ space: Const.LIST_ITEM_SPACE }) {
- ForEach(this.taskList, (item: ITaskItem) => {
- ListItem() {
- Row() {
- Row() {
- Image(item?.icon)
- Text(item?.taskName)
- ...
- }
- .width(Const.THOUSANDTH_500)
-
- Blank()
- .layoutWeight(1)
-
- // 状态显示
- if (item?.isOpen) {
- Text($r('app.string.already_open'))
- }
- Image($r('app.media.ic_right_grey'))
- .width(Const.DEFAULT_8)
- .height(Const.DEFAULT_16)
-
- }
- ...
- }
- ...
-
- // 路由跳转到任务编辑页
- .onClick(() => {
- router.pushUrl({
- url: 'pages/TaskEditPage',
- params: {
- params: formatParams(item)
- }
- })
- })
- ...
- })
- }
TaskListComponent列表组件完整实现如下:
- // view--/task/--TaskListComponent.ets
- import router from '@ohos.router';
- import { CommonConstants as Const } from '../../common/constants/CommonConstants';
- import { formatParams } from '../../viewmodel/TaskViewModel';
- import { ITaskItem } from '../../model/TaskInitList';
-
- @Component
- export default struct TaskList {
- @Consume taskList: ITaskItem[];
-
- build() {
- List({ space: Const.LIST_ITEM_SPACE }) {
- ForEach(this.taskList, (item: ITaskItem) => {
- ListItem() {
- Row() {
- Row() {
- Image(item?.icon)
- .width(Const.DEFAULT_24)
- .height(Const.DEFAULT_24)
- .margin({ right: Const.DEFAULT_8 })
- Text(item?.taskName).fontSize(Const.DEFAULT_20).fontColor($r('app.color.titleColor'))
- }.width(Const.THOUSANDTH_500)
-
- Blank()
- .layoutWeight(1)
- if (item?.isOpen) {
- Text($r('app.string.already_open'))
- .fontSize(Const.DEFAULT_16)
- .flexGrow(1)
- .align(Alignment.End)
- .margin({ right: Const.DEFAULT_8 })
- .fontColor($r('app.color.titleColor'))
- }
- Image($r('app.media.ic_right_grey'))
- .width(Const.DEFAULT_8)
- .height(Const.DEFAULT_16)
- }
- .width(Const.THOUSANDTH_1000)
- .justifyContent(FlexAlign.SpaceBetween)
- .padding({ left: Const.DEFAULT_12, right: Const.DEFAULT_12 })
- }
- .height(Const.THOUSANDTH_80)
- .borderRadius(Const.DEFAULT_12)
- .onClick(() => {
- router.pushUrl({
- url: 'pages/TaskEditPage',
- params: {
- params: formatParams(item)
- }
- })
- })
- .backgroundColor($r('app.color.white'))
- }, (item: ITaskItem) => JSON.stringify(item))
- }
- .height(Const.THOUSANDTH_1000)
- .width(Const.THOUSANDTH_940)
- }
- }
model目录下的数据模型定义:
- // TaskInitList.ets
- import AchievementMapInfo from '../common/bean/AchievementMapInfo';
-
- import TaskInfo from '../common/bean/TaskInfo';
- import { CommonConstants as Const } from '../common/constants/CommonConstants';
-
- export interface ITaskItem {
- taskID: number;
- taskName: Resource;
- isOpen: boolean;
- unit: string;
- icon: Resource;
- dialogBg: Resource;
- targetValue: string;
- isAlarm: boolean;
- startTime: string;
- endTime: string;
- frequency: string;
- isInit: boolean;
- step: number;
- }
viewmodel目录下的TaskViewModel定义:
- import { CommonConstants as Const } from '../common/constants/CommonConstants';
- import Logger from '../common/utils/Logger';
- import reminder from '../service/ReminderAgent';
- import TaskInfoApi from '../common/database/tables/TaskInfoApi';
- import { padTo2Digits } from '../common/utils/Utils';
- import TaskInfo, { oneWeek } from '../common/bean/TaskInfo';
- import { TaskMapById, RemindContentMap, ITaskItem } from '../model/TaskInitList';
- import PublishReminderInfo from '../common/bean/PublishReminderInfo';
-
- const publishReminder = reminder.publishReminder;
- const cancelReminder = reminder.cancelReminder;
- const hasNotificationId = reminder.hasNotificationId;
- export const taskOriginData: ITaskItem[] = TaskMapById;
-
- /**
- * @description Get all task status
- * @return object[] Database query results
- */
- export const getAllTask = () => {
- return new Promise<TaskInfo[]>((resolve) => {
- TaskInfoApi.query(Const.GLOBAL_KEY, true, (res: TaskInfo[]) => {
- if (res?.length === 0) {
- Logger.warn('queryTaskList', 'has no data!!');
- resolve(res ?? []);
- }
- resolve(res);
- })
- });
- }
-
- /**
- * @description format data as json string
- * @param params = {}
- */
- export const formatParams = (params: ITaskItem) => {
- return JSON.stringify(params);
- }
- //......
最终的任务列表页面实现如下:
- // TaskListPage.ets
- import { ITaskItem } from '../model/TaskInitList';
- import TaskList from '../view/task/TaskListComponent';
- import { CommonConstants as Const } from '../common/constants/CommonConstants';
- import { getAllTask, taskIndexDataInit, taskOriginData } from '../viewmodel/TaskViewModel';
- import TaskInfo from '../common/bean/TaskInfo';
-
- @Entry
- @Component
- @Preview
- struct TaskIndex {
- @Provide taskList: ITaskItem[] = taskOriginData;
-
- onPageShow() {
- getAllTask().then((res: TaskInfo[]) => {
- let deepCopyDataStr = JSON.stringify(this.taskList);
- let deepCopyData: ITaskItem[] = JSON.parse(deepCopyDataStr);
- this.taskList = taskIndexDataInit(deepCopyData, res);
- })
- }
-
- build() {
- Row() {
- Navigation() {
- Column() {
- TaskList()
- }
- .width(Const.THOUSANDTH_1000)
- .justifyContent(FlexAlign.Center)
- }
- .size({ width: Const.THOUSANDTH_1000, height: Const.THOUSANDTH_1000 })
- .title(Const.ADD_TASK_TITLE)
- .titleMode(NavigationTitleMode.Mini)
- }
- .backgroundColor($r('app.color.primaryBgColor'))
- .height(Const.THOUSANDTH_1000)
- }
- }
特别注意,上述 TaskListPage页面中调用了自定义的TaskList()列表组件,如何完成父与子组件的传值的呢?即如何把数据从mode中获取出来传递给TaskList组件?并未见有参数传递啊。这就涉及arkUI的状态管理相关的装饰器了。
在上述示例中,虽然没见到TaskList()列表组件中有参数传递,但是发现有 @Provide taskList 这一装饰器修饰的变量,且把mode中的数据赋值给了它。
在HarmonyOS ArkUI开发框架中,@Consume和@Provide是状态管理相关的装饰器,用于组件间的数据传递与同步。它们主要用于跨组件的状态共享,尤其是在多层级的父子组件之间。
@Provide:该装饰器用来声明一个状态变量,并将其提供给后代组件使用。当一个状态变量被@Provide装饰后,这个变量会自动对所有子组件可见,无需通过props或事件手动向下级组件传递。后代组件可以直接通过@Consume装饰器来获取并使用这个变量。
@Consume:此装饰器用于从祖先组件中消费(获取)由@Provide提供的状态变量。当在一个组件内使用@Consume装饰器时,它会绑定到其祖先组件中对应@Provide修饰的同名或别名状态变量上,实现双向数据同步。
- // 在父组件中提供状态
- class ParentComponent {
- @Provide("theta")
- theta_axis = 0;
-
- // 其他逻辑...
- }
-
- // 在子组件中消费状态
- @Entry
- @Component
- struct ChildComponent {
- @Consume("theta") // 使用相同的别名"theta"
- consumeTheta: number;
-
- render() {
- return <Text>{`Theta Axis Value: ${this.consumeTheta}`}</Text>;
- }
- }
ParentComponent提供了名为“theta”的状态变量,而ChildComponent通过@Consume装饰器消费了这个变量,并在渲染函数中显示它的值。当theta_axis发生变化时,ChildComponent中的consumeTheta也会相应更新。
在HarmonyOS ArkUI开发框架中,MVVM(Model-View-ViewModel)是一种用于构建用户界面的开发模式。它将应用程序分为三个主要部分:
Model 代表应用程序的数据模型。在 arkUI 中,Model 可能是数据实体、数据访问对象(DAO)或者远程服务的数据源。Model 负责数据的获取、存储和处理。
View(视图层) 是用户界面的表示。在 arkUI 中,View 表示应用程序的界面元素,如 UI 控件,布局等。View 负责将数据展示给用户,并接收用户的交互操作。在ArkUI中,视图层由一系列UI组件组成,如Text、Image、List等,并通过声明式语法进行布局和样式设计。
ViewModel(视图模型层),作为Model和View之间的桥梁,包含了视图所需要的数据以及视图相关的业务逻辑。ViewModel对Model中的数据进行处理并提供给View使用,同时响应View的交互事件,执行相应的业务操作。在 arkUI 中,ViewModel 包含业务逻辑和状态管理,负责处理 View 层和 Model 层之间的通信。ViewModel 通常通过数据绑定的方式将数据从 Model 层传递给 View 层,并接收来自 View 层的用户输入。
在ArkUI中,开发者可以通过数据绑定机制来实现ViewModel和View之间的数据同步,即当ViewModel中的数据发生变化时,关联的视图会自动更新,反之亦然。
通过采用 MVVM 结构,arkUI 提供了一种分离关注点、使界面逻辑更易于测试和维护的方式,同时也使得界面的设计和逻辑更灵活和可扩展。借助数据绑定,开发人员可以更方便地管理界面的数据和状态,提高开发效率。通过这样的分离,ArkUI的MVVM结构提升了代码可读性、复用性和可测试性,同时也简化了用户界面与后端业务逻辑之间的耦合度。
举例,一个完整项目典型的mvvm结构。一个好的结构很重要,它提升了代码可读性、复用性和可测试性,同时也简化了用户界面与后端业务逻辑之间的耦合度。
目录结构示意:
- ├──entry/src/main/ets // 代码区
- │ ├──agency // 2x4 ArkTS卡片目录
- │ │ └──pages
- │ │ └──AgencyCard.ets // 2x4 ArkTS卡片任务
- │ ├──common
- │ │ ├──constants
- │ │ │ └──CommonConstants.ets // 公共常量
- │ │ ├──database
- │ │ │ ├──rdb // 数据库封装类
- │ │ │ │ ├──RdbHelper.ets
- │ │ │ │ ├──RdbUtils.ets
- │ │ │ │ └──TableHelper.ets
- │ │ │ └──tables // 数据表
- │ │ │ ├──DayInfoApi.ets
- │ │ │ ├──GlobalInfoApi.ets
- │ │ └──utils
- │ │ ├──BroadCast.ets // 通知
- │ │ ├──FormUtils.ets // 卡片操作工具类
- │ │ ├──GlobalContext.ets
- │ │ ├──Logger.ets // 日志类
- │ │ └──Utils.ets // 工具类
- │ ├──entryability
- │ │ └──EntryAbility.ets // 程序入口类
- │ ├──entryformability
- │ │ └──EntryFormAbility.ets // 卡片创建,更新,删除操作类
- │ ├──model // model
- │ │ ├──AchieveModel.ets
- │ │ ├──DatabaseModel.ets // 数据库model
- │ │ ├──Mine.ets
- │ │ ├──NavItemModel.ets // 菜单栏model
- │ │ ├──TaskInitList.ets
- │ ├──pages
- │ │ ├──MainPage.ets // 应用主页面
- │ │ ├──MinePage.ets // 我的页面
- │ ├──progress // 2x2 ArkTS卡片目录
- │ │ └──pages
- │ │ └──ProgressCard.ets // 2x2 ArkTS卡片任务进度
- │ ├──service
- │ │ └──ReminderAgent.ets // 后台提醒代理操作类
- │ ├──view
- │ │ ├──dialog // 弹窗组件
- │ │ │ ├──AchievementDialog.ets // 成就弹窗
- │ │ │ ├──CustomDialogView.ets // 自定义弹窗
- │ │ ├──home // 主页面相关组件
- │ │ │ ├──AddBtnComponent.ets // 添加任务按钮组件
- │ │ │ ├──HomeTopComponent.ets // 首页顶部组件
- │ │ │ └──WeekCalendarComponent.ets // 日历组件
- │ │ ├──HealthTextComponent.ets // 自定义text组件
- │ │ ├──HomeComponent.ets // 首页页面
- │ │ ├──ListInfo.ets // 用户信息列表
- │ │ └──UserBaseInfo.ets // 用户基本信息
- │ └──viewmodel // viewmodel
- │ ├──AchievementInfo.ets // 成就信息接口
- │ ├──WeekCalendarInfo.ets // 日期信息接口
- │ └──WeekCalendarMethodInfo.ets // 日期操作接口
- └──entry/src/main/resources // 资源文件目录
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。