HarmonyOS鸿蒙应用开辟( 四、重磅组件List列表组件利用详解) ...

十念  论坛元老 | 2024-7-28 02:41:53 | 显示全部楼层 | 阅读模式
打印 上一主题 下一主题

主题 1026|帖子 1026|积分 3078

马上注册,结交更多好友,享用更多功能,让你轻松玩转社区。

您需要 登录 才可以下载或查看,没有账号?立即注册

x
List列表组件,是一个非常常用的组件。可以说在一个应用中,它的身影无处不在。它包含一系列相同宽度的列表项,适合连续、多行呈现同类数据,如商品列表、图片列表和和文本列表等。ArkUI 框架采用 List 容器组件创建列表(类似 Android 的 RecycleView、Compose 的 LazyColumn)。
  之所以称List列表组件比较重磅,一方面是因为它很常用,另一方面是因为一旦学会了它,其他组件也天然不在话下。有了它共同数据的加持,可以让你的应用有模有样。类比下 Android 中的 RecycleView,它的地位足够紧张吧。
网上先容 ArkUI 的List组件知识都太琐屑,且不敷深入和系统。这里以一个使命列表页的完备实现为例,详细先容下List组件的利用、组件间的值的通报,及项目代码的MVVM结构划分。分享给有必要的小伙伴,喜欢的可以点下关注并收藏。
List组件先容

List容器组件是一种常用的结构容器,它主要用于展示一系列数据项,这些数据项可以是同范例或差别范例的数据聚集。List组件能够自动管理其内部子元素的复用和滚动行为,非常适合构建列表界面,比方商品列表,接洽人列表、消息列表等,可以轻松高效地显示结构化、可滚动的信息。
通过在 List 组件中按垂直或者程度方向线性排列子组件 ListItemGroup 或 ListItem,为列表中的行或列提供单个视图。或利用ForEach 迭代一组行或列,或混合任意数量的单个视图和 ForEach 结构,构建一个列表。留意List的子组件必须是 ListItemGroup 或 ListItem,ListItem 和 ListItemGroup 也必须共同 List 来利用。
如下图所示的两个页面:
第一个页面中利用了Swiper容器组件实现的轮播图,紧接着往下是利用的Grid网格容器组件,最下方是Tabs组件。右侧图片则是List容器组件,共同Scroll组件以及LazyForEach组件实现一个商品列表的页面,并且拥有下拉刷新、懒加载和到底提示的结果。



一个简单的示例
List下用ForEach循环数据,列表子项用ListItem组件,组件中再设置结构。divider属性设置列表分割线,listDirection属性设置列表是横向排列照旧纵向排列(默认纵向)。

  1. import router from '@ohos.router'
  2. @Entry
  3. @Component
  4. struct Index {
  5.   private arr: number[] = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
  6.   build() {
  7.     Row() {
  8.       Column() {
  9.         List({ space: 10 }) {
  10.           ForEach(this.arr, (item: number) => {
  11.             ListItem() {
  12.               Text(`${item}`)
  13.                 .width('100%')
  14.                 .height(100)
  15.                 .fontSize(20)
  16.                 .fontColor(Color.White)
  17.                 .textAlign(TextAlign.Center)
  18.                 .borderRadius(10)
  19.                 .backgroundColor(0x007DFF)
  20.                 .onClick(() => {
  21.                   if (item === 0) {
  22.                     //跳转到GridPage页面
  23.                     router.push({
  24.                       url: 'pages/GridPage'
  25.                     })
  26.                   }
  27.                 })
  28.             }
  29.           }, item => item)
  30.         }
  31.         //strokeWidth: 分割线的线宽。
  32.         //color: 分割线的颜色。
  33.         //startMargin:分割线距离列表侧边起始端的距离。
  34.         //endMargin: 分割线距离列表侧边结束端的距离。
  35.         // .divider({
  36.         //   strokeWidth: 1,
  37.         //   color: Color.Gray,
  38.         //   startMargin: 10,
  39.         //   endMargin: 10
  40.         // })
  41.         //Vertical(默认值):子组件ListItem在List容器组件中呈纵向排列。
  42.         //子组件ListItem在List容器组件中呈横向排列。
  43.         .listDirection(Axis.Vertical)
  44.       }
  45.       .padding(12)
  46.       .height('100%')
  47.       .backgroundColor(0xF1F3F5)
  48.     }
  49.     .height('100%')
  50.   }
  51. }
复制代码
通过 ForEach 提供了组件的循环渲染本领。我们可以利用 ForEach,在其中以嵌套 ListItem 的形式来取代多个平铺的、内容相似的 ListItem,从而减少重复代码。
上述示例显示的有些单调,但是够基础。我们可以丰富一下 ListItem,比方给它加上图标:
  1. @Component
  2. struct ListTest {
  3.   build() {
  4.     List() {
  5.       ListItem() {
  6.         Row() {
  7.           Image($r('app.media.icon')).width(20).height(20).margin(10)
  8.           Text("Kotlin").fontSize(10)
  9.         }
  10.       }
  11.       ListItem() {
  12.         Row() {
  13.           Image($r('app.media.icon')).width(20).height(20).margin(10)
  14.           Text("TypeScript").fontSize(10)
  15.         }
  16.       }
  17.       ListItem() {
  18.         Row() {
  19.           Image($r('app.media.icon')).width(20).height(20).margin(10)
  20.           Text("ArkTS").fontSize(10)
  21.         }
  22.       }
  23.     }
  24.     .backgroundColor('#FFF1F3F5')
  25.     .alignListItem(ListItemAlign.Start)
  26.   }
  27. }
复制代码

List列表滚动变乱监听
List组件提供了一系列变乱方法用来监听列表的滚动,您可以根据必要,监听这些变乱来做一些操作:


  • onScroll:列表滑动时触发,返回值scrollOffset为滑动偏移量,scrollState为当前滑动状态。
  • onScrollIndex:列表滑动时触发,返回值分别为滑动起始位置索引值与滑动竣事位置索引值。
  • onReachStart:列表到达起始位置时触发。
  • onReachEnd:列表到底末端位置时触发。
  • onScrollStop:列表滑动制止时触发。
利用示例代码如下:
  1. List({ space: 10 }) {
  2.   ForEach(this.arr, (item) => {
  3.     ListItem() {
  4.       Text(`${item}`)
  5.         ...
  6.     }
  7.   }, item => item)
  8. }
  9. .onScrollIndex((firstIndex: number, lastIndex: number) => {
  10.   console.info('first' + firstIndex)
  11.   console.info('last' + lastIndex)
  12. })
  13. .onScroll((scrollOffset: number, scrollState: ScrollState) => {
  14.   console.info('scrollOffset' + scrollOffset)
  15.   console.info('scrollState' + scrollState)
  16. })
  17. .onReachStart(() => {
  18.   console.info('onReachStart')
  19. })
  20. .onReachEnd(() => {
  21.   console.info('onReachEnd')
  22. })
  23. .onScrollStop(() => {
  24.   console.info('onScrollStop')
  25. })
复制代码
List利用详解

以下内容举例实现下图中的使命列表展示,详细先容下List组件的利用。


使命列表页

在pages目录下,创建TaskListPage.ets文件。使命列表页由上部分的标题、返回按钮以及正中间的使命列表组成。利用Navigation以及List组件构成元素,利用ForEach遍历生成详细列表。大抵内容如下:
  1. // TaskListPage.ets
  2. Navigation() {
  3.   Column() {
  4.     // 页面中间的列表
  5.     TaskList()
  6.   }
  7.   .width(Const.THOUSANDTH_1000)
  8.   .justifyContent(FlexAlign.Center)
  9. }
  10. .size({ width: Const.THOUSANDTH_1000, height: Const.THOUSANDTH_1000 })
  11. .title(Const.ADD_TASK_TITLE)
  12. .titleMode(NavigationTitleMode.Mini)
复制代码
TaskListComponent组件

TaskListPage中,利用了自定义的TaskList()列表组件。由于TaskList是一个自定义的视图组件,所以放在view目录里最合适。在TaskList列表的右侧有一个判断是否开启的文字标识,点击某个列表必要跳转到对应的使命编辑页里。列表组件实现如下:
  1. // TaskListComponent.ets
  2. List({ space: Const.LIST_ITEM_SPACE }) {
  3.   ForEach(this.taskList, (item: ITaskItem) => {
  4.     ListItem() {
  5.       Row() {
  6.         Row() {
  7.           Image(item?.icon)
  8.           Text(item?.taskName)
  9.             ...
  10.         }
  11.         .width(Const.THOUSANDTH_500)
  12.         Blank()
  13.           .layoutWeight(1)
  14.         // 状态显示
  15.         if (item?.isOpen) {
  16.           Text($r('app.string.already_open'))
  17.         }
  18.         Image($r('app.media.ic_right_grey'))
  19.           .width(Const.DEFAULT_8)
  20.           .height(Const.DEFAULT_16)
  21.       }
  22.       ...
  23.     }
  24.     ...
  25.     // 路由跳转到任务编辑页
  26.     .onClick(() => {
  27.       router.pushUrl({
  28.         url: 'pages/TaskEditPage',
  29.         params: {
  30.           params: formatParams(item)
  31.         }
  32.       })
  33.     })
  34.     ...
  35.   })
  36. }
复制代码
TaskListComponent列表组件完备实现如下:
  1. // view--/task/--TaskListComponent.ets
  2. import router from '@ohos.router';
  3. import { CommonConstants as Const } from '../../common/constants/CommonConstants';
  4. import { formatParams } from '../../viewmodel/TaskViewModel';
  5. import { ITaskItem } from '../../model/TaskInitList';
  6. @Component
  7. export default struct TaskList {
  8.   @Consume taskList: ITaskItem[];
  9.   build() {
  10.     List({ space: Const.LIST_ITEM_SPACE }) {
  11.       ForEach(this.taskList, (item: ITaskItem) => {
  12.         ListItem() {
  13.           Row() {
  14.             Row() {
  15.               Image(item?.icon)
  16.                 .width(Const.DEFAULT_24)
  17.                 .height(Const.DEFAULT_24)
  18.                 .margin({ right: Const.DEFAULT_8 })
  19.               Text(item?.taskName).fontSize(Const.DEFAULT_20).fontColor($r('app.color.titleColor'))
  20.             }.width(Const.THOUSANDTH_500)
  21.             Blank()
  22.               .layoutWeight(1)
  23.             if (item?.isOpen) {
  24.               Text($r('app.string.already_open'))
  25.                 .fontSize(Const.DEFAULT_16)
  26.                 .flexGrow(1)
  27.                 .align(Alignment.End)
  28.                 .margin({ right: Const.DEFAULT_8 })
  29.                 .fontColor($r('app.color.titleColor'))
  30.             }
  31.             Image($r('app.media.ic_right_grey'))
  32.             .width(Const.DEFAULT_8)
  33.             .height(Const.DEFAULT_16)
  34.           }
  35.           .width(Const.THOUSANDTH_1000)
  36.           .justifyContent(FlexAlign.SpaceBetween)
  37.           .padding({ left: Const.DEFAULT_12, right: Const.DEFAULT_12 })
  38.         }
  39.         .height(Const.THOUSANDTH_80)
  40.         .borderRadius(Const.DEFAULT_12)
  41.         .onClick(() => {
  42.           router.pushUrl({
  43.             url: 'pages/TaskEditPage',
  44.             params: {
  45.               params: formatParams(item)
  46.             }
  47.           })
  48.         })
  49.         .backgroundColor($r('app.color.white'))
  50.       }, (item: ITaskItem) => JSON.stringify(item))
  51.     }
  52.     .height(Const.THOUSANDTH_1000)
  53.     .width(Const.THOUSANDTH_940)
  54.   }
  55. }
复制代码
model数据模子定义

model目录下的数据模子定义:
  1. // TaskInitList.ets
  2. import AchievementMapInfo from '../common/bean/AchievementMapInfo';
  3. import TaskInfo from '../common/bean/TaskInfo';
  4. import { CommonConstants as Const } from '../common/constants/CommonConstants';
  5. export interface ITaskItem {
  6.   taskID: number;
  7.   taskName: Resource;
  8.   isOpen: boolean;
  9.   unit: string;
  10.   icon: Resource;
  11.   dialogBg: Resource;
  12.   targetValue: string;
  13.   isAlarm: boolean;
  14.   startTime: string;
  15.   endTime: string;
  16.   frequency: string;
  17.   isInit: boolean;
  18.   step: number;
  19. }
复制代码
ViewModel定义

 viewmodel目录下的TaskViewModel定义:
  1. import { CommonConstants as Const } from '../common/constants/CommonConstants';
  2. import Logger from '../common/utils/Logger';
  3. import reminder from '../service/ReminderAgent';
  4. import TaskInfoApi from '../common/database/tables/TaskInfoApi';
  5. import { padTo2Digits } from '../common/utils/Utils';
  6. import TaskInfo, { oneWeek } from '../common/bean/TaskInfo';
  7. import { TaskMapById, RemindContentMap, ITaskItem } from '../model/TaskInitList';
  8. import PublishReminderInfo from '../common/bean/PublishReminderInfo';
  9. const publishReminder = reminder.publishReminder;
  10. const cancelReminder = reminder.cancelReminder;
  11. const hasNotificationId = reminder.hasNotificationId;
  12. export const taskOriginData: ITaskItem[] = TaskMapById;
  13. /**
  14. * @description Get all task status
  15. * @return object[] Database query results
  16. */
  17. export const getAllTask = () => {
  18.   return new Promise<TaskInfo[]>((resolve) => {
  19.     TaskInfoApi.query(Const.GLOBAL_KEY, true, (res: TaskInfo[]) => {
  20.       if (res?.length === 0) {
  21.         Logger.warn('queryTaskList', 'has no data!!');
  22.         resolve(res ?? []);
  23.       }
  24.       resolve(res);
  25.     })
  26.   });
  27. }
  28. /**
  29. * @description format data as json string
  30. * @param params = {}
  31. */
  32. export const formatParams = (params: ITaskItem) => {
  33.   return JSON.stringify(params);
  34. }
  35. //......
复制代码
完备的使命列表页

最终的使命列表页面实现如下:
  1. // TaskListPage.ets
  2. import { ITaskItem } from '../model/TaskInitList';
  3. import TaskList from '../view/task/TaskListComponent';
  4. import { CommonConstants as Const } from '../common/constants/CommonConstants';
  5. import { getAllTask, taskIndexDataInit, taskOriginData } from '../viewmodel/TaskViewModel';
  6. import TaskInfo from '../common/bean/TaskInfo';
  7. @Entry
  8. @Component
  9. @Preview
  10. struct TaskIndex {
  11.   @Provide taskList: ITaskItem[] = taskOriginData;
  12.   onPageShow() {
  13.     getAllTask().then((res: TaskInfo[]) => {
  14.       let deepCopyDataStr = JSON.stringify(this.taskList);
  15.       let deepCopyData: ITaskItem[] = JSON.parse(deepCopyDataStr);
  16.       this.taskList = taskIndexDataInit(deepCopyData, res);
  17.     })
  18.   }
  19.   build() {
  20.     Row() {
  21.       Navigation() {
  22.         Column() {
  23.           TaskList()
  24.         }
  25.         .width(Const.THOUSANDTH_1000)
  26.         .justifyContent(FlexAlign.Center)
  27.       }
  28.       .size({ width: Const.THOUSANDTH_1000, height: Const.THOUSANDTH_1000 })
  29.       .title(Const.ADD_TASK_TITLE)
  30.       .titleMode(NavigationTitleMode.Mini)
  31.     }
  32.     .backgroundColor($r('app.color.primaryBgColor'))
  33.     .height(Const.THOUSANDTH_1000)
  34.   }
  35. }
复制代码
List组件值的通报

特殊留意,上述 TaskListPage页面中调用了自定义的TaskList()列表组件,如何完成父与子组件的传值的呢?即如何把数据从mode中获取出来通报给TaskList组件?并未见有参数通报啊。这就涉及arkUI的状态管理相关的装饰器了。
在上述示例中,虽然没见到TaskList()列表组件中有参数通报,但是发现有 @Provide taskList 这一装饰器修饰的变量,且把mode中的数据赋值给了它。
在HarmonyOS ArkUI开辟框架中,@Consume和@Provide是状态管理相关的装饰器,用于组件间的数据通报与同步。它们主要用于跨组件的状态共享,尤其是在多层级的父子组件之间。
@Provide:该装饰器用来声明一个状态变量,并将其提供给后代组件利用。当一个状态变量被@Provide装饰后,这个变量会自动对所有子组件可见,无需通过props或变乱手动向下级组件通报。后代组件可以直接通过@Consume装饰器来获取并利用这个变量。
@Consume:此装饰器用于从先人组件中消费(获取)由@Provide提供的状态变量。当在一个组件内利用@Consume装饰器时,它会绑定到其先人组件中对应@Provide修饰的同名或别名状态变量上,实现双向数据同步。
  1. // 在父组件中提供状态
  2. class ParentComponent {
  3.   @Provide("theta")
  4.   theta_axis = 0;
  5.   // 其他逻辑...
  6. }
  7. // 在子组件中消费状态
  8. @Entry
  9. @Component
  10. struct ChildComponent {
  11.   @Consume("theta") // 使用相同的别名"theta"
  12. consumeTheta: number;
  13.   render() {
  14.     return <Text>{`Theta Axis Value: ${this.consumeTheta}`}</Text>;
  15.   }
  16. }
复制代码
ParentComponent提供了名为“theta”的状态变量,而ChildComponent通过@Consume装饰器消费了这个变量,并在渲染函数中显示它的值。当theta_axis发生变革时,ChildComponent中的consumeTheta也会相应更新。
MVVM结构先容

在HarmonyOS ArkUI开辟框架中,MVVM(Model-View-ViewModel)是一种用于构建用户界面的开辟模式。它将应用步伐分为三个主要部分:
Model

Model 代表应用步伐的数据模子。在 arkUI 中,Model 大概是数据实体、数据访问对象(DAO)或者远程服务的数据源。Model 负责数据的获取、存储和处理。
View

View(视图层) 是用户界面的表示。在 arkUI 中,View 表示应用步伐的界面元素,如 UI 控件,结构等。View 负责将数据展示给用户,并接收用户的交互操作。在ArkUI中,视图层由一系列UI组件组成,如Text、Image、List等,并通过声明式语法进行结构和样式设计。
ViewModel

ViewModel(视图模子层),作为Model和View之间的桥梁,包含了视图所必要的数据以及视图相关的业务逻辑。ViewModel对Model中的数据进行处理并提供给View利用,同时响应View的交互变乱,实行相应的业务操作。在 arkUI 中,ViewModel 包含业务逻辑和状态管理,负责处理 View 层和 Model 层之间的通信。ViewModel 通常通过数据绑定的方式将数据从 Model 层通报给 View 层,并接收来自 View 层的用户输入。
在ArkUI中,开辟者可以通过数据绑定机制来实现ViewModel和View之间的数据同步,即当ViewModel中的数据发生变革时,关联的视图会自动更新,反之亦然。
通过采用 MVVM 结构,arkUI 提供了一种分离关注点、使界面逻辑更易于测试和维护的方式,同时也使得界面的设计和逻辑更机动和可扩展。借助数据绑定,开辟人员可以更方便地管理界面的数据和状态,提高开辟效率。通过这样的分离,ArkUI的MVVM结构提升了代码可读性、复用性和可测试性,同时也简化了用户界面与后端业务逻辑之间的耦合度。
一个典型的mvvm结构

举例,一个完备项目典型的mvvm结构。一个好的结构很紧张,它提升了代码可读性、复用性和可测试性,同时也简化了用户界面与后端业务逻辑之间的耦合度。
目录结构示意:
  1. ├──entry/src/main/ets                 // 代码区
  2. │  ├──agency                          // 2x4 ArkTS卡片目录
  3. │  │  └──pages
  4. │  │     └──AgencyCard.ets            // 2x4 ArkTS卡片任务
  5. │  ├──common
  6. │  │  ├──constants
  7. │  │  │  └──CommonConstants.ets       // 公共常量
  8. │  │  ├──database
  9. │  │  │  ├──rdb                       // 数据库封装类
  10. │  │  │  │  ├──RdbHelper.ets
  11. │  │  │  │  ├──RdbUtils.ets
  12. │  │  │  │  └──TableHelper.ets
  13. │  │  │  └──tables                    // 数据表
  14. │  │  │     ├──DayInfoApi.ets
  15. │  │  │     ├──GlobalInfoApi.ets
  16. │  │  └──utils
  17. │  │     ├──BroadCast.ets             // 通知
  18. │  │     ├──FormUtils.ets             // 卡片操作工具类
  19. │  │     ├──GlobalContext.ets         
  20. │  │     ├──Logger.ets                // 日志类
  21. │  │     └──Utils.ets                 // 工具类
  22. │  ├──entryability
  23. │  │  └──EntryAbility.ets             // 程序入口类
  24. │  ├──entryformability
  25. │  │  └──EntryFormAbility.ets         // 卡片创建,更新,删除操作类
  26. │  ├──model                           // model
  27. │  │  ├──AchieveModel.ets
  28. │  │  ├──DatabaseModel.ets            // 数据库model
  29. │  │  ├──Mine.ets
  30. │  │  ├──NavItemModel.ets             // 菜单栏model
  31. │  │  ├──TaskInitList.ets
  32. │  ├──pages
  33. │  │  ├──MainPage.ets                 // 应用主页面
  34. │  │  ├──MinePage.ets                 // 我的页面
  35. │  ├──progress                        // 2x2 ArkTS卡片目录
  36. │  │  └──pages
  37. │  │     └──ProgressCard.ets          // 2x2 ArkTS卡片任务进度
  38. │  ├──service
  39. │  │  └──ReminderAgent.ets            // 后台提醒代理操作类
  40. │  ├──view
  41. │  │  ├──dialog                       // 弹窗组件
  42. │  │  │  ├──AchievementDialog.ets     // 成就弹窗
  43. │  │  │  ├──CustomDialogView.ets      // 自定义弹窗
  44. │  │  ├──home                         // 主页面相关组件
  45. │  │  │  ├──AddBtnComponent.ets       // 添加任务按钮组件
  46. │  │  │  ├──HomeTopComponent.ets      // 首页顶部组件
  47. │  │  │  └──WeekCalendarComponent.ets // 日历组件
  48. │  │  ├──HealthTextComponent.ets      // 自定义text组件
  49. │  │  ├──HomeComponent.ets            // 首页页面
  50. │  │  ├──ListInfo.ets                 // 用户信息列表
  51. │  │  └──UserBaseInfo.ets             // 用户基本信息
  52. │  └──viewmodel                       // viewmodel
  53. │     ├──AchievementInfo.ets          // 成就信息接口
  54. │     ├──WeekCalendarInfo.ets         // 日期信息接口
  55. │     └──WeekCalendarMethodInfo.ets   // 日期操作接口
  56. └──entry/src/main/resources           // 资源文件目录
复制代码
写在最后



  • 假如你以为这篇内容对你还蛮有帮助,我想邀请你帮我三个小忙:
  • 点赞,转发,有你们的 『点赞和品评』,才是我创造的动力。
  • 关注博主,同时可以等待后续文章ing
回复

使用道具 举报

0 个回复

倒序浏览

快速回复

您需要登录后才可以回帖 登录 or 立即注册

本版积分规则

十念

论坛元老
这个人很懒什么都没写!
快速回复 返回顶部 返回列表