【共创季稿事节】鸿蒙原生 ArkTS 布局之 List 基础案例:联系人列表实战(
鸿蒙原生 ArkTS 布局之 List 基础案例:联系人列表实战(API 24)



一、背景与目标
1.1 什么是 ArkTS
ArkTS 是鸿蒙原生应用开发的声明式 UI 编程语言,基于 TypeScript 语法扩展而来。它采用 声明式 + 状态驱动 的编程范式——开发者描述 UI 应该"长什么样",框架自动计算 UI 的更新路径,无需手动操作 DOM。
ArkTS 的核心特点:
- @Entry / @Component 装饰器:标记页面入口和自定义组件
- @State / @Prop / @Link 装饰器:实现状态驱动的 UI 自动刷新
- 内置组件库:
List、Grid、Column、Row、Stack、Text、Image等 - 链式 API:通过
.属性()方法链式配置组件样式
1.2 本文目标
本文将基于鸿蒙 NEXT(API 24)实现一个经典的联系人列表页面,具有以下特征:
| 功能点 | 技术方案 |
|---|---|
| 页面标题栏 | Text 组件 + 背景色 |
| 联系人列表 | List + ForEach 循环渲染 |
| 圆形头像 | Stack + Circle + Text(纯代码,无图片依赖) |
| 昵称 + 描述 | Column 纵向排列 |
| 列表项分割线 | List.divider() 统一配置 |
| 滚动回弹 | EdgeEffect.Spring |
| 右侧指示箭头 | Text('>') 模拟 |
二、项目创建与配置
2.1 创建 HarmonyOS 项目
打开 DevEco Studio,选择 File → New → Create Project。在模板选择界面选择 Empty Ability(Stage Model),语言选择 ArkTS。
提示:本文基于 API 24(HarmonyOS NEXT 6.2+)编写。实际开发时请确保
build-profile.json5中compatibleSdkVersion配置为"6.2.0(24)"。
项目创建完成后,核心文件结构如下:
entry/src/main/ets/
├── entryability/
│ └── EntryAbility.ets # Ability 生命周期(无需修改)
├── pages/
│ └── Index.ets # 主页面(本文核心)
└── resources/
└── base/
├── element/
├── media/ # 图片资源目录
└── ...
我们只需要修改 pages/Index.ets 一个文件即可完成整个页面。
2.2 项目依赖说明
本示例不引入任何第三方依赖,全部使用 ArkTS 内置组件和 API 24 的系统能力。头像的实现采用了纯代码绘制的圆形色块 + 姓氏首字,无需在 resources/media 中放置任何图片文件,真正做到"开箱即用"。
三、完整代码实现
下面给出 Index.ets 的完整代码。每一段都配有详细的中文注释,建议读者逐段阅读,理解每一行代码的作用。
/**
* 联系人列表 — List 基础案例
* ============================
* 场景:带头像(圆形)、昵称和描述信息的经典联系人列表。
* 核心技术:List + ForEach + 自定义组件 + 分割线
*
* 布局要点:
* 1. List 组件作为整体垂直滚动容器;
* 2. ListItem 内使用 Row + Column 组合实现"头像 | 昵称+描述"布局;
* 3. 头像使用 Circle + Text 组合生成纯色圆形占位头像(无需本地图片资源);
* 4. 每个 ListItem 之间通过 List 的 divider 属性添加分割线;
* 5. 使用 @Builder 封装列表项布局,提升代码复用性。
*/
// ---------- 定义联系人数据类型 ----------
/**
* 联系人数据模型
* name - 联系人昵称
* avatar - 头像背景色(使用纯色圆形 + 姓氏首字,无需图片文件)
* desc - 联系人描述 / 签名信息
*/
interface ContactItem {
name: string;
color: string; // 头像圆形背景色
desc: string;
}
// 预定义一组头像背景色(轮流使用)
const AVATAR_COLORS: string[] = [
'#4A90D9', '#E8833A', '#47B881', '#D85A5A',
'#7B68EE', '#D97CB5', '#5AB0C6', '#F0A030',
];
// ============================================================
// 页面入口组件
// ============================================================
@Entry
@Component
struct Index {
/**
* @State 装饰的 contacts 数组驱动 UI 更新。
* 头像使用姓氏首字 + 纯色圆形背景,无需本地图片资源,
* 开箱即用,预览或真机均可直接展示。
*/
@State contacts: ContactItem[] = [
{ name: '张三', color: AVATAR_COLORS[0], desc: '鸿蒙开发者 · 全栈工程师' },
{ name: '李四', color: AVATAR_COLORS[1], desc: 'UI/UX 设计师 · 爱画画' },
{ name: '王五', color: AVATAR_COLORS[2], desc: '后端架构师 · 马拉松爱好者' },
{ name: '赵六', color: AVATAR_COLORS[3], desc: '测试工程师 · 代码质量守护者' },
{ name: '孙七', color: AVATAR_COLORS[4], desc: '产品经理 · 需求文档书写者' },
{ name: '周八', color: AVATAR_COLORS[5], desc: '运维工程师 · 7×24 在线' },
{ name: '吴九', color: AVATAR_COLORS[6], desc: '数据科学家 · 用数据说话' },
{ name: '郑十', color: AVATAR_COLORS[7], desc: '安全工程师 · 守护每一行代码' },
];
// ============================================================
// build 方法 — 页面 UI 树
// ============================================================
build() {
// ---- 外层列:标题 + 列表 ----
Column() {
// ---- 页面标题栏 ----
Text('通讯录')
.fontSize(24)
.fontWeight(FontWeight.Bold)
.width('100%')
.height(56)
.textAlign(TextAlign.Center)
.backgroundColor('#0078D7')
.fontColor(Color.White)
.padding({ top: 12 })
// =========================================================
// 【核心】List 组件 — 垂直滚动的联系人列表
// 布局要点:
// ① List 本身不设固定高度,由父容器 Column 约束;
// ② 通过 ForEach 循环渲染每一个 ListItem;
// ③ ListItem 内部使用 Row + Column 实现横向图文混排;
// ④ 分割线通过 List.divider() 属性统一配置,简洁统一。
// =========================================================
List({ space: 0 }) {
// ForEach 遍历 contacts 数组,为每个联系人创建一个列表项
ForEach(this.contacts, (item: ContactItem) => {
// ---- 单个列表项 ----
ListItem() {
// 使用 @Builder 构建的列表项内容
this.ContactListItem(item)
}
// -------- 分割线说明 --------
// 分割线已通过 List.divider() 全局配置(见下方),
// 无需在每个 ListItem 后手动添加 Divider 组件。
// 若需每个列表项独立控制分割线样式,可使用
// ListItemSeparator { .strokeWidth(0.5).startMargin(72) }
})
}
// -------- List 全局属性 --------
.width('100%')
.layoutWeight(1) // 填满剩余垂直空间
.divider({ // 统一设置列表项分割线
strokeWidth: 0.5, // 分割线线宽 0.5vp
color: '#e0e0e0', // 浅灰色分割线
startMargin: 72, // 从左 72vp 处开始(对齐头像右侧)
endMargin: 16 // 距右 16vp 结束
})
.edgeEffect(EdgeEffect.Spring) // 列表滚动到边缘时回弹效果
}
.width('100%')
.height('100%')
.backgroundColor('#f5f5f5') // 整体背景色:浅灰
}
// ============================================================
// @Builder 封装列表项布局
// 将每个 ListItem 的内部布局提取为独立构建函数,便于复用。
// 布局结构: [圆形头像] [昵称 + 描述] [> 箭头]
// ============================================================
@Builder
ContactListItem(item: ContactItem) {
// ---- 行容器:头像 | 文本信息 | 箭头 ----
Row() {
// ---- ① 头像(圆形,纯色背景 + 姓氏首字) ----
// 使用 Stack 叠加圆形背景和文字,避免依赖任何图片资源
Stack() {
Circle()
.width(48)
.height(48)
.fill(item.color) // 使用预设颜色填充
Text(item.name.slice(0, 1)) // 取姓氏第一个字
.fontSize(18)
.fontColor(Color.White)
.fontWeight(FontWeight.Bold)
}
.width(48)
.height(48)
.margin({ left: 16, right: 12 })
// ---- ② 昵称 + 描述(纵向排列) ----
Column() {
// 昵称
Text(item.name)
.fontSize(16)
.fontWeight(FontWeight.Medium)
.fontColor('#333333')
.width('100%')
.textAlign(TextAlign.Start)
// 描述
Text(item.desc)
.fontSize(13)
.fontColor('#999999')
.width('100%')
.textAlign(TextAlign.Start)
.margin({ top: 4 }) // 与昵称保持 4vp 间距
}
.alignItems(HorizontalAlign.Start) // Column 内部左对齐
.layoutWeight(1) // 文本区域填满剩余宽度
// ---- ③ 右侧箭头图标(使用文本替代,无需系统资源) ----
Text('>')
.fontSize(16)
.fontColor('#cccccc')
.margin({ right: 16 })
}
.width('100%')
.height(72) // 列表项高度 72vp(符合 HIG 建议)
.alignItems(VerticalAlign.Center) // Row 内垂直居中
.backgroundColor(Color.White) // 列表项白色背景
.padding({ top: 0, bottom: 0 })
}
}
四、布局要点逐层解析
4.1 整体页面结构
Column(全屏)
├── Text "通讯录" ← 标题栏(蓝底白字,高 56vp)
└── List ← 列表容器(layoutWeight:1 填满剩余空间)
├── ForEach
│ ├── ListItem 0 ← 联系人条目
│ ├── ListItem 1
│ └── ...
└── .divider() ← 统一分割线
最外层 Column 将页面划分为上下两个区域:标题栏和列表区域。layoutWeight(1) 让 List 自动填充扣除标题栏之后的所有剩余高度,无需手动计算。
4.2 List 组件的核心属性
List 是鸿蒙中最重要的滚动容器组件之一。本示例用到的关键属性:
| 属性 | 值 | 作用 |
|---|---|---|
{ space: 0 } |
构造参数 | 列表项之间的间距(0 表示紧贴,由分割线提供视觉分隔) |
.width('100%') |
字符串 | 宽度占满父容器 |
.layoutWeight(1) |
数字 | 弹性权重,填满父容器剩余空间 |
.divider({...}) |
对象 | 统一配置列表项分割线 |
.edgeEffect(EdgeEffect.Spring) |
枚举 | 滚动到头/尾时的弹簧回弹动画 |
特别说明:List 构造参数 { space: 0 } 控制项间距。如果设置为非零值,space 在下拉刷新或滑动删除等交互中会影响动画效果,因此建议使用 divider 替代 space 做视觉分隔。
4.3 列表项的内部布局
每个 ListItem 的内部结构(由 @Builder ContactListItem 定义):
Row(高 72vp,白底,垂直居中)
├── Stack(48×48) ← 圆形头像容器
│ ├── Circle(fill: color)← 彩色圆形
│ └── Text(姓氏首字) ← 白色文字居中
├── Column(layoutWeight:1) ← 文本信息
│ ├── Text(昵称) ← 16px 深色
│ └── Text(描述) ← 13px 灰色
└── Text(">") ← 右侧箭头
头像的设计考量:传统联系人列表需要为每个联系人准备头像图片,这在 DEMO 阶段会增加复杂度。本示例采用 Stack + Circle + Text 组合——Circle 设置不同的 fill 颜色作为背景,Text 显示姓氏的第一个字。这样既美观又完全脱离了图片资源的依赖,适合作为快速原型或演示项目。
4.4 分割线的两种实现方式
ArkTS 为列表分割线提供了两种配置途径:
方式一:List.divider() 全局配置(推荐,本示例采用)
List() { ... }
.divider({
strokeWidth: 0.5, // 线宽
color: '#e0e0e0', // 颜色
startMargin: 72, // 起始缩进(对齐头像右侧)
endMargin: 16 // 结束缩进
})
优点:一行配置,全局生效;分隔线是列表底层绘制,不影响列表项的高度计算。
方式二:ListItemSeparator 组件
ListItem() { ... }
ListItemSeparator {
.strokeWidth(0.5)
.startMargin(72)
}
优点:每个列表项可以独立控制分割线样式(例如最后一项可以隐藏)。
最佳实践:如果所有列表项的分割线样式一致,优先使用
.divider();如果某些项需要特殊处理(如隐藏、粗线、不同颜色),则使用ListItemSeparator。
4.5 @Builder 装饰器的作用
@Builder 是 ArkTS 中用来封装可复用 UI 片段的装饰器。在本例中,我们将每个列表项的布局抽出为 ContactListItem 方法,而不是直接写在 build() 方法中。
这样做的好处:
- 代码可读性:
build()方法结构清晰,一眼看出整体布局骨架 - 复用性:如果页面其他地方也需要展示联系人卡片,直接调用
this.ContactListItem(item)即可 - 可维护性:修改列表项 UI 时只需改动
@Builder内部,不影响外层逻辑
五、状态驱动与数据流
5.1 @State 装饰器的用法
@State contacts: ContactItem[] = [ ... ];
@State 是 ArkTS 中最重要的响应式装饰器之一。被 @State 装饰的变量具有以下特性:
- 值变化自动触发 UI 重新渲染:当
contacts数组的元素被修改(增删改)时,List会自动重新绘制受影响的ListItem - 深层次监听:数组内部元素的属性变更也会触发更新(例如修改某个联系人的
desc字段)
这种机制使得开发者只需要关注数据层的变更,UI 层由框架自动维护——这就是"状态驱动 UI"的核心思想。
5.2 数据变更示例
假设我们需要动态添加联系人,只需这样写:
// 在 struct Index 内部
addContact() {
this.contacts.push({
name: '新联系人',
color: AVATAR_COLORS[this.contacts.length % AVATAR_COLORS.length],
desc: '新加入的联系人'
});
// 无需手动操作 DOM,List 会自动更新
}
再比如修改某个联系人的描述:
updateDesc(index: number, newDesc: string) {
this.contacts[index].desc = newDesc;
// 框架自动找到对应的 ListItem 并重新渲染
}
六、常见问题与避坑指南
6.1 头像图片加载失败怎么办?
如果你坚持使用图片作为头像(例如从网络加载),建议设置兜底策略:
Image(item.avatarUrl)
.width(48)
.height(48)
.borderRadius(24)
.backgroundColor('#cccccc') // 图片加载前的占位色
.alt($r('sys.media.ohos_app_icon')) // 加载失败时的兜底图
6.2 API 24 中 List 组件的变化
在 API 24(HarmonyOS NEXT 6.2+)中,List 组件有如下演进:
- 性能优化:懒加载机制更完善,万级列表不需要额外配置
LazyForEach也能保持流畅 - 手势冲突处理:
ListItem内嵌可滑动组件(如SwipeAction)时,滚动冲突自动解决 - 新属性:
.scrollBar()支持更细粒度的滚动条样式控制 - 废弃 API:
ListItem的sticky属性已由List.sticky()代替
6.3 编译错误排查
如果你遇到 ArkTS 编译错误,优先检查以下几点:
| 错误类型 | 常见原因 | 解决方案 |
|---|---|---|
',' expected |
链式调用写在了不支持的语法位置(如 ForEach 的 {} 后直接加 .xxx()) |
将属性移动到 List 级别或 ListItem 内部 |
Cannot find name 'xxx' |
拼写错误或未 import | ArkTS 内置组件无需 import,但枚举值如 EdgeEffect.Spring 需确认包名 |
Unknown resource name |
$r('sys.media.xxx') 引用了不存在的系统资源 |
改用 app.media 自有资源,或纯代码实现 |
Property 'xxx' does not exist |
API 版本不支持该属性 | 检查 build-profile.json5 中的 compatibleSdkVersion |
七、总结
本文从零到一构建了一个鸿蒙 NEXT(API 24)联系人列表页面,涵盖了以下核心知识点:
- ArkTS 页面结构:
@Entry @Component装饰器定义页面入口,build()方法构建 UI 树 - List 组件:作为垂直滚动容器,配合
ForEach循环渲染数据驱动的列表项 - @Builder 封装:将列表项布局独立为可复用的构建函数
- 纯代码头像:
Stack + Circle + Text组合实现圆形色彩头像,零图片依赖 - 分割线配置:
List.divider()统一声明式配置 vs.ListItemSeparator细粒度控制 - 状态驱动:
@State响应式装饰器实现数据 → UI 的自动映射
这套布局模式适用于鸿蒙应用中绝大多数列表类场景:通讯录、设置页、消息列表、商品列表、订单列表等。掌握了 List + ForEach + @Builder 的组合拳,就掌握了鸿蒙页面开发中最核心的布局能力。
配套资源:本文完整代码可在 DevEco Studio 中直接导入运行。如有任何问题,欢迎在评论区留言交流。
更多推荐




所有评论(0)