HarmonyOS 鸿蒙Next的ArkUI中List组件的详细使用与状态管理

发布于 1周前 作者 itying888 来自 鸿蒙OS

HarmonyOS 鸿蒙Next的ArkUI中List组件的详细使用与状态管理
<markdown _ngcontent-wqm-c147="" class="markdownPreContainer">

目录

  • List 组件介绍
    • 基本用法
    • 组件间的值传递
    • MVVM 结构应用
    • 实现步骤
    • 状态管理中的 [@Provide](/user/Provide)[@Consume](/user/Consume) 装饰器
  • 完整任务列表页实现
    • 简单示例
    • List 列表滚动事件监听
    • 数据模型定义
    • ViewModel 定义
    • 完整任务列表页代码
  • 写在最后
  • 其他资源

List 组件介绍

基本用法

List 容器组件是一种常用的布局容器,它主要用于展示一系列数据项,这些数据项可以是同类型或不同类型的数据集合。List 组件能够自动管理其内部子元素的复用和滚动行为,非常适合构建列表界面,例如商品列表、联系人列表、消息列表等,可以轻松高效地显示结构化、可滚动的信息。

通过在 List 组件中按垂直或水平方向线性排列子组件 ListItemGroupListItem,为列表中的行或列提供单个视图。或使用 ForEach 迭代一组行或列,或混合任意数量的单个视图和 ForEach 结构,构建一个列表。注意 List 的子组件必须是 ListItemGroupListItemListItemListItemGroup 也必须配合 List 来使用。

组件间的值传递

在 HarmonyOS ArkUI 框架中,[@Provide](/user/Provide)[@Consume](/user/Consume) 装饰器用于组件间的数据传递与同步。它们主要用于跨组件的状态共享,尤其是在多层级的父子组件之间。

  • [@Provide](/user/Provide):该装饰器用来声明一个状态变量,并将其提供给后代组件使用。当一个状态变量被 [@Provide](/user/Provide) 装饰后,这个变量会自动对所有子组件可见,无需通过 props 或事件手动向下级组件传递。后代组件可以直接通过 [@Consume](/user/Consume) 装饰器来获取并使用这个变量。
  • [@Consume](/user/Consume):此装饰器用于从祖先组件中消费(获取)由 [@Provide](/user/Provide) 提供的状态变量。当在一个组件内使用 [@Consume](/user/Consume) 装饰器时,它会绑定到其祖先组件中对应 [@Provide](/user/Provide) 修饰的同名或别名状态变量上,实现双向数据同步。
MVVM 结构应用

在实际开发中,我们通常会使用 MVVM(Model-View-ViewModel)架构来组织代码,使得代码结构更加清晰和易于维护。在这种架构下,List 组件通常位于 View 层,而数据模型和业务逻辑则位于 ModelViewModel 层。

实现步骤
  1. 创建 List 组件:在 pages 目录下创建 TaskListPage.ets 文件。
  2. 定义数据模型:在 model 目录下定义数据模型。
  3. 实现 ViewModel:在 viewmodel 目录下实现业务逻辑。
  4. 使用 [@Provide](/user/Provide)[@Consume](/user/Consume) 装饰器进行状态管理

简单示例

一个简单的示例:

深色代码主题
复制
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(<span class="hljs-subst">${item}</span>) .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); } .listDirection(Axis.Vertical); } .padding(12) .height(‘100%’) .backgroundColor(0xF1F3F5); } .height(‘100%’); } }

通过 ForEach 提供了组件的循环渲染能力。我们可以使用 ForEach,在其中以嵌套 ListItem 的形式来代替多个平铺的、内容相似的 ListItem,从而减少重复代码。

上述示例显示的有些单调,但是够基础。我们可以丰富一下 ListItem,例如给它加上图标:

深色代码主题
复制
[@Component](/user/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 组件提供了一系列事件方法用来监听列表的滚动,您可以根据需要,监听这些事件来做一些操作:

  • onScroll:列表滑动时触发,返回值 scrollOffset 为滑动偏移量,scrollState 为当前滑动状态。
  • onScrollIndex:列表滑动时触发,返回值分别为滑动起始位置索引值与滑动结束位置索引值。
  • onReachStart:列表到达起始位置时触发。
  • onReachEnd:列表到底末尾位置时触发。
  • onScrollStop:列表滑动停止时触发。

使用示例代码如下:

深色代码主题
复制
List({ space: 10 }) {
  ForEach(this.arr, (item) => {
    ListItem() {
      Text(`${item}`)
        ...
    }
  }, item => item);
}
.onScrollIndex((firstIndex: number, lastIndex: number) => {
  console.info('firstIndex: ' + firstIndex);
  console.info('lastIndex: ' + 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');
});

完整任务列表页实现

任务列表页

pages 目录下,创建 TaskListPage.ets 文件。任务列表页由上部分的标题、返回按钮以及正中间的任务列表组成。使用 NavigationList 组件构成元素,使用 ForEach 遍历生成具体列表。大致内容如下:

深色代码主题
复制
// 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';

@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); } }

TaskListComponent 组件

TaskListPage 中使用了自定义的 TaskList 组件。由于 TaskList 是一个自定义的视图组件,所以放在 view 目录里最合适。在 TaskList 列表的右侧有一个判断是否开启的文字标识,点击某个列表需要跳转到对应的任务编辑页里。列表组件实现如下:

深色代码主题
复制
// 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);

        <span class="hljs-title class_">Blank</span>()
          .<span class="hljs-title function_">layoutWeight</span>(<span class="hljs-number">1</span>);

        <span class="hljs-keyword">if</span> (item?.<span class="hljs-property">isOpen</span>) {
          <span class="hljs-title class_">Text</span>($r(<span class="hljs-string">'app.string.already_open'</span>))
            .<span class="hljs-title function_">fontSize</span>(<span class="hljs-title class_">Const</span>.<span class="hljs-property">DEFAULT_16</span>)
            .<span class="hljs-title function_">flexGrow</span>(<span class="hljs-number">1</span>)
            .<span class="hljs-title function_">align</span>(<span class="hljs-title class_">Alignment</span>.<span class="hljs-property">End</span>)
            .<span class="hljs-title function_">margin</span>({ <span class="hljs-attr">right</span>: <span class="hljs-title class_">Const</span>.<span class="hljs-property">DEFAULT_8</span> })
            .<span class="hljs-title function_">fontColor</span>($r(<span class="hljs-string">'app.color.titleColor'</span>));
        }

        <span class="hljs-title class_">Image</span>($r(<span class="hljs-string">'app.media.ic_right_grey'</span>))
          .<span class="hljs-title function_">width</span>(<span class="hljs-title class_">Const</span>.<span class="hljs-property">DEFAULT_8</span>)
          .<span class="hljs-title function_">height</span>(<span class="hljs-title class_">Const</span>.<span class="hljs-property">DEFAULT_16</span>);
      }
      .<span class="hljs-title function_">width</span>(<span class="hljs-title class_">Const</span>.<span class="hljs-property">THOUSANDTH_1000</span>)
      .<span class="hljs-title function_">justifyContent</span>(<span class="hljs-title class_">FlexAlign</span>.<span class="hljs-property">SpaceBetween</span>)
      .<span class="hljs-title function_">padding</span>({ <span class="hljs-attr">left</span>: <span class="hljs-title class_">Const</span>.<span class="hljs-property">DEFAULT_12</span>, <span class="hljs-attr">right</span>: <span class="hljs-title class_">Const</span>.<span class="hljs-property">DEFAULT_12</span> });
    }
    .<span class="hljs-title function_">height</span>(<span class="hljs-title class_">Const</span>.<span class="hljs-property">THOUSANDTH_80</span>)
    .<span class="hljs-title function_">borderRadius</span>(<span class="hljs-title class_">Const</span>.<span class="hljs-property">DEFAULT_12</span>)
    .<span class="hljs-title function_">onClick</span>(<span class="hljs-function">() =&gt;</span> {
      router.<span class="hljs-title function_">pushUrl</span>({
        <span class="hljs-attr">url</span>: <span class="hljs-string">'pages/TaskEditPage'</span>,
        <span class="hljs-attr">params</span>: {
          <span class="hljs-attr">params</span>: <span class="hljs-title function_">formatParams</span>(item)
        }
      });
    })
    .<span class="hljs-title function_">backgroundColor</span>($r(<span class="hljs-string">'app.color.white'</span>));
  }, <span class="hljs-function">(<span class="hljs-params">item: ITaskItem</span>) =&gt;</span> <span class="hljs-title class_">JSON</span>.<span class="hljs-title function_">stringify</span>(item));
}
.<span class="hljs-title function_">height</span>(<span class="hljs-title class_">Const</span>.<span class="hljs-property">THOUSANDTH_1000</span>)
.<span class="hljs-title function_">width</span>(<span class="hljs-title class_">Const</span>.<span class="hljs-property">THOUSANDTH_940</span>);

} }

数据模型定义

model 目录下的数据模型定义如下:

深色代码主题
复制
// model/TaskInitList.ets
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 定义

viewmodel 目录下的 TaskViewModel 定义如下:

深色代码主题
复制
// viewmodel/TaskViewModel.ets
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 获取所有任务状态
  • @return object[] 数据库查询结果 */ export const getAllTask = () => { return new Promise<TaskInfo[]>((resolve) => { TaskInfoApi.query(Const.GLOBAL_KEY, true, (res: TaskInfo[]) => { if (res?.length === 0) { Logger.warn(‘queryTaskList’, ‘无数据!!’); resolve(res ?? []); } resolve(res); }); }); };

/**

  • @description 格式化数据为 JSON 字符串
  • @param params 需要格式化的数据 */ export const formatParams = (params: ITaskItem) => { return JSON.stringify(params); };

// 其他代码实现…

完整任务列表页代码

最终的任务列表页面实现如下:

深色代码主题
复制
// pages/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';

@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); } }

List 组件值的传递

特别注意,上述 TaskListPage 页面中调用了自定义的 TaskList 组件,如何完成父与子组件的传值呢?即如何把数据从 Model 中获取出来传递给 TaskList 组件?并未见有参数传递啊。这就涉及 ArkUI 的状态管理相关的装饰器了。

在上述示例中,虽然没见到 TaskList 组件中有参数传递,但是发现有 [@Provide](/user/Provide) taskList 这一装饰器修饰的变量,且把 Model 中的数据赋值给了它。

深色代码主题
复制
// 在父组件中提供状态
class ParentComponent {
  [@Provide](/user/Provide)("theta")
  theta_axis = 0;

// 其他逻辑… }

// 在子组件中消费状态 @Entry @Component struct ChildComponent { @Consume(“theta”) // 使用相同的别名"theta" consumeTheta: number;

build() { Text(Theta Axis Value: <span class="hljs-subst">${<span class="hljs-variable language_">this</span>.consumeTheta}</span>) .fontSize(20) .fontColor(Color.Black) .margin({ top: 20 }); } }

ParentComponent 提供了名为 theta 的状态变量,而 ChildComponent 通过 [@Consume](/user/Consume) 装饰器消费了这个变量,并在 build 方法中显示它的值。当 theta_axis 发生变化时,ChildComponent 中的 consumeTheta 也会相应更新。

写在最后

如果你觉得这篇内容对你还蛮有帮助,我想邀请你帮我三个小忙:

  1. 点赞和转发:点赞和评论是博主创作的动力。
  2. 关注博主:关注博主可以期待后续文章,不定期分享原创知识。
  3. 获取更多资料:想要获取更多完整 HarmonyOS 最新 VIP 学习资料,请关注猫哥公众号【猫青年】,回复“鸿蒙”获取。

其他资源

</markdown>

更多关于HarmonyOS 鸿蒙Next的ArkUI中List组件的详细使用与状态管理的实战系列教程也可以访问 https://www.itying.com/category-93-b0.html

1 回复

更多关于HarmonyOS 鸿蒙Next的ArkUI中List组件的详细使用与状态管理的实战系列教程也可以访问 https://www.itying.com/category-93-b0.html


在HarmonyOS鸿蒙Next的ArkUI中,List组件用于展示垂直或水平排列的子组件列表。List组件通过list数据源进行渲染,数据源可以是数组或其他可迭代对象。

详细使用方法如下:

  1. 基础使用

    • 定义数据源,例如let items = ['Item1', 'Item2', 'Item3'];
    • 使用<List>标签,并通过for循环绑定数据源,例如<List for="{{items}}" item="{{item}}">{{item}}</List>
  2. 状态管理

    • List组件的状态管理通常通过ArkUI的数据绑定机制实现。
    • 定义一个状态变量,例如@State itemList: Array<string> = ['Item1', 'Item2', 'Item3'];
    • 使用@Effect或其他事件处理函数来更新状态,例如@Effect handleAdd() { this.itemList.push('New Item'); }
    • 更新数据源后,List组件会自动重新渲染。
  3. 高级用法

    • List组件支持多种布局方式,可通过设置layout属性进行配置。
    • 支持滚动事件监听,可通过onScroll等事件处理函数实现。
    • 可通过itemComponent属性自定义每个列表项的组件。

请注意,以上内容仅为List组件的基本使用示例。实际应用中,可能需要根据具体需求进行更复杂的配置和处理。如果问题依旧没法解决请联系官网客服,官网地址是:https://www.itying.com/category-93-b0.html。

回到顶部