HarmonyOS 入门 · 第2周

掌握 ArkUI 组件体系
构建应用界面

理解 ArkUI 核心组件、布局系统、列表优化和样式复用机制,搭建出完整的应用界面。

📅 预计耗时:5–7 天 🎯 目标:独立搭建一个多组件复合页面

1 基础组件

ArkUI 提供了一套丰富的内置组件,掌握它们是构建 UI 的第一步

📝
Text
文本显示组件,支持多行、富文本、字体样式控制
🔘
Button
按钮组件,支持点击事件、样式自定义、加载状态
🖼️
Image
图片组件,支持本地/网络图片、缩放模式、占位图
✏️
TextInput
文本输入框,支持占位符、密码模式、键盘类型控制

📝 Text 组件详解

Text 是 ArkUI 中最常用的组件,用于展示文本内容。

// 基础文本 Text('Hello HarmonyOS') .fontSize(20) .fontWeight(FontWeight.Bold) .fontColor('#e8e8f0') .textAlign(TextAlign.Center) // 多行文本(maxLines + textOverflow) Text('这是一段很长很长的文本内容,当文本超出指定行数时会被截断并显示省略号...') .maxLines(2) .textOverflow({ overflow: TextOverflow.Ellipsis }) .lineHeight(22) // 富文本(Span 子节点) Text() { Span('普通文字 ') Span('高亮文字') .fontColor('#6c63ff') .fontWeight(FontWeight.Bold) Span(' 普通文字') }
💡 关键属性:fontSizefontWeightfontColortextAlignmaxLinestextOverflowlineHeight

🔘 Button 组件详解

Button 支持多种样式和交互状态,是用户操作的核心入口。

// 基础按钮 Button('点击登录') .type(ButtonType.Capsule) // 胶囊样式 .backgroundColor('#6c63ff') .width(200) .height(44) .onClick(() => { this.handleLogin(); }) // 自定义内容按钮 Button() { Row() { Image($r('app.media.icon_add')) .width(18) .height(18) Text('添加项目') .fontSize(14) .fontColor('#fff') } .alignItems(VerticalAlign.Center) .gap(6) } .type(ButtonType.Normal) .backgroundColor('#6c63ff') .borderRadius(8) .onClick(() => this.addItem())
💡 ButtonType 三种样式:
ButtonType.Normal — 直角矩形,适合自定义圆角
ButtonType.Capsule — 胶囊形(两端半圆),最常见
ButtonType.Circle — 圆形,适合图标按钮

🖼️ Image 组件详解

Image 支持加载本地资源和网络图片,是应用中不可或缺的展示组件。

// 加载本地资源 Image($r('app.media.logo')) .width(120) .height(120) .borderRadius(60) // 圆形裁剪 .objectFit(ImageFit.Cover) // 加载网络图片 Image('https://example.com/avatar.png') .width('100%') .aspectRatio(16 / 9) // 保持宽高比 .objectFit(ImageFit.Contain) .onError(() => { this.showPlaceholder = true; }) // 占位图(条件渲染) if (this.showPlaceholder) { Column() { Text('图片加载失败') .fontSize(14) .fontColor('#9393b0') } .width('100%') .aspectRatio(16 / 9) .backgroundColor('#1a1a2e') .justifyContent(FlexAlign.Center) }
💡 ImageFit 模式速览:
Cover — 裁剪填充(类似 background-size: cover)
Contain — 完整显示(类似 contain)
Fill — 拉伸填充(可能变形)
Auto — 默认,自动选择合适模式

✏️ TextInput 组件详解

TextInput 是用户输入的入口,ArkUI 提供了丰富的输入控制能力。

@State username: string = ''; @State password: string = ''; // 普通文本框 TextInput({ placeholder: '请输入用户名', text: this.username }) .placeholderColor('#6b6b88') .backgroundColor('#141428') .borderRadius(8) .height(44) .onChange((value: string) => { this.username = value; }) // 密码输入框 TextInput({ placeholder: '请输入密码', text: this.password }) .type(InputType.Password) // 密码模式 .backgroundColor('#141428') .borderRadius(8) .height(44) .showPasswordIcon(true) // 显示密码切换图标 .onChange((value: string) => { this.password = value; }) // 多行文本输入 TextArea({ placeholder: '请输入备注信息', text: this.note }) .backgroundColor('#141428') .borderRadius(8) .height(100)
💡 InputType 类型:Normal(默认)、Password(密码)、Email(邮箱)、Number(数字)、PhoneNumber(电话)

2 布局系统

ArkUI 的布局系统基于弹性盒子模型,理解它们是控制界面排布的关键

Column
垂直排列(从上到下)
Row
水平排列(从左到右)
Flex
弹性布局(更灵活的排列)
Grid
网格布局(行列对齐)

📐 Column — 垂直布局

Column 是 ArkUI 最常用的布局容器,子组件从上到下依次排列。

Column() { Text('标题').fontSize(24).fontWeight(FontWeight.Bold) Text('这是一段描述文字').fontSize(14).fontColor('#9393b0') Button('了解更多').type(ButtonType.Capsule).margin({ top: 16 }) } .width('100%') .padding(20) .backgroundColor('#1a1a2e') .borderRadius(12) .alignItems(HorizontalAlign.Start) // 水平对齐:Start / Center / End .justifyContent(FlexAlign.SpaceBetween) // 垂直分布
💡 Column 关键属性:
alignItems — 水平方向对齐(Start / Center / End / Stretch)
justifyContent — 垂直方向分布(Start / Center / End / SpaceBetween / SpaceAround / SpaceEvenly)

➡️ Row — 水平布局

Row 与 Column 类似,但子组件从左到右水平排列。

// 水平排列的图标 + 文字 Row() { Image($r('app.media.icon_star')).width(24).height(24) Text('4.8 分').fontSize(14).fontColor('#fbbf24') Text('(1,234 条评价)').fontSize(12).fontColor('#6b6b88') } .alignItems(VerticalAlign.Center) // 垂直居中对齐 .gap(6) // 子组件间距 // 两端对齐的布局(类似 flex justify-content: space-between) Row() { Text('左侧标题').fontSize(16) Text('详情 →').fontSize(14).fontColor('#6c63ff') } .width('100%') .justifyContent(FlexAlign.SpaceBetween)
💡 Row vs Column 核心区别:Row 的主轴是水平方向,Column 的主轴是垂直方向。justifyContent 控制主轴,alignItems 控制交叉轴。

🔀 Flex — 弹性布局

Flex 是 Column/Row 的底层实现,提供更细粒度的弹性控制。

// 水平弹性布局(等同 Row) Flex({ direction: FlexDirection.Row }) { Text('项目 1') Text('项目 2') Text('项目 3') } .justifyContent(FlexAlign.SpaceAround) .width('100%') // 垂直弹性布局(等同 Column) Flex({ direction: FlexDirection.Column }) { // ... } // 弹性系数控制(layoutWeight) Row() { Column() { Text('左侧(占比 1)') } .layoutWeight(1) .backgroundColor('#2a2a4a') Column() { Text('右侧(占比 2)') } .layoutWeight(2) .backgroundColor('#1a1a2e') } .width('100%') .height(60) .borderRadius(8)
💡 layoutWeight 弹性系数:类似 CSS Flexbox 的 flex 属性。父容器中所有子组件的 layoutWeight 值之和决定了各自占比。非常适合自适应布局。

🧩 Grid — 网格布局

Grid 提供行列对齐的网格布局能力,适合宫格导航、图片墙等场景。

// 3 列网格 Grid() { ForEach(this.menuItems, (item: MenuItem, index) => { Column() { Image(item.icon).width(32).height(32) Text(item.label).fontSize(12).margin({ top: 4 }) } .width('100%') .padding(16) .backgroundColor('#1a1a2e') .borderRadius(8) }) } .columnsTemplate('1fr 1fr 1fr') // 三列等宽 .rowsTemplate('1fr 1fr') // 两行等宽 .columnsGap(12) // 列间距 .rowsGap(12) // 行间距 .padding(16)
💡 Grid 模板语法:
columnsTemplate('1fr 1fr 1fr') — 3 列等宽
columnsTemplate('1fr 2fr') — 第 1 列占 1 份,第 2 列占 2 份
columnsTemplate('100px 1fr') — 第 1 列固定 100px,其余自适应
rowsAuto(true) — 行数自适应内容

🪆 布局嵌套实战:一个典型的卡片布局

实际应用中,Column、Row、Flex 通常嵌套使用来构建复杂 UI。

// 商品卡片:Column 内嵌 Row 和 Flex Column() { // 商品图片 Image(item.imageUrl) .width('100%') .aspectRatio(1) .objectFit(ImageFit.Cover) .borderRadius({ topLeft: 12, topRight: 12 }) // 商品信息区域 Column({ space: 6 }) { Text(item.title) .fontSize(16) .fontWeight(FontWeight.Medium) .maxLines(2) .textOverflow({ overflow: TextOverflow.Ellipsis }) Row({ space: 4 }) { Text(`¥${item.price}`) .fontSize(18) .fontWeight(FontWeight.Bold) .fontColor('#f87171') Text(`已售 ${item.sold}`) .fontSize(12) .fontColor('#6b6b88') } .alignItems(VerticalAlign.Bottom) .width('100%') .justifyContent(FlexAlign.SpaceBetween) } .padding(12) } .width('48%') .backgroundColor('#1a1a2e') .borderRadius(12) .onClick(() => this.gotoDetail(item))

3 列表与网格

List 是 ArkUI 中最核心的数据展示组件,配合 LazyForEach 实现高性能长列表

📋 List + ForEach — 基础列表

List 组件配合 ForEach 是最常见的数据列表实现方式。

// 数据模型 interface TodoItem { id: number; title: string; done: boolean; } @State todos: TodoItem[] = [ { id: 1, title: '学习 Text 组件', done: true }, { id: 2, title: '学习 Button 组件', done: true }, { id: 3, title: '掌握布局系统', done: false }, ]; // 渲染列表 List({ space: 12 }) { // space: 列表项间距 ForEach(this.todos, (item: TodoItem) => { ListItem() { Row() { Text(item.done ? '✅' : '⬜').fontSize(18) Text(item.title) .fontSize(16) .fontColor(item.done ? '#6b6b88' : '#e8e8f0') .decoration({ type: item.done ? TextDecorationType.LineThrough : TextDecorationType.None }) } .alignItems(VerticalAlign.Center) .gap(12) .padding(16) .width('100%') } .onClick(() => this.toggleTodo(item.id)) }, (item: TodoItem) => item.id.toString()) // key 生成器 } .width('100%') .height('100%') .divider({ strokeWidth: 1, color: '#2a2a4a', startMargin: 16, endMargin: 16 })
💡 List 核心属性:
space — 列表项间距
divider — 分割线样式
scrollBar — 滚动条显示策略
edgeEffect — 边缘回弹效果(Spring / None)
listDirection — 列表方向(Vertical / Horizontal)

⚡ LazyForEach — 高性能长列表

当数据量很大(几百条以上)时,ForEach 会导致所有列表项同时渲染,性能下降。LazyForEach 通过懒加载机制只渲染可视区域内的子组件。

ForEach
❌ 全部渲染
❌ 不适合大数据
✅ 用法简单
✅ 适合 <100 条数据
LazyForEach
✅ 按需渲染
✅ 万条数据流畅
⚠️ 需实现数据源类
✅ 适合列表长/图片多
// 第一步:实现 IDataSource 数据源 class TodoDataSource implements IDataSource { private data: TodoItem[] = []; totalCount(): number { return this.data.length; } getData(index: number): TodoItem { return this.data[index]; } registerDataChangeListener(listener: DataChangeListener): void {} unregisterDataChangeListener(listener: DataChangeListener): void {} } // 第二步:在组件中使用 @State todoSource: TodoDataSource = new TodoDataSource(); aboutToAppear() { // 初始化数据(如网络请求后填充) } List({ space: 12 }) { LazyForEach(this.todoSource, (item: TodoItem) => { ListItem() { // 列表项 UI(同上) } }, (item: TodoItem) => item.id.toString()) } .width('100%') .height('100%')
✅ 经验法则:数据量 < 50 条 → 用 ForEach,简单直接;数据量 50+ 条或列表项包含图片 → 用 LazyForEach。性能差距在真机上非常明显。

🔄 下拉刷新 + 加载更多

实际应用中,列表通常需要支持下拉刷新和上拉加载更多功能。

// 下拉刷新(使用 Refresh 组件包裹 List) Refresh({ refreshing: $$this.isRefreshing }) { List({ space: 12 }) { LazyForEach(this.dataSource, (item) => { ListItem() { /* ... */ } }) } } .onRefresh(() => { this.loadData(); // 加载数据后设置 this.isRefreshing = false }) // 上拉加载更多(监听 onReachEnd) List({ space: 12 }) { LazyForEach(this.dataSource, (item) => { ListItem() { /* ... */ } }) // 底部加载状态指示器 if (this.hasMore) { ListItem() { Row() { LoadingProgress().width(20).height(20) Text('加载中...').fontSize(14).fontColor('#6b6b88') } .justifyContent(FlexAlign.Center) .gap(8) .padding(16) .width('100%') } } } .onReachEnd(() => { if (this.hasMore) { this.loadMore(); } })
💡 注意:onReachEnd 在列表滑动到底部时触发,适合做"加载更多"。Refresh 组件提供系统级的下拉刷新动画。

4 样式体系

ArkUI 提供多种样式复用机制,帮助你编写更干净、更高效的 UI 代码

🔗 链式调用(最基本的方式)

ArkUI 中的每个组件都支持链式调用设置样式属性。

Text('Hello') .fontSize(20) .fontWeight(FontWeight.Bold) .fontColor('#6c63ff') .textAlign(TextAlign.Center) .backgroundColor('#1a1a2e') .padding(16) .borderRadius(8) .width('100%')

📐 @Styles — 通用样式复用

@Styles 用于定义可在多个组件中复用的通用样式方法。适合没有参数变化的固定样式。

// 在组件 struct 内定义 @Styles cardStyle() { .backgroundColor('#1a1a2e') .borderRadius(12) .padding(16) .width('100%') } // 使用 Column() { Text('卡片标题').fontSize(18) Text('卡片内容').fontSize(14).fontColor('#9393b0') } .cardStyle() // ✅ 复用卡片样式 // 另一个卡片 Row() { Text('配置') Text('→').fontColor('#6c63ff') } .cardStyle() // ✅ 同样的卡片样式
💡 @Styles 限制:只能在 struct 内部定义,且不能接收参数。适合纯样式复用。

🧩 @Extend — 带参数的样式扩展

@Extend 可以为目标组件类型扩展自定义样式方法,且支持参数传递。

// 全局定义(可以在文件任意位置) @Extend(Text) titleStyle(color?: string) { .fontSize(20) .fontWeight(FontWeight.Bold) .fontColor(color ?? '#e8e8f0') } @Extend(Button) primaryButton(bgColor?: string) { .type(ButtonType.Capsule) .backgroundColor(bgColor ?? '#6c63ff') .width(200) .height(44) } // 使用 Text('欢迎回来').titleStyle() // 默认颜色 Text('错误提示').titleStyle('#f87171') // 自定义颜色 Button('登录').primaryButton() // 默认紫色 Button('危险操作').primaryButton('#f87171') // 红色按钮
💡 @Extend vs @Styles:
@Extend — 支持参数、可全局定义、绑定到特定组件类型
@Styles — 无参数、只能在 struct 内定义、任何组件可用
优先使用 @Extend 当需要参数化样式时。

🏗️ @Builder — 可复用 UI 片段

@Builder 用于定义可复用的 UI 片段,比样式更进一步——它可以包含完整的 UI 结构和逻辑。

// 定义可复用的列表项 @Builder todoItemBuilder(item: TodoItem) { Row() { Text(item.done ? '✅' : '⬜').fontSize(18) Text(item.title).fontSize(16) } .alignItems(VerticalAlign.Center) .gap(12) .padding(16) .width('100%') .backgroundColor('#1a1a2e') .borderRadius(8) } // 在 build 中复用 build() { Column({ space: 12 }) { this.todoItemBuilder({ id: 1, title: '学习 @Builder', done: true }) this.todoItemBuilder({ id: 2, title: '练习样式复用', done: false }) } .padding(16) .width('100%') } // 使用 @BuilderParam 实现插槽效果 @BuilderParam content: () => void = this.defaultContent; @Builder defaultContent() { /* 默认内容 */ }
💡 @Builder 最佳实践:将复杂的列表项 UI 抽取为 @Builder 方法,让 build() 保持清晰。配合 @BuilderParam 可实现类似 Vue Slot 的插槽机制。

🎯 样式复用策略对比

理解不同机制的适用场景,选择合适的复用方式。

机制 适用范围 参数 定义位置
链式调用 一次性的简单样式 直接使用
@Styles 通用样式复用(无参数) struct 内部
@Extend 组件特定的参数化样式 全局任意位置
@Builder 完整 UI 片段复用 struct 内部
💡 推荐策略:简单的组件链式调用直接写;重复样式用 @Styles;带参数用 @Extend;复杂 UI 片段用 @Builder。层层递进,保持代码清爽。

5 本周实战:待办清单 App

综合运用本周所学,写出一个有完整交互的待办应用

🎯 项目需求

用 ArkUI 实现一个简洁的待办清单 App,包含以下功能:

  • 待办列表展示 — 使用 List + ForEach 渲染任务列表
  • 添加待办 — TextInput + Button 实现新增任务
  • 切换完成状态 — 点击列表项切换 done 状态,已完成项显示删除线
  • 删除待办 — 长按列表项弹出删除确认
  • 统计信息 — 显示"总共 X 项,已完成 Y 项"
// 完整实现(约 80 行) @Entry @Component struct TodoApp { @State todos: TodoItem[] = []; @State newTodo: string = ''; build() { Column() { // 标题 Text('我的待办') .fontSize(28) .fontWeight(FontWeight.Bold) .padding({ top: 24, bottom: 16 }) // 统计 Text(`总共 ${this.todos.length} 项,已完成 ${this.todos.filter(t => t.done).length} 项`) .fontSize(14) .fontColor('#6b6b88') .padding({ bottom: 16 }) // 输入区域 Row({ space: 12 }) { TextInput({ placeholder: '输入新待办...', text: $$this.newTodo }) .layoutWeight(1) .backgroundColor('#141428') .borderRadius(8) .height(44) Button('添加') .type(ButtonType.Capsule) .backgroundColor('#6c63ff') .height(44) .onClick(() => this.addTodo()) } .width('100%') .padding({ bottom: 20 }) // 待办列表 List({ space: 8 }) { ForEach(this.todos, (item: TodoItem, index) => { ListItem() { this.todoItemView(item, index) } }) } .width('100%') .layoutWeight(1) } .padding(20) .width('100%') .height('100%') } @Builder todoItemView(item: TodoItem, index: number) { Row() { Text(item.done ? '✅' : '⬜').fontSize(20) Text(item.title) .fontSize(16) .fontColor(item.done ? '#6b6b88' : '#e8e8f0') .decoration({ type: item.done ? TextDecorationType.LineThrough : TextDecorationType.None }) } .alignItems(VerticalAlign.Center) .gap(12) .padding(16) .width('100%') .backgroundColor('#1a1a2e') .borderRadius(8) .onClick(() => this.toggleTodo(index)) } addTodo() { if (this.newTodo.trim().length > 0) { this.todos.push({ id: Date.now(), title: this.newTodo.trim(), done: false }); this.newTodo = ''; } } toggleTodo(index: number) { this.todos[index].done = !this.todos[index].done; } }
🧪 动手做:在 DevEco Studio 中新建一个 Empty Ability 项目,将上述代码替换到 Index.ets,运行到模拟器体验效果。然后试着添加 删除功能(长按删除)和 分类标签(全部/待完成/已完成)来加深理解。

第2周通关清单

完成以下事项,即可进入第3周的学习

  • 掌握 Text、Button、Image、TextInput 的核心用法 常用属性、事件绑定、显示状态
  • 理解 Column/Row/Flex/Grid 布局系统的差异 主轴/交叉轴、弹性系数、模板语法
  • 学会使用 List + ForEach/LazyForEach 渲染数据列表 列表渲染、数据源、性能优化
  • 掌握 @Styles / @Extend / @Builder 的样式复用技巧 理解各自适用场景,编写可维护的样式代码
  • 完成待办清单实战项目 在 DevEco Studio 中运行并体验
📖 下一步:返回 完整入门指南,查看第 3 周的学习路线。