鸿蒙原生应用 ArkUI 声明式实战:从零实现 —— 问卷设计器的四种题型与条件渲染联动
鸿蒙原生应用 ArkUI 声明式实战从零实现 —— 问卷设计器的四种题型与条件渲染联动App 21「在线问卷调查」创建 TabFunc1Tab是问卷设计器页。整页由 4 个 Builder 块拼出 Header 问卷信息标题/说明 题目类型单选/多选/评分/填空 4 种类型 题目内容条件渲染选项设置 发布按钮。本篇基于21-online-survey/entry/src/main/ets/pages/Func1Tab.ets约 122 行逐段拆解重点解析题目类型状态联动题目内容条件渲染——本系列第一个问卷设计器页面。附 4 张实机截图。一、整体结构4 个 Builder 的问卷设计器Func1Tab 是本系列第一个问卷设计器页面——用户可以创建一份问卷输入标题、选题目类型、填题目、设选项build() { Column() { this.Header() Scroll() { Column({ space: 16 }) { this.InfoCard() this.TypeCard() this.QuestionCard() this.SubmitBtn() } .width(100%) .padding({ left: D.pad, right: D.pad, top: 16, bottom: D.pad this.safeBottom 20 }) } .layoutWeight(1).scrollBar(BarState.Off).align(Alignment.Top) } .width(100%).height(100%).backgroundColor(C.bg) }4 个块按信息 → 类型 → 题目 → 发布漏斗展开InfoCard— 问卷标题 说明TypeCard— 4 种题目类型选择QuestionCard— 题目内容含选项设置的条件渲染SubmitBtn— 发布问卷与系列表单页对比App 13/14/15/16/17/19/20 都是复杂表单5-6 块 多种状态 模板App 21 是**极简单一题表单4 块 一道题 单选/多选/评分/填空 4 种类型——问卷 一题多类型**的特殊模式。项目源码开源https://gitee.com/codenestFlow/HarmonyOSHub二、Header InfoCard问卷标题与说明Header 是创建问卷单行标题与系列同款Builder Header() { Row() { Text(创建问卷) .fontSize(20).fontWeight(FontWeight.Bold).fontColor(C.text) } .width(100%).height(this.safeTop 56) .padding({ top: this.safeTop, left: D.pad, right: D.pad }) .backgroundColor(C.card) .alignItems(VerticalAlign.Bottom) }height(this.safeTop 56)padding({ top: this.safeTop })alignItems(VerticalAlign.Bottom)三件套——状态栏让出 内容靠底——系列标准 Header 写法。InfoCard是问卷信息标题 说明Builder InfoCard() { Column({ space: 12 }) { Text(问卷信息).fontSize(15).fontWeight(FontWeight.Bold).fontColor(C.text) TextInput({ placeholder: 问卷标题 }) .height(44).backgroundColor(C.cardSoft).borderRadius(D.rSm) .placeholderColor(C.textDim).placeholderFont({ size: 14 }) TextArea({ placeholder: 问卷说明 / 简介... }) .height(70).backgroundColor(C.cardSoft).borderRadius(D.rSm) .placeholderColor(C.textDim).placeholderFont({ size: 14 }) } .width(100%) .padding(14) .backgroundColor(C.card) .borderRadius(D.rLg) .border({ width: 1, color: C.stroke }) }TextInput标题 TextArea说明——注意未绑定 State没有text: this.xxx也没有onChange——InfoCard 没有联动状态——demo 简化数据只入不存——真实项目应加State titleState desc双向绑定。问卷标题占位vs App 13 请输入标题——占位 业务提示用户一看就知道这里填标题不用看标题label。问卷说明 / 简介...占位加省略号——省略号 还有更多暗示可以写长。三、TypeCard4 种题目类型选择TypeCard 是 4 种题目类型的胶囊选择单选高亮Builder TypeCard() { Column({ space: 12 }) { Text(题目类型).fontSize(15).fontWeight(FontWeight.Bold).fontColor(C.text) Row({ space: 10 }) { ForEach(this.types, (t: string) { Text(t) .fontSize(13) .fontColor(this.qType t ? #FFFFFF : C.textSub) .padding({ left: 16, right: 16, top: 8, bottom: 8 }) .backgroundColor(this.qType t ? C.primary : C.cardSoft) .borderRadius(16) .onClick(() { this.qType t; }) }, (t: string) t) } .width(100%) } .width(100%) .padding(14) .backgroundColor(C.card) .borderRadius(D.rLg) .border({ width: 1, color: C.stroke }) }4 种题目类型State qType: string 单选;private types: string[] [单选, 多选, 评分, 填空];4 种类型 问卷平台 4 大题型类型问卷平台通用名场景单选Single Choice你最喜欢的颜色多选Multiple Choice你经常使用的功能评分Rating请给本次服务打分 1-5填空Text Input你有什么建议**State qType: string 单选 默认选中单选**——onClick(() { this.qType t; })切换——**qType 一变 → QuestionCard 条件渲染重算**与 App 18 发现页selectedCat 同款分类状态联动。题目类型是问卷设计器的题型选择器——不同题型对应不同的输入组件单选Radio/选项、多选CheckBox/选项、评分星级/数字、填空TextInput——qType一变 → 题目内容区的输入组件就变。State qType: string而不是number——字符串状态直接对比可读qType 单选vsselectedType 1——业务状态用字符串更易读。四、QuestionCard题目内容条件渲染核心QuestionCard 是本系列最值得学习的条件渲染示例——根据qType决定显示哪些子组件Builder QuestionCard() { Column({ space: 12 }) { Text(题目内容).fontSize(15).fontWeight(FontWeight.Bold).fontColor(C.text) TextInput({ placeholder: 输入题目... }) .height(44).backgroundColor(C.cardSoft).borderRadius(D.rSm) .placeholderColor(C.textDim).placeholderFont({ size: 14 }) if (this.qType 单选 || this.qType 多选) { Text(选项设置).fontSize(13).fontColor(C.textSub) TextInput({ placeholder: 选项 A }) .height(40).backgroundColor(C.cardSoft).borderRadius(D.rSm) .placeholderColor(C.textDim).placeholderFont({ size: 14 }) TextInput({ placeholder: 选项 B }) .height(40).backgroundColor(C.cardSoft).borderRadius(D.rSm) .placeholderColor(C.textDim).placeholderFont({ size: 14 }) Text( 添加选项).fontSize(13).fontColor(C.primary).width(100%) .onClick(() { promptAction.showToast({ message: 添加选项 }); }) } } .width(100%) .padding(14) .backgroundColor(C.card) .borderRadius(D.rLg) .border({ width: 1, color: C.stroke }) }4.1 条件渲染本页最大亮点if (this.qType 单选 || this.qType 多选) { Text(选项设置).fontSize(13).fontColor(C.textSub) TextInput({ placeholder: 选项 A }) .height(40).backgroundColor(C.cardSoft).borderRadius(D.rSm) .placeholderColor(C.textDim).placeholderFont({ size: 14 }) TextInput({ placeholder: 选项 B }) .height(40).backgroundColor(C.cardSoft).borderRadius(D.rSm) .placeholderColor(C.textDim).placeholderFont({ size: 14 }) Text( 添加选项).fontSize(13).fontColor(C.primary).width(100%) .onClick(() { promptAction.showToast({ message: 添加选项 }); }) }if (this.qType 单选 || this.qType 多选)条件渲染——只有单选/多选显示选项设置区域评分/填空不显示——qType 决定 QuestionCard 的内容结构。4 种 qType 的不同呈现qType显示内容单选题目输入 选项 A/B 输入 添加选项多选题目输入 选项 A/B 输入 添加选项同单选评分题目输入无选项真实项目应显示 1-5 星级输入填空题目输入无选项真实项目应显示填空说明单选/多选 必有选项评分/填空 无选项——题型决定内容是问卷设计器的核心逻辑。或条件的写法qType 单选 || qType 多选——两种题型共用选项设置区——条件合并简化代码不用写else if分两支。4.2 选项的半完整实现当前只显示 A/B 两个选项 添加选项按钮——Text( 添加选项).fontSize(13).fontColor(C.primary).width(100%) .onClick(() { promptAction.showToast({ message: 添加选项 }); }) 添加选项是蓝色字——文字按钮不是 Button 组件是 Text 加 onClick——点击弹 Toast 添加选项——真实项目应动态添加 TextInput 到数组// 真实项目的动态选项 State options: string[] [选项 A, 选项 B]; addOption() { this.options.push(选项 ${String.fromCharCode(65 this.options.length)}); // 触发 ForEach 重渲染 this.options [...this.options]; }动态选项数组——State string[]数组 push 整体替换触发渲染——**动态表单字段**是问卷设计器的进阶能力。4.3 状态联动闭环qType状态联动逻辑链用户点击 TypeCard 胶囊 → qType 切换单选→评分 → build() 重执行 → QuestionCard 内的 if 重新判断 → 单选显示选项区 / 评分隐藏选项区状态驱动 UI是 ArkUI 的核心范式——qType一个状态驱动 QuestionCard 的整个结构——不需要手动操作 DOM不需要隐藏/显示if 直接控制渲染——条件渲染比显隐控制更彻底。对比 App 18 发现页App 18selectedCat/ageRange/matchScope/anonymous4 个状态各自联动对应区域App 21qType一个状态联动 QuestionCard 整个结构更彻底的状态驱动状态联动深度 状态影响 UI 结构的程度——App 21 的qType影响 5 个子组件选项设置标题、选项 A 输入、选项 B 输入、 添加选项按钮、潜在的其他选项联动深度比 App 18 更高。五、SubmitBtn发布问卷SubmitBtn 是发布按钮无校验Builder SubmitBtn() { Button(发布问卷) .width(100%).height(48) .fontSize(16).fontColor(#FFFFFF).backgroundColor(C.primary) .borderRadius(D.rMd) .onClick(() { promptAction.showToast({ message: 问卷已发布 }); }) }发布问卷48vp 高蓝底白字按钮——点击弹 Toast 问卷已发布——无校验没有检查标题/题目是否填了——真实项目应加校验标题非空题目非空单选/多选必须 ≥ 2 个选项评分/填空必须有评分范围/输入说明4 道校验是问卷发布按钮的完整校验——demo 只做了发布反馈校验需补全。六、State 的1 状态驱动大结构Func1Tab 只有1 个 StateqType题目类型——1 个状态驱动 QuestionCard 的整个结构State类型用途联动qTypestring题目类型单选/多选/评分/填空TypeCard 高亮 QuestionCard 条件渲染1 个 State 影响 5 个 UI 元素——**State 数量少但联动深**是问卷设计器的特点——比5 个 State 各自联动 1 个区域App 13 创建表单联动效率更高。对比页面State联动深度App 13 创建表单5每个联动 1-2 处App 18 发现页5每个联动 1-2 处App 21 创建问卷1联动 5 处结构级1 个 State 联动结构是更高级的状态管理——**少状态、强联动**是问卷设计器的设计哲学。七、问卷设计器的类型字段设计App 21 的types: string[]是 4 个中文类型名——真实问卷平台的题型字段通常是枚举// 真实问卷平台的题型枚举 enum QuestionType { Single single, // 单选 Multiple multiple, // 多选 Rating rating, // 评分 Text text, // 填空 }中文显示 英文存储是问卷平台的最佳实践显示用中文单选更易读存储用英文single更稳定不受本地化影响渲染时映射typeDisplayMap[type]显示中文App 21 直接用中文做状态值——demo 简化不做 i18n真实项目应分离显示/存储。八、问卷设计器 vs 系列其他表单App 21 与系列其他表单App 13/14/15/16/17/19/20的本质差异维度系列其他表单App 21 问卷设计器表单类型填空式用户填字段选择题式用户选结构状态复杂度多字段5少状态但联动深1 状态字段类型固定字段标题/描述/日期动态字段题型/选项业务通用信息录入问卷设计问卷设计器是动态表单的代表——用户根据题型选择画出不同形态的问卷——比填空式表单复杂得多一个问卷含多题每题类型不同。真实问卷平台的动态表单实现题目数组Question[]每题有自己的 type/options/rangeForEach渲染多题每题内根据type条件渲染对应组件添加题目按钮往数组 push 一题删除题目按钮从数组 splice动态表单是表单进阶——App 21 是简化版一题一问卷真实平台是多题多类型。九、问卷填写页与创建页的双向流程App 21 的创建页Func1Tab只能创建一道题——真实问卷是多题一份问卷含 5-20 题。但 App 21 的设计暗示了双向流程创建页创建问卷→ 创作者视角发布问卷数据页数据统计→ 创作者视角查看填写结果首页热门问卷→ 填写者视角参与问卷我的页我的问卷→ 创作者视角管理自己的问卷创建 → 展示 → 填写 → 数据是问卷平台的完整生态——App 21 简化了一题/一问卷的版本真实平台是多题问卷 题库复用 模板市场。创建-填写双向流程值得读者在自己产品中设计——创作者和消费者在同一 App 切换视角小红书 创建笔记/查看笔记、TikTok 创建视频/观看视频、问卷星 创建问卷/填写问卷。十、总结App 21 创建问卷页解析完毕。4 种题目类型 qType 状态联动条件渲染 动态选项区是核心组件。**1 个 State 驱动 5 个 UI 元素**是本页最大亮点——状态管理的少而深哲学。问卷设计器是动态表单的代表——App 21 简化为一题/一问卷真实平台支持多题多类型。**题型 → 输入组件 → 校验规则**的动态映射是问卷设计器的核心引擎。读者做表单类项目时借鉴qType 状态联动结构的设计——比5 个 State 各自联动更优雅、更易维护。问卷设计器还是问卷填写器的模板——同一份 UI 配置既是创建者设计问卷也是填写者回答问卷的源头——**配置驱动 UI**是问卷/表单类应用的核心架构思想。