鸿蒙ArkUI实战:步骤表单与进度指示器
本文介绍了一个使用ArkUI实现的三步文章发布表单设计,通过分步填写显著提升用户体验和完成率。核心设计包括: 分步架构:将长表单拆解为基本信息、内容填写和确认发布三个步骤,每步独立校验确保数据完整性 智能导航:动态显示"上一步/下一步"按钮,第1步仅"下一步",第3步显示红色"发布"按钮强化操作警示 状态管理:仅用currentStep等4个@State变量控制整个流程,保持各步骤数据持久性 视觉
长表单拆成多步骤是提升填写完成率的关键 UX 手段。本文用 ArkUI 构建一个三步文章发布流程——自定义步骤指示器、每步独立校验、"上一步/下一步"导航,以及确认页面的一键发布。
一、我们要做什么
一个"发布文章"的三步表单:
- 第1步:基本信息 — 输入标题(必填,最多 30 字)+ 选择分类(5 个标签,点击选中/取消)
- 第2步:填写内容 — TextArea 输入正文(必填,不少于 10 个字符,最多 500 字)
- 第3步:确认发布 — 只读汇总(标题、分类、内容预览),点击"发布"弹窗确认
交互点:
- 步骤指示器 — 顶部三个圆点 + 连接线,已完成绿色对勾,当前蓝色数字,未完成灰色
- 上一步/下一步 — 底部按钮根据当前步骤动态切换,第 1 步只有"下一步",第 3 步只有"发布"
- 每步校验 — 第 1 步:标题非空 + 分类已选;第 2 步:内容 ≥ 10 字;校验不通过 → Toast 提示,不跳转
- 发布确认 — 第 3 步弹窗确认 → 发布成功 → 重置全部状态回到第 1 步
二、状态管理:一个 currentStep 掌控全局
@State currentStep: number = 1;
@State title: string = '';
@State category: string = '';
@State content: string = '';
整个三步表单只有 4 个 @State。currentStep 决定当前显示哪一步的内容,title / category / content 是三步共享的表单数据。
为什么三个步骤的数据放在同一个页面而不是拆成三个独立页面?因为步骤表单的数据是有依赖关系的——第 3 步需要汇总第 1 步和第 2 步的数据。如果拆成三个页面,需要通过路由参数传递这些数据,增加不必要的复杂性。
表单数据的持久性 — 用户在第 1 步填了标题 → 点"下一步"到第 2 步 → 后悔了,点"上一步"回第 1 步 → 标题还在。因为 currentStep 只是切换了内容区域的可见性,并没有销毁状态。数据一直保存在 @State 中。

三、交互点1:步骤指示器
@Builder
stepDot(step: number, label: string) {
Column() {
Row() {
if (step < this.currentStep) {
Text('✓') // 已完成:对勾
.fontSize(14)
.fontColor(Color.White)
}
if (step === this.currentStep) {
Text(`${step}`) // 当前步骤:数字
.fontSize(14)
.fontWeight(FontWeight.Bold)
.fontColor(Color.White)
}
if (step > this.currentStep) {
Text(`${step}`) // 未完成:灰色数字
.fontSize(14)
.fontColor(AppColors.TEXT_DISABLED)
}
}
.width(28).height(28)
.borderRadius(14) // 圆形
.backgroundColor(
step < this.currentStep ? AppColors.PRIMARY : // 已完成:蓝色
(step === this.currentStep ? AppColors.PRIMARY : '#E8E8E8') // 当前:蓝色 / 未完成:灰色
)
.justifyContent(FlexAlign.Center)
Text(label)
.fontSize(FontSize.CAPTION)
.fontColor(step <= this.currentStep ? AppColors.PRIMARY : AppColors.TEXT_DISABLED)
.fontWeight(step === this.currentStep ? FontWeight.Medium : FontWeight.Regular)
.margin({ top: Spacing.SM })
}
.alignItems(HorizontalAlign.Center)
}
三个视觉状态:
| 状态 | 圆圈 | 文字 |
|---|---|---|
| 已完成(step < current) | 蓝色实心 + 白色 ✓ | 蓝色 |
| 当前(step === current) | 蓝色实心 + 白色数字 | 蓝色加粗 |
| 未开始(step > current) | 灰色实心 + 灰色数字 | 灰色常规 |
连接线
@Builder
stepLine(from: number) {
Row()
.width(40).height(2)
.backgroundColor(from < this.currentStep ? AppColors.PRIMARY : '#E8E8E8')
.margin({ left: Spacing.XS, right: Spacing.XS, bottom: Spacing.XXL })
}
from 是起点步骤的编号(1 或 2)。连接线 width 40vp + 左右 margin XS(4vp) ≈ 48vp 间距。线在圆圈的水平中间位置,margin-bottom: Spacing.XXL(24vp) 让线和圆圈的底部有一个固定的视觉间距。

四、交互点2:步骤导航与校验
4.1 "下一步"校验
private canNextStep1(): boolean {
return this.title.trim().length > 0 && this.category.length > 0;
}
private canNextStep2(): boolean {
return this.content.trim().length >= 10;
}
private goNext(): void {
if (this.currentStep === 1) {
if (!this.canNextStep1()) {
promptAction.showToast({ message: '请填写标题并选择分类', duration: 1500 });
return;
}
this.currentStep = 2;
} else if (this.currentStep === 2) {
if (!this.canNextStep2()) {
promptAction.showToast({ message: '内容不少于10个字符', duration: 1500 });
return;
}
this.currentStep = 3;
}
}
每步的校验逻辑独立封装为 canNextStep1() / canNextStep2()。校验不通过 → Toast 提示 → return 阻止跳转。校验通过 → currentStep++ 切换到下一步。
为什么不在点"下一步"前就显示校验错误? 因为步骤表单的语义是"先填完这一步,再进入下一步"。在用户还没点"下一步"之前,不判断他"填得对不对"——他可能正在填写中。过早的校验错误只会让用户焦虑。
4.2 “上一步”
private goPrev(): void {
if (this.currentStep > 1) {
this.currentStep--;
}
}
上一步不需要校验——用户回头修改是正常的。只需要 currentStep-- 回到前一页,之前填的数据原封不动。
4.3 底部按钮的动态切换
if (this.currentStep > 1) {
Text('上一步') // 第 2、3 步显示
.border({ width: 1, color: AppColors.BORDER })
.borderRadius(9999)
.onClick(() => this.goPrev())
}
if (this.currentStep < 3) {
Text('下一步') // 第 1、2 步显示
.backgroundColor(AppColors.PRIMARY)
.borderRadius(9999)
.onClick(() => this.goNext())
}
if (this.currentStep === 3) {
Text('发布') // 仅第 3 步显示
.backgroundColor(AppColors.ERROR)
.borderRadius(9999)
.onClick(() => this.publish())
}
三个按钮通过 if 条件渲染,"上一步"和"下一步"不会同时出现在第 1 步或第 3 步。"发布"按钮用红色(AppColors.ERROR)——发布是破坏性的"提交"操作,红色在视觉上让用户审慎对待。

五、交互点3:发布确认与重置
private publish(): void {
promptAction.showDialog({
title: '确认发布',
message: `标题:${this.title}\n分类:${this.category}\n内容长度:${this.content.length}字`,
buttons: [
{ text: '取消', color: AppColors.TEXT_TERTIARY },
{ text: '发布', color: AppColors.PRIMARY }
]
}).then((result) => {
if (result.index === 1) {
this.title = '';
this.category = '';
this.content = '';
this.currentStep = 1;
promptAction.showToast({ message: '发布成功!', duration: 1500 });
}
});
}
弹窗再次确认来自用户的操作——在第 3 步已经看到了全部信息,最后一步用弹窗做最终确认。result.index === 1 表示用户点了"发布"。
发布成功后:
- 清空三个表单字段
- 回到第 1 步
- Toast 反馈
这模拟了"发布完成,可以重新填写下一篇"的完整流程闭环。
六、第3步的只读汇总
this.summaryRow('标题', this.title)
this.summaryRow('分类', this.category)
// 内容预览
Text(this.content)
.maxLines(5)
.textOverflow({ overflow: TextOverflow.Ellipsis })
@Builder
summaryRow(label: string, value: string) {
Row() {
Text(label)
.fontSize(FontSize.BODY)
.fontColor(AppColors.TEXT_TERTIARY)
.width(60)
Text(value)
.fontSize(FontSize.BODY)
.fontColor(AppColors.TEXT_PRIMARY)
.fontWeight(FontWeight.Medium)
.layoutWeight(1)
}
}
summaryRow 是一个统一的键值对展示组件——左边灰色标签固定 60vp 宽度,右边黑色值占据剩余空间。内容预览用 maxLines(5) 限制高度,超出显示省略号——用户在确认页不需要读完整内容,只需要确认"是自己刚才写的"。
七、第1步的分类标签
Flex({ wrap: FlexWrap.Wrap }) {
ForEach(CATEGORIES, (item: string) => {
Text(item)
.fontColor(this.category === item ? Color.White : AppColors.TEXT_SECONDARY)
.backgroundColor(this.category === item ? AppColors.PRIMARY : AppColors.BACKGROUND)
.borderRadius(BorderRadius.SM)
.padding(...)
.onClick(() => {
this.category = this.category === item ? '' : item; // 点击切换
})
})
}
5 个分类标签,用 Flex({ wrap: FlexWrap.Wrap }) 包裹——超过一行自动换行。Row 不支持换行,Flex 支持。这是 ArkUI 布局的一个重要区别。
点击行为:this.category === item ? '' : item——选中后再次点击可取消。这是一个轻量的单选交互,比 RadioGroup 更直观。
八、页面结构总结
StepperPage (~300 行)
├── 状态层
│ ├── @State currentStep: number — 当前步骤 (1/2/3)
│ ├── @State title: string — 文章标题
│ ├── @State category: string — 文章分类
│ └── @State content: string — 文章内容
├── 校验方法
│ ├── canNextStep1() — 标题 + 分类校验
│ └── canNextStep2() — 内容 ≥ 10 字校验
├── 导航方法
│ ├── goNext() — 校验通过 → 下一步
│ └── goPrev() — 无校验 → 上一步
├── 业务方法
│ └── publish() — 弹窗确认 → 发布 → 重置
├── 步骤指示 Builder
│ ├── stepDot(step, label) — 圆形步骤标记
│ └── stepLine(from) — 连接线
├── 步骤内容 Builder
│ ├── stepContent1() — 标题输入 + 分类选择
│ ├── stepContent2() — 内容 TextArea + 字数
│ └── stepContent3() — 只读汇总 + 发布
└── UI
├── Header
├── 步骤指示器 (3 个 stepDot + 2 条 stepLine)
├── 步骤内容 (条件渲染)
└── 底部导航按钮
九、常见面试题 / 踩坑点
9.1 Flex vs Row 什么时候用哪个?
- Row — 单行排列,子元素不换行。适合固定数量的元素(如导航栏的按钮)
- Flex + wrap — 多行排列,子元素超出容器宽度自动换行。适合数量不固定的标签/芯片
常见错误:在 Row 中放了 7-8 个 Chip/Tag,期望换行,但 Row 不支持 wrap。改用 Flex({ wrap: FlexWrap.Wrap })。
9.2 第三步为什么是"只读"而不是"可编辑"?
步骤表单有两种常见模式:
- 确认模式(本 Demo)— 第 3 步只读展示,不能修改。要改 → 点"上一步"回到对应步骤
- 全文模式 — 第 3 步可以编辑所有字段,等同于一个长表单
确认模式的优点:每步职责单一,校验逻辑不重复(只在第 1、2 步校验,第 3 步不需要)。全文模式适合"用户不喜欢来回跳"的场景,但校验逻辑需要集中在最后一步。
9.3 步骤表单的数据会在"上一步"时丢失吗?
不会。@State 变量不会被 if (currentStep === n) 的条件渲染销毁。数据一直保存在组件中,切换步骤只是切换了内容的可见性。
但如果某个步骤的内容过于复杂(如富文本编辑器),可以考虑用 visibility 或 .offset 隐藏而不是 if 条件渲染——避免重建组件的开销。
9.4 为什么第 2 步的字符数提示用红色/灰色切换?
Text(this.content.length < 10
? `还差${10 - this.content.length}个字符`
: `${this.content.length}/500`)
.fontColor(this.content.length < 10 ? AppColors.ERROR : AppColors.TEXT_DISABLED)
少于 10 字时用红色提示"还差 N 个字符"——这是一个实时校验反馈,让用户知道"为什么不让我点下一步"。达到 10 字后变回灰色,提示消失。这种"即时反馈"可以减少用户点击"下一步"后被 Toast 拒绝的挫败感。
9.5 步骤指示器的 step < this.currentStep 判断为什么用 < 而不是 <=?
三种状态的覆盖逻辑:
step < currentStep→ 已完成(step 1 < current 2 → 第 1 步已完成)step === currentStep→ 当前step > currentStep→ 未完成
< 确保了第 3 步(currentStep=3)时,前两步都显示为"已完成"(对勾)。
十、运行方式
代码位于 dev/entry/src/main/ets/pages/StepperPage.ets。
用 DevEco Studio 打开 dev/ 项目,首页点击"步骤表单 — 三步发布文章与进度指示"即可体验:
- 进入页面 → 第 1 步,步骤指示器显示蓝色"1",输入标题 + 选择分类
- 点击"下一步" → 进入第 2 步(如未填 → Toast 提示)
- 第 2 步输入内容,字数不足 10 时显示红色提示"还差 N 个字符"
- 点击"下一步" → 进入第 3 步,查看汇总信息
- 点"上一步"回到第 2 步修改 → 再回来,数据不变
- 第 3 步点击"发布" → 弹窗确认 → 发布成功 → 回到第 1 步(已清空)
十一、扩展方向
- 步骤指示器动画 — 步骤切换时有平滑的颜色过渡(animateTo),对勾出现时有缩放动画
- 保存草稿 — 用 Preferences 持久化表单数据,用户退出页面后回来可以继续填写
- 条件步骤 — 根据第 1 步的分类选择,动态决定是否需要第 2 步(如"转载"分类跳过内容填写)
- 步骤校验前置 — 第 1 步实时校验标题长度和分类,不等到点"下一步"才报错
- 长表单拆分策略 — 超过 10 个字段的长表单,自动拆分为 3-4 步,每步 2-3 个字段
- 进度百分比 — 在步骤指示器下方显示"已完成 66%",给用户明确的目标感
- 步骤间数据依赖 — 第 2 步的选项列表根据第 1 步的选择动态变化(如选了"技术文章"分类 → 第 2 步出现"技术标签"字段)
更多推荐



所有评论(0)