HarmonyOS 鸿蒙Next的ArkUI中List组件的详细使用与状态管理
HarmonyOS 鸿蒙Next的ArkUI中List组件的详细使用与状态管理
<markdown _ngcontent-wqm-c147="" class="markdownPreContainer">
目录
- List 组件介绍
- 基本用法
- 组件间的值传递
- MVVM 结构应用
- 实现步骤
- 状态管理中的
[@Provide](/user/Provide)
和[@Consume](/user/Consume)
装饰器
- 完整任务列表页实现
- 简单示例
- List 列表滚动事件监听
- 数据模型定义
- ViewModel 定义
- 完整任务列表页代码
- 写在最后
- 其他资源
List 组件介绍
基本用法
List 容器组件是一种常用的布局容器,它主要用于展示一系列数据项,这些数据项可以是同类型或不同类型的数据集合。List 组件能够自动管理其内部子元素的复用和滚动行为,非常适合构建列表界面,例如商品列表、联系人列表、消息列表等,可以轻松高效地显示结构化、可滚动的信息。
通过在 List 组件中按垂直或水平方向线性排列子组件 ListItemGroup
或 ListItem
,为列表中的行或列提供单个视图。或使用 ForEach
迭代一组行或列,或混合任意数量的单个视图和 ForEach
结构,构建一个列表。注意 List 的子组件必须是 ListItemGroup
或 ListItem
,ListItem
和 ListItemGroup
也必须配合 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
层,而数据模型和业务逻辑则位于 Model
和 ViewModel
层。
实现步骤
- 创建 List 组件:在
pages
目录下创建TaskListPage.ets
文件。 - 定义数据模型:在
model
目录下定义数据模型。 - 实现 ViewModel:在
viewmodel
目录下实现业务逻辑。 - 使用
[@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
文件。任务列表页由上部分的标题、返回按钮以及正中间的任务列表组成。使用 Navigation
和 List
组件构成元素,使用 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">() =></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>) =></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
也会相应更新。
写在最后
如果你觉得这篇内容对你还蛮有帮助,我想邀请你帮我三个小忙:
- 点赞和转发:点赞和评论是博主创作的动力。
- 关注博主:关注博主可以期待后续文章,不定期分享原创知识。
- 获取更多资料:想要获取更多完整 HarmonyOS 最新 VIP 学习资料,请关注猫哥公众号【猫青年】,回复“鸿蒙”获取。
其他资源
</markdown>更多关于HarmonyOS 鸿蒙Next的ArkUI中List组件的详细使用与状态管理的实战系列教程也可以访问 https://www.itying.com/category-93-b0.html
更多关于HarmonyOS 鸿蒙Next的ArkUI中List组件的详细使用与状态管理的实战系列教程也可以访问 https://www.itying.com/category-93-b0.html
在HarmonyOS鸿蒙Next的ArkUI中,List组件用于展示垂直或水平排列的子组件列表。List组件通过list
数据源进行渲染,数据源可以是数组或其他可迭代对象。
详细使用方法如下:
-
基础使用:
- 定义数据源,例如
let items = ['Item1', 'Item2', 'Item3'];
- 使用
<List>
标签,并通过for
循环绑定数据源,例如<List for="{{items}}" item="{{item}}">{{item}}</List>
。
- 定义数据源,例如
-
状态管理:
- List组件的状态管理通常通过ArkUI的数据绑定机制实现。
- 定义一个状态变量,例如
@State itemList: Array<string> = ['Item1', 'Item2', 'Item3'];
- 使用
@Effect
或其他事件处理函数来更新状态,例如@Effect handleAdd() { this.itemList.push('New Item'); }
。 - 更新数据源后,List组件会自动重新渲染。
-
高级用法:
- List组件支持多种布局方式,可通过设置
layout
属性进行配置。 - 支持滚动事件监听,可通过
onScroll
等事件处理函数实现。 - 可通过
itemComponent
属性自定义每个列表项的组件。
- List组件支持多种布局方式,可通过设置
请注意,以上内容仅为List组件的基本使用示例。实际应用中,可能需要根据具体需求进行更复杂的配置和处理。如果问题依旧没法解决请联系官网客服,官网地址是:https://www.itying.com/category-93-b0.html。