news 2026/9/9 20:13:12

鸿蒙原生应用 ArkUI 声明式实战:从零实现 —— 问卷设计器的四种题型与条件渲染联动

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
鸿蒙原生应用 ArkUI 声明式实战:从零实现 —— 问卷设计器的四种题型与条件渲染联动

鸿蒙原生应用 ArkUI 声明式实战:从零实现 —— 问卷设计器的四种题型与条件渲染联动

App 21「在线问卷调查」创建 Tab(Func1Tab),是问卷设计器页。整页由 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 个块按"信息 → 类型 → 题目 → 发布"漏斗展开

  1. InfoCard— 问卷标题 + 说明
  2. TypeCard— 4 种题目类型选择
  3. QuestionCard— 题目内容(含选项设置的条件渲染
  4. 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 title+@State desc双向绑定

"问卷标题"占位vs App 13 "请输入标题"——"占位 = 业务提示"(用户一看就知道"这里填标题",不用看"标题"label)。"问卷说明 / 简介..."占位加省略号——"省略号 = 还有更多"(暗示可以写长)。

三、TypeCard:4 种题目类型选择

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 18:selectedCat/ageRange/matchScope/anonymous4 个状态各自联动对应区域
  • App 21:qType一个状态联动 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 "问卷已发布"——"无校验"(没有检查标题/题目是否填了)——真实项目应加校验

  1. 标题非空
  2. 题目非空
  3. 单选/多选必须 ≥ 2 个选项
  4. 评分/填空必须有评分范围/输入说明

"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/range
  • ForEach渲染多题
  • 每题内根据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"**是问卷/表单类应用的核心架构思想。

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/9/9 20:10:16

基于SpringBoot的职称评审管理系统设计与实战

每年一到职称评审季,人事部门就像打仗一样,纸质材料堆成山、专家评审靠传递、进度追踪靠微信,光收集、整理、分发申报材料就得折腾大半个月。我做过的这个基于SpringBoot的职称评审管理系统,就是为了把整个申报到评审的流程搬上线…

作者头像 李华
网站建设 2026/9/9 20:09:45

深入理解 invokedynamic:JVM 动态链接协议如何支撑多语言生态

我第一次在javap -v的输出里看到invokedynamic,是在研究 Java 8 Lambda 的字节码。习惯了invokevirtual、invokestatic这类一眼能看出调用目标的指令后,突然看到一条“运行时再决定”的指令,第一反应是奇怪。后来才意识到,这条指令…

作者头像 李华
网站建设 2026/9/9 20:08:50

从零实现Java五子棋:核心算法与界面编程实战

简介:面向Java开发者的五子棋游戏实战教程资源包,覆盖棋盘设计、棋子逻辑、玩家交互、AI算法与图形界面构建等核心环节,适合希望通过完整项目入门游戏开发的编程学习者。压缩包共65个文件,包含工程源码、可执行程序、音频与位图素…

作者头像 李华
网站建设 2026/9/9 20:08:42

探访绵阳厨房收纳源头工厂:定制橱柜的板材、五金与动线避坑指南

1. 跑去绵阳看收纳厂,最初只因一个柜子返工三次要不是因为家里厨房的柜子返工三次,我不会专程跑去绵阳看这家厨房收纳厂。上一次装修,定制橱柜装完就出了状况:抽屉面板装歪,怎么调都有半毫米错位;转角柜门打…

作者头像 李华
网站建设 2026/9/9 20:06:37

AI时代程序员两条路:造系统,还是造垃圾?

Cursor首席设计师最近抛了一个挺扎心的判断:AI时代,程序员只剩两条路,造系统,或者造垃圾。这句话这两天在不少技术群里被转疯了,有人焦虑,有人不服,也有人觉得就是标题党。我做了十几年开发&…

作者头像 李华
网站建设 2026/9/9 20:06:19

报障、事件、问题别混淆:从半年6次报障看运维问题管理落地

同一家门店,半年报障6次,每一张事件单都按流程关闭了,可到了第七次故障发生时,我们翻历史记录才发现,所谓“处理完”不过是一次又一次地重启、重置、换线。这个场景在运维圈里太常见了:报障有人接&#xff…

作者头像 李华