当前位置: 首页 > news >正文

鸿蒙ArkUI实战:步骤表单与进度指示器

长表单拆成多步骤是提升填写完成率的关键 UX 手段。本文用 ArkUI 构建一个三步文章发布流程——自定义步骤指示器、每步独立校验、"上一步/下一步"导航,以及确认页面的一键发布。


一、我们要做什么

一个"发布文章"的三步表单:

  1. 第1步:基本信息— 输入标题(必填,最多 30 字)+ 选择分类(5 个标签,点击选中/取消)
  2. 第2步:填写内容— TextArea 输入正文(必填,不少于 10 个字符,最多 500 字)
  3. 第3步:确认发布— 只读汇总(标题、分类、内容预览),点击"发布"弹窗确认

交互点:

  1. 步骤指示器— 顶部三个圆点 + 连接线,已完成绿色对勾,当前蓝色数字,未完成灰色
  2. 上一步/下一步— 底部按钮根据当前步骤动态切换,第 1 步只有"下一步",第 3 步只有"发布"
  3. 每步校验— 第 1 步:标题非空 + 分类已选;第 2 步:内容 ≥ 10 字;校验不通过 → Toast 提示,不跳转
  4. 发布确认— 第 3 步弹窗确认 → 发布成功 → 重置全部状态回到第 1 步

二、状态管理:一个currentStep掌控全局

@StatecurrentStep:number=1;@Statetitle:string='';@Statecategory:string='';@Statecontent:string='';

整个三步表单只有 4 个@StatecurrentStep决定当前显示哪一步的内容,title/category/content是三步共享的表单数据。

为什么三个步骤的数据放在同一个页面而不是拆成三个独立页面?因为步骤表单的数据是有依赖关系的——第 3 步需要汇总第 1 步和第 2 步的数据。如果拆成三个页面,需要通过路由参数传递这些数据,增加不必要的复杂性。

表单数据的持久性— 用户在第 1 步填了标题 → 点"下一步"到第 2 步 → 后悔了,点"上一步"回第 1 步 → 标题还在。因为currentStep只是切换了内容区域的可见性,并没有销毁状态。数据一直保存在@State中。


三、交互点1:步骤指示器

@BuilderstepDot(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)灰色实心 + 灰色数字灰色常规

连接线

@BuilderstepLine(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 "下一步"校验

privatecanNextStep1():boolean{returnthis.title.trim().length>0&&this.category.length>0;}privatecanNextStep2():boolean{returnthis.content.trim().length>=10;}privategoNext():void{if(this.currentStep===1){if(!this.canNextStep1()){promptAction.showToast({message:'请填写标题并选择分类',duration:1500});return;}this.currentStep=2;}elseif(this.currentStep===2){if(!this.canNextStep2()){promptAction.showToast({message:'内容不少于10个字符',duration:1500});return;}this.currentStep=3;}}

每步的校验逻辑独立封装为canNextStep1()/canNextStep2()。校验不通过 → Toast 提示 →return阻止跳转。校验通过 →currentStep++切换到下一步。

为什么不在点"下一步"前就显示校验错误?因为步骤表单的语义是"先填完这一步,再进入下一步"。在用户还没点"下一步"之前,不判断他"填得对不对"——他可能正在填写中。过早的校验错误只会让用户焦虑。

4.2 “上一步”

privategoPrev():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:发布确认与重置

privatepublish():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})
@BuildersummaryRow(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.1FlexvsRow什么时候用哪个?

  • 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 步,步骤指示器显示蓝色"1",输入标题 + 选择分类
  2. 点击"下一步" → 进入第 2 步(如未填 → Toast 提示)
  3. 第 2 步输入内容,字数不足 10 时显示红色提示"还差 N 个字符"
  4. 点击"下一步" → 进入第 3 步,查看汇总信息
  5. 点"上一步"回到第 2 步修改 → 再回来,数据不变
  6. 第 3 步点击"发布" → 弹窗确认 → 发布成功 → 回到第 1 步(已清空)

十一、扩展方向

  • 步骤指示器动画— 步骤切换时有平滑的颜色过渡(animateTo),对勾出现时有缩放动画
  • 保存草稿— 用 Preferences 持久化表单数据,用户退出页面后回来可以继续填写
  • 条件步骤— 根据第 1 步的分类选择,动态决定是否需要第 2 步(如"转载"分类跳过内容填写)
  • 步骤校验前置— 第 1 步实时校验标题长度和分类,不等到点"下一步"才报错
  • 长表单拆分策略— 超过 10 个字段的长表单,自动拆分为 3-4 步,每步 2-3 个字段
  • 进度百分比— 在步骤指示器下方显示"已完成 66%",给用户明确的目标感
  • 步骤间数据依赖— 第 2 步的选项列表根据第 1 步的选择动态变化(如选了"技术文章"分类 → 第 2 步出现"技术标签"字段)
http://www.jsqmd.com/news/958730/

相关文章:

  • 免费解锁Wand专业版:终极完整指南与远程控制教程
  • GBase 8s数据库的四种武器之一,图形化管理平台GEM解析
  • 数据预处理实战:分层防御架构与缺失/异常值决策树
  • 如何挑选真正实力派的GEO公司?指南分享
  • 别再手动画图了!用VSCode+PlantUML插件5分钟搞定UML类图(附完整语法速查表)
  • 非参数核聚类与老虎机反馈:理论与应用解析
  • STM32项目从Keil迁移到System Workbench全记录:工程配置、库管理与调试避坑指南
  • 2026年汽车电线线选型评测:储能线线缆、充电桩线缆、新能源电缆、机器人拖链线缆、汽车电线线、逆变器线缆、风能线缆选择指南 - 优质品牌商家
  • 从‘大泥球’到‘乐高积木’:聊聊我们团队踩过的架构坑与Service Mesh救赎之路
  • 实战演练,基于快马平台jdk17环境快速搭建restful api微服务
  • 2026年口碑好的装饰设计专业公司排名,靠谱的品牌推荐 - 工业品牌热点
  • ollama v0.30.5 更新:Hermes Desktop 上线、Windows 安装优化、Gemma4 崩溃修复、Cline CLI 集成文档全量补齐
  • Linux 服务器性能优化基础(CPU/内存/磁盘/网络)
  • 从DAG到值编码:图解编译原理龙书第六章核心概念,手把手教你搞定表达式优化
  • AD9851对比AD9850实战:6倍频到底香不香?实测70MHz+信号生成心得
  • 基于STM32与AD9851的双通道可编程波形发生器,支持基波+5次谐波叠加及三种基础波形输出
  • 技术演进:BepInEx Unity插件框架架构转型与IL2CPP运行时稳定性突破
  • 告别NTP服务器:手把手教你用ESP8266+STM32F103从零搭建一个离线/在线双模天气时钟(附完整代码)
  • 企业AI落地踩坑复盘:只做RAG走不远,ReAct补齐短板
  • 2026年Q2嘉兴奢侈品回收实测:嘉兴名鉴钟表有限公司联系/嘉兴首饰回收/嘉兴奢侈品回收/嘉兴工艺美术品回收/嘉兴黄金回收/选择指南 - 优质品牌商家
  • Linux 下 gcc / g++ 编译过程详解:从编译到链接
  • 实战指南:基于快马ai为django项目生成wsl2一体化开发环境配置脚本
  • 唐山广告宣传,哪家更靠谱?专业解析带你了解真相
  • EMR Serverless Spark 数据湖上新能力:一条 SQL 实现标量向量混合检索
  • Go 实验特性全解析:生命周期、状态及启用方法,开发者必看!
  • [特殊字符] 五大核心挑战与 Anthropic 建议
  • Beyond Compare 5永久激活解决方案:一键生成专业版密钥的完整指南
  • Sigil EPUB编辑器深度解析:从基础编辑到高级定制的完整实战手册
  • 教资科三知识点汇总|初中高中各学科重点笔记整理
  • Claude on AWS 三种路径,开发者别只看模型调用