鸿蒙原生开发——从零构建趣味问答
一、引言问答Quiz是人类最古老的知识测试形式——从科举考试到电视智力竞赛从驾照科目一到社交网络上的性格测试问答的核心公式从未改变给出一个问题提供若干选项选出正确答案最后看分数。从技术角度看趣味问答是一个三阶段状态机开始界面 → 答题循环 → 结果展示。与井字棋回合制博弈和记忆翻牌实时翻牌延时不同问答的交互是线性推进的——每道题的选择→反馈→下一题10 道题构成一条不可逆的直线。这种线性结构简化了状态管理但带来了新的挑战即时反馈的视觉呈现绿色正确/红色错误、自动推进的节奏控制1.5s 等待、以及最终评级的设计A-D 四个等级。本文用 ArkUI 从零构建一个趣味问答应用包含 10 道涵盖编程与常识的选择题、即时正误反馈、自动推进下一题、计时计分和 A-D 评级。整个应用是一个三页面的条件渲染——开始页、答题页和结果页共享同一个build()方法通过quizStarted和quizFinished两个布尔变量切换。阅读完本文你将能够用条件渲染实现三阶段页面切换开始 / 答题中 / 结果管理问答状态机选题 → 反馈 → 自动推进实现即时正误反馈绿色/红色高亮1.5s 后自动下一题用进度条 ABCD 评级展示答题成绩用 Fisher-Yates 洗牌随机化题目顺序二、应用设计2.1 三阶段状态机应用有三个互斥的界面阶段开始界面 答题界面 结果界面 ┌──────────┐ ┌──────────┐ ┌──────────┐ │ 图标 │ 点击 │ 第 3/10 │ 答完 │ 图标 │ │ 趣味问答 │ ─────→ │ 问题... │ ─────→ │ 答对 8/10 │ │ 开始答题 │ │ A. 选项 │ │ 等级 B │ │ │ │ B. 选项 │ │ 用时 1:23 │ │ │ │ C. 选项 │ │ 再来一次 │ │ │ │ D. 选项 │ │ │ └──────────┘ └──────────┘ └──────────┘用两个布尔State变量控制StatequizStarted:booleanfalse;// 是否已开始StatequizFinished:booleanfalse;// 是否已结束三种界面用if / else if / else条件渲染if(!this.quizStarted!this.quizFinished){// 开始界面}elseif(this.quizFinished){// 结果界面}else{// 答题界面}两个布尔变量的组合产生三种有效状态。第四种组合quizStartedtrue且quizFinishedtrue不应该出现但即使出现也会走到结果界面——这是一种防御性设计。2.2 交互流程一局完整的问答包含 4 个交互点开始答题点击开始答题按钮 → 初始化题库、打乱顺序、启动计时选择答案点击 A/B/C/D 其中一个选项 → 即时显示正误反馈自动推进1.5s 后自动跳到下一题如果是最后一题则进入结果页再来一次查看成绩和评级后点击再来一次重新开始注意自动推进不是用户主动触发的——它是setInterval驱动的被动行为。这意味着在选择答案后用户有 1.5s 的时间观察反馈哪道对了、哪道错了、正确答案是什么然后无操作地进入下一题。这种设计给了用户喘一口气的时间同时保持了答题的流畅节奏。三、题库设计3.1 Question 接口每道题的数据结构interfaceQuestion{question:string;// 题目文字options:string[];// 四个选项answer:number;// 正确答案的索引0-3}answer是正确选项在options数组中的索引。这个设计比存储正确选项的文字更健壮——因为索引不会随着选项顺序的调整而变化且比较时只需要一次整数相等判断optIdx q.answer。3.2 题库与答案分布题库包含 10 道题涵盖编程、科学、常识三个领域constQ_BANK:Question[][{question:鸿蒙系统的开发语言是,options:[ArkTS,Java,Swift,Kotlin],answer:0},{question:typeof null 在JavaScript中的结果是,options:[null,object,undefined,boolean],answer:1},{question:HTTP状态码 404 表示什么,options:[服务器错误,重定向,资源未找到,请求成功],answer:2},// ... 共 10 题];一个细微但重要的设计点正确答案的位置不是全部放在 A索引 0而是分散在 A/B/C/D 四个位置。题库中 answer 的分布为 0、0、1、2、0、1、0、1、0、1——不均匀但分散确保用户不会形成总是选 A的肌肉记忆。3.3 题目随机化每次点击开始答题时使用 Fisher-Yates 洗牌算法打乱题目顺序startQuiz():void{constpool[...Q_BANK];this.shuffle(pool);this.questionspool;// ...}shuffle(arr:Question[]):void{for(letiarr.length-1;i0;i--){constjMath.floor(Math.random()*(i1));consttmparr[i];arr[i]arr[j];arr[j]tmp;}}洗牌只打乱题目的出现顺序不打乱每道题内的选项顺序。这是因为选项顺序在题库设计中已经做好了分散A/B/C/D 四个位置的答案分布不同再打乱选项会导致用户困惑——例如看到A. 太平洋和B. 大西洋时太平洋是正确答案但换了顺序后A. 大西洋和B. 太平洋会让熟悉题目顺序的用户选错尽管答案仍然是太平洋对应的新位置。因此选项顺序保持题库中的定义题目顺序随机化以增加重复玩耍的趣味性。四、答题交互4.1 selectOption 方法用户点击某个选项后执行以下逻辑selectOption(optIdx:number):void{if(this.selectedOption!-1)return;// 已选择忽略重复点击constqthis.questions[this.currentIdx];if(optIdxq.answer)this.score;// 正确则加分this.selectedOptionoptIdx;// 记录选择触发反馈 UIthis.feedbackTimerIdsetInterval((){clearInterval(this.feedbackTimerId);this.feedbackTimerId-1;this.selectedOption-1;// 清除反馈状态if(this.currentIdxthis.questions.length-1){this.currentIdx;// 下一题}else{clearInterval(this.timerId);// 最后一题 → 结束this.timerId-1;this.quizFinishedtrue;}},1500);}整个方法的逻辑可以分为四个阶段阶段一守卫selectedOption ! -1意味着用户已经做出了选择正在进行 1.5s 的反馈展示。在此期间点击任何选项都被忽略防止改答案或快速连点跳过。阶段二计分比较optIdx q.answer如果选中了正确选项则score。这个比较只在用户首次点击时执行一次。阶段三反馈设置selectedOption optIdx触发 UI 的颜色变化。此时所有四个选项变为正确选项绿底、错误选择红底如果选错、其余灰色。阶段四推进setInterval在 1.5s 后清除反馈状态并推进到下一题。使用setInterval而非setTimeout是基于记忆翻牌的经验教训——setInterval在 ArkTS 中经过验证可靠。4.2 即时反馈反馈期间的选项颜色由optionBg()方法动态计算optionBg(optIdx:number):string{if(this.selectedOption-1)return#F5F5FA;// 未选择浅灰constcorrectthis.questions[this.currentIdx].answer;if(optIdxcorrect)return#C8E6C9;// 正确浅绿if(optIdxthis.selectedOptionoptIdx!correct)return#FFCDD2;// 选错浅红return#E8E8EE;// 其余更浅灰}三种反馈颜色传递的信息选项状态背景色文字色边框含义正确答案#C8E6C9浅绿#2E7D32深绿绿色边框“这是对的”选错答案#FFCDD2浅红#C62828深红红色边框“你选了这个但它不对”未选错误#E8E8EE浅灰#BBBBBB无“无关选项”这种三色反馈同时达到了两个目的(1) 让用户知道自己是否答对(2) 如果答错让用户知道正确答案是什么。1.5s 的展示时间足够用户消化这个信息。4.3 选项标签每个选项前有一个圆形标签A/B/C/D在反馈阶段也跟随变色Text(label).width(24).height(24).borderRadius(12).backgroundColor(this.selectedOption-1?#E8E8EE:// 未选灰圆(oicorrect?#C8E6C9:// 正确绿圆(oithis.selectedOption?#FFCDD2:#E8E8EE)))// 选错红圆.textAlign(TextAlign.Center)圆标签的引入不仅仅是为了美观——它将四个选项从无差别文字块变成A/B/C/D 四个可区分的实体降低了用户的视觉搜索成本。用户可以在扫一眼之后直接记住答案是 C而不需要重读选项文字来确认位置。五、计时与计分5.1 计时器计时从开始答题按钮被点击时启动在最后一题的反馈结束后停止// startQuiz() 中this.timerIdsetInterval((){this.elapsedSec;},1000);// selectOption() 中最后一题反馈后clearInterval(this.timerId);计时器只在答题阶段运行不在开始界面和结果界面运转。这样用户可以在开始界面从容阅读说明在结果界面仔细查看成绩而不必担心计时器空转。5.2 进度条页面顶部有一个细长的进度条实时反映答题进度Row(){Row(){}.width(${(this.currentIdx/this.questions.length)*100}%).height(4).backgroundColor(#667eea).borderRadius(2)}.width(100%).height(4).backgroundColor(#E8E8EE).borderRadius(2)外层的灰色Row是进度条背景内层的蓝色Row是填充部分。填充宽度使用百分比字符串动态计算——currentIdx / questions.length * 100。例如第 5 题时进度为 5/10 50%。进度条的上方还有三个信息项当前题号第 X/10 题、用时MM:SS和当前得分✓ X形成一个紧凑的信息行。5.3 评级系统结果页面使用 A-D 四个等级评价成绩grade():string{constpctthis.score/this.questions.length;if(pct0.9)returnA;if(pct0.7)returnB;if(pct0.5)returnC;returnD;}gradeColor():string{constpctthis.score/this.questions.length;if(pct0.9)return#52C41A;// A → 绿色if(pct0.7)return#1677FF;// B → 蓝色if(pct0.5)return#FAAD14;// C → 黄色背景用非白字叠底return#FF4D4F;// D → 红色}等级跨度A90%9-10 题正确卓越B70-89%7-8 题正确良好C50-69%5-6 题正确及格D50%0-4 题正确继续努力等级用 48sp 大号粗体字展示颜色随等级变化。下方有一个水平进度条填充宽度等于正确率百分比颜色与等级颜色一致——在分数和视觉上双重呈现成绩。注意 C 级使用#FAAD14黄色但仅用于评级文字和进度条不作为白色文字的叠加背景——遵循项目中黄色不给白字做背景的颜色规则。六、UI 设计6.1 开始界面开始界面是一个垂直居中的介绍页包含64sp 图标大号 emoji营造轻松氛围趣味问答标题两句说明文字“10 道题测试你的知识储备” “涵盖编程、科学、常识等多个领域”开始答题按钮紫色#667eea界面极简、无干扰——用户只有一个操作点击开始答题。这是一个经典的单按钮启动页设计减少初次用户的选择成本。6.2 答题界面答题界面从上到下依次为第 3/10 题 01:23 ✓ 2 ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ ← 进度条 光年是什么的单位 A 距离 ← 四个选项 B 时间 C 速度 D 质量问题文字使用 18sp 粗体独占一行与选项之间有 24vp 的间距——确保问题和选项在视觉上明确分开。选项使用全宽卡片布局点击区域覆盖整个卡片而非仅文字减少误触。6.3 结果界面结果界面以垂直居中方式展示表情图标 8 分 / 5-7 分 / 0-4 分答题完成标题答对数字“答对 X / 10 题”48sp 彩色评级字母用时统计百分比进度条再来一次按钮表情图标的选择是一个细节高分用 庆祝、中等用 鼓励、低分用 加油不使用任何带有贬低或负面含义的图标。这种总是鼓励的反馈风格让用户即使得分不高也愿意再试一次。七、完整代码结构QuizPage (~270 行) ├── 数据定义 │ ├── Question — 题目接口question / options / answer │ ├── Q_BANK[10] — 题库 │ └── LABELS[4] — 选项标签 [A,B,C,D] ├── 状态变量 │ ├── State questions — 当前局的题目已洗牌 │ ├── State currentIdx / score / elapsedSec — 答题进度 │ ├── State quizStarted / quizFinished — 阶段控制 │ └── State selectedOption — 反馈状态-1未选 ├── 游戏逻辑 │ ├── startQuiz() — 洗牌 初始化 启动计时 │ ├── selectOption() — 选题 → 计分 → 反馈 → 推进 │ └── shuffle() — Fisher-Yates 洗牌 ├── 视觉辅助 │ ├── optionBg() — 选项背景色灰/绿/红 │ ├── optionTextColor() — 选项文字色 │ ├── grade() — A-D 等级 │ └── gradeColor() — 等级颜色 ├── 视图三阶段条件渲染 │ ├── 开始界面 — 图标 说明 开始按钮 │ ├── 答题界面 — 进度条 问题 四选项ForEach │ └── 结果界面 — 等级 分数 进度条 再来一次 └── 生命周期 └── aboutToDisappear() — 清理计时器和反馈定时器八、总结本文从零构建了一个趣味问答应用。与前两篇游戏类文章记忆翻牌、井字棋不同趣味问答不是一款游戏——它是教育测试工具每道题的反馈都有学习的目的。从技术角度看它也是三阶段条件渲染的典型示例——两个布尔变量切换三个完整界面每个界面的布局、交互和信息密度都截然不同。核心要点回顾三阶段状态机用quizStarted和quizFinished两个State布尔变量控制开始 → 答题 → 结果三阶段的if/else if/else条件渲染。两个变量产生 2²4 种组合其中 3 种有效第四种防御性兜底。即时三色反馈选中答案后正确选项绿底绿字、错误选择红底红字、其余灰色——三色系统同时传达正确答案是什么和你选了什么两个信息。1.5s 反馈时长给用户充足的学习时间又不打断答题节奏。自动推进setInterval(fn, 1500)在反馈期结束后自动调用下一题或结束。selectedOption从 -1 变为选择索引再变回 -1驱动 UI 在可交互和反馈展示之间切换。重复点击守卫if (this.selectedOption ! -1) return防止反馈期间改答案。Fisher-Yates 洗题每次开始前打乱题目顺序但保持选项内部顺序不变——洗题目增加重玩性不洗选项避免混淆。这是与密码生成器和记忆翻牌中同一算法的第三次应用。A-D 评级 进度条4 级评级90%→A, 70%→B, 50%→C, 其余→D四种等级颜色绿/蓝/黄/红。水平进度条用百分比宽度可视化成绩。C 级的黄色仅用于文字和进度条不给白字做背景。总是鼓励的结果设计高分 、中等 、低分 ——三个表情符号覆盖全部等级没有一个暗示失败。这种正向引导的结果展示降低了用户放弃的可能性增加了再来一次的点击率。趣味问答是三道选择题和一段说明文字构成的最小知识测试单元但在这个 270 行的 ArkUI 实现中它包含了题库管理、随机化、即时反馈、自动推进、计时计分和评级展示的完整流水线。它是教育类应用的微型原型也是三阶段条件渲染的集中示范。