
灯光模拟HarmonyOS应用实战-64-请选择答案为何显示成功色-用FeedbackState表达中性过程与结果“请选择答案”是一条等待用户操作的中性提示它既不代表成功也不代表失败。当前The_kemusan页面却用answerCorrecttrue初始化这条文案渲染时又把true映射到app.color.success。于是从源码关系看答题前的提示会使用成功语义的颜色资源。这个现象的根源不在某个颜色值而在状态模型只能表达真假两种结果。答题前、正在提交、已经答对、已经答错至少是四种不同阶段。一个名为answerCorrect的布尔值适合描述作答结果却不适合同时承担“当前反馈应该如何呈现”的职责。本文只讨论反馈提示不扩展到选项锁定、答案揭示或完整页面状态机。方案用FeedbackState把中性、过程、成功和错误分开再把业务语义映射到颜色、图标、文本和播报策略。当前颜色的实际 RGB、真机对比度与辅助功能效果均未在本文中验证。一、源码中待答提示和正确结果共用 trueIndex.ets的初始状态把answerMessage设为“请选择答案”同时把answerCorrect设为true。开始新的理论练习和切换下一题时也会再次写入这组值。只有用户选择答案后answerCorrect才真正表示本次答案是否正确。StateselectedAnswer:string;StateanswerMessage:string请选择答案;StateanswerCorrect:booleantrue;privatestartTheoryPractice():void{this.selectedAnswer;this.answerMessage请选择答案;this.answerCorrecttrue;}privatenextTheoryQuestion():void{this.selectedAnswer;this.answerMessage请选择答案;this.answerCorrecttrue;}这段代码说明true有两个含义答题前表示“没有错误先用正向样式”答题后表示“答案正确”。两个含义的生命周期不同却占用同一个变量。变量名让读者以为它始终是作答事实初始化逻辑却把它当成呈现开关。渲染位置进一步固定了这种复用Text(this.answerMessage).fontSize(14).fontWeight(FontWeight.Medium).fontColor(this.answerCorrect?$r(app.color.success):$r(app.color.danger))能够确认的是资源名映射关系不能从这里只读断言success在设备上一定呈现为某个具体绿色也不能断言对比度不足。资源值、主题切换、显示模式和设备渲染都需要另行查看与实测。二、真假布尔无法覆盖反馈阶段把反馈压成布尔值时所有非错误状态都会被迫挤进true或所有未完成状态都被迫挤进false。无论选哪边中性和过程态都会借用结果态的视觉含义。业务时刻文案示例是否已有结果合理语义用answerCorrect表达的问题题目刚出现请选择答案否中性只能借用true正在处理提交正在提交否过程/信息没有对应取值答案正确回答正确是成功true含义清晰答案错误回答错误是错误false含义清晰新题重置请选择答案否中性再次把结果字段改为true问题链很直接页面需要四种语义状态只有两种取值重置函数为了获得正向颜色写入true渲染器再把true当作成功最终文案与视觉含义不一致。修复时不应把初始值简单改成false。那只会让“请选择答案”使用错误色把中性从成功一侧搬到失败一侧。也不应添加第二个isNeutral布尔值因为isNeutraltrue与answerCorrectfalse等组合会制造互相矛盾的状态。三、FeedbackState 同时表达阶段和语义最小模型可以用枚举表达四种反馈语义并把稳定原因码、文案键和是否需要播报放进同一个值对象。下面是方案示例尚未写入当前工程。exportenumFeedbackTone{NEUTRALneutral,INFOinfo,SUCCESSsuccess,ERRORerror}exportenumFeedbackPhase{IDLEidle,IN_PROGRESSin_progress,SETTLEDsettled}exportinterfaceFeedbackState{tone:FeedbackTone;phase:FeedbackPhase;code:string;messageKey:string;announce:boolean;}tone回答“这条反馈的语义是什么”phase回答“业务是否已经得到终态”code用于测试和诊断messageKey用于查找文案announce则表达是否应主动提示辅助技术。四个字段不是为了堆配置而是让过去隐藏在布尔值中的决定显式化。可以先定义几个不可变常量constWAITING_FOR_ANSWER:FeedbackState{tone:FeedbackTone.NEUTRAL,phase:FeedbackPhase.IDLE,code:WAITING_FOR_ANSWER,messageKey:theory.choose_answer,announce:false};constANSWER_CORRECT:FeedbackState{tone:FeedbackTone.SUCCESS,phase:FeedbackPhase.SETTLED,code:ANSWER_CORRECT,messageKey:theory.answer_correct,announce:true};constANSWER_WRONG:FeedbackState{tone:FeedbackTone.ERROR,phase:FeedbackPhase.SETTLED,code:ANSWER_WRONG,messageKey:theory.answer_wrong,announce:true};待答状态明确为NEUTRAL IDLE因此不再伪装成成功结果。正在进行的状态可以使用INFO IN_PROGRESS但只有当页面确实存在异步提交或数据加载时才创建不能为了凑齐四种颜色虚构一个用户看不到的流程。四、业务事件先归约成唯一反馈状态页面不应在多个函数里分别改文案、布尔值、颜色和播报开关。更稳的方式是把业务事件交给一个纯归约器产出完整的FeedbackState。同一个事件只生成一个状态渲染器不再推测答案是否已经提交。exportenumTheoryFeedbackEvent{QUESTION_PRESENTEDquestion_presented,SUBMIT_STARTEDsubmit_started,ANSWER_ACCEPTEDanswer_accepted,ANSWER_REJECTEDanswer_rejected}exportfunctionreduceTheoryFeedback(event:TheoryFeedbackEvent):FeedbackState{if(eventTheoryFeedbackEvent.QUESTION_PRESENTED){returnWAITING_FOR_ANSWER;}if(eventTheoryFeedbackEvent.SUBMIT_STARTED){return{tone:FeedbackTone.INFO,phase:FeedbackPhase.IN_PROGRESS,code:ANSWER_SUBMITTING,messageKey:theory.answer_submitting,announce:false};}if(eventTheoryFeedbackEvent.ANSWER_ACCEPTED){returnANSWER_CORRECT;}returnANSWER_WRONG;}这个函数只决定语义不引用$r资源不操作 ArkUI 组件也不写历史。纯函数可以用小型输入表覆盖不需要启动页面。若以后错误结果需要携带正确选项可把参数放进独立的messageArgs不要把最终拼接文本当作状态身份。页面状态可以从三项收敛为一项Statefeedback:FeedbackStateWAITING_FOR_ANSWER;privatepresentQuestion():void{this.selectedAnswer;this.feedbackreduceTheoryFeedback(TheoryFeedbackEvent.QUESTION_PRESENTED);}privateapplyAnswerResult(correct:boolean):void{this.feedbackreduceTheoryFeedback(correct?TheoryFeedbackEvent.ANSWER_ACCEPTED:TheoryFeedbackEvent.ANSWER_REJECTED);}answerCorrect仍可作为领域结果保留在记录对象中但不再承担提示样式。一次作答事实和当前反馈视图可以来源相同却不应是同一个变量。五、颜色、图标和文案分别从语义映射有了语义状态后视觉层再做资源映射。映射函数应返回资源引用或页面可消费的展示对象而不是让每个Text自行写四段条件。interfaceFeedbackVisual{color:ResourceColor;icon:Resource;message:string;}privatetoFeedbackVisual(state:FeedbackState):FeedbackVisual{if(state.toneFeedbackTone.NEUTRAL){return{color:$r(app.color.text_secondary),icon:$r(app.media.feedback_neutral),message:this.resolveMessage(state.messageKey)};}if(state.toneFeedbackTone.INFO){return{color:$r(app.color.accent),icon:$r(app.media.feedback_info),message:this.resolveMessage(state.messageKey)};}if(state.toneFeedbackTone.SUCCESS){return{color:$r(app.color.success),icon:$r(app.media.feedback_success),message:this.resolveMessage(state.messageKey)};}return{color:$r(app.color.danger),icon:$r(app.media.feedback_error),message:this.resolveMessage(state.messageKey)};}资源名称是方案中的占位示例当前工程未必已经有这些媒体资源。实际实现可以先只调整文字颜色避免为了状态改造同时引入大量图标资产。关键是映射入口唯一后续主题或深色模式调整只改资源层不改变业务事件。颜色不能成为唯一线索。成功和错误应同时有文案或图标中性提示不应使用勾号进行中状态如果没有真实等待也不应显示旋转动画。视觉层负责清楚表达已有语义不能反过来决定业务是否成功。六、播报策略与颜色映射相互独立announce不等于“只要状态变化就朗读”。题目刚出现时页面可能已经通过标题让辅助技术获得上下文再主动播报“请选择答案”可能重复回答结果通常更值得及时提示。具体 ArkUI 辅助功能接口与参数必须按目标 SDK 文档核对本文不编造调用名称。可以先把策略写成与平台无关的决策interfaceAnnouncementDecision{shouldAnnounce:boolean;messageKey:string;}functiondecideAnnouncement(previous:FeedbackState,next:FeedbackState):AnnouncementDecision{constchangedprevious.code!next.code;constterminalnext.phaseFeedbackPhase.SETTLED;return{shouldAnnounce:changedterminalnext.announce,messageKey:next.messageKey};}这个策略把“状态变化”“已经形成结果”“该状态允许主动播报”三个条件同时检查避免页面重渲染时重复提示。同一结果如果因父组件刷新被再次赋值code没变就不重复触发。反馈状态颜色角色图标角色默认主动播报原因NEUTRAL次级文字无或中性提示否尚未产生结果INFO强调信息进度或信息视流程而定只在真实等待较长时提示SUCCESS成功资源勾选是用户需要知道作答已接受ERROR错误资源警示是用户需要知道结果与后续信息实际可访问性结论必须通过屏幕阅读、焦点顺序、动态内容提示与颜色对比度核对。源码中的资源名不能替代这些设备证据。七、理论与实操可以共用外壳但保留领域码当前页面还有message与isSuccessMessage用于灯光演示和考试反馈它们也呈现真假二分。可以复用FeedbackState这一外壳但不要把理论和实操所有原因码混成一组模糊字符串。建议用领域前缀区分理论THEORY_WAITING、THEORY_CORRECT、THEORY_WRONG。普通灯光LIGHT_DEMO、LIGHT_ACTION_CORRECT、LIGHT_TIMEOUT。科三实操PRACTICAL_DEMO、PRACTICAL_ACTION_CORRECT、PRACTICAL_TIMEOUT。仓储或加载若确有页面反馈再使用HISTORY_LOADING、HISTORY_READ_ERROR。统一的是tone/phase/code/messageKey/announce结构和呈现适配器领域判定仍由各自业务函数完成。这样既减少颜色条件复制又不会让“答题错误”和“灯光超时”失去各自诊断信息。一个适配函数可以要求领域码唯一functioncreateSettledFeedback(code:string,success:boolean,messageKey:string):FeedbackState{if(code.length0||messageKey.length0){thrownewError(feedback identity is required);}return{tone:success?FeedbackTone.SUCCESS:FeedbackTone.ERROR,phase:FeedbackPhase.SETTLED,code,messageKey,announce:true};}这不是让所有调用点继续传任意布尔值。调用方仍应先得到明确的领域结果再选择对应原因码该函数只减少重复构造。八、防止旧异步结果覆盖新题的中性状态如果答题流程将来变成异步旧题提交结果可能在用户已经进入下一题后返回。仅有FeedbackState仍不足以阻止过期回调覆盖新题的WAITING_FOR_ANSWER。状态应绑定稳定题目编号或会话令牌。interfaceScopedFeedback{questionId:string;revision:number;state:FeedbackState;}privateapplyScopedFeedback(next:ScopedFeedback):void{if(next.questionId!this.getCurrentQuestion().id){return;}if(next.revisionthis.feedbackRevision){return;}this.feedbackRevisionnext.revision;this.feedbacknext.state;}当前answerTheoryQuestion()是同步比较答案本次只读源码没有证明已经存在这种竞态。这里把它列为演进边界一旦接入网络判题、异步存储确认或延时动画就要避免旧反馈越过题目切换。重置新题时应通过一个入口同时更新selectedAnswer、题目身份和feedback。若先重置反馈、后更新题目异步回调的身份核对可能短暂使用旧题具体顺序应由单向状态更新函数固定。九、测试矩阵要覆盖语义、呈现和重复播报第一层只测reduceTheoryFeedback()第二层测toFeedbackVisual()的资源角色第三层再测页面从待答到结果再到下一题的序列。不要只截图比较颜色因为状态码与阶段同样重要。constexpectations:Array[TheoryFeedbackEvent,FeedbackTone,FeedbackPhase][[TheoryFeedbackEvent.QUESTION_PRESENTED,FeedbackTone.NEUTRAL,FeedbackPhase.IDLE],[TheoryFeedbackEvent.SUBMIT_STARTED,FeedbackTone.INFO,FeedbackPhase.IN_PROGRESS],[TheoryFeedbackEvent.ANSWER_ACCEPTED,FeedbackTone.SUCCESS,FeedbackPhase.SETTLED],[TheoryFeedbackEvent.ANSWER_REJECTED,FeedbackTone.ERROR,FeedbackPhase.SETTLED]];for(constitemofexpectations){conststatereduceTheoryFeedback(item[0]);expect(state.tone).toBe(item[1]);expect(state.phase).toBe(item[2]);}用例操作序列预期语义重点核对F01页面首次进入NEUTRAL/IDLE“请选择答案”不用成功资源F02选择正确答案SUCCESS/SETTLED文案、颜色、原因码一致F03选择错误答案ERROR/SETTLED正确答案补充不改变错误语义F04点击下一题回到NEUTRAL/IDLE不残留上一题成功或错误F05同一结果重复赋值状态不变不重复主动播报F06旧题延迟回调被身份守卫忽略新题中性反馈不被覆盖F07切换主题语义不变仅资源映射变化F08理论与实操连续操作领域码不同不把两类失败混成同一记录这些是实现后的用例设计本文没有运行它们。页面截图只能证明某一次渲染不能证明状态序列和重复回调纯函数测试、ArkUI 交互与真机辅助功能检查应分别保留结果。十、验证清单、排障表与事实边界实现后可逐项核对“请选择答案”对应NEUTRAL/IDLE。“正在提交”只在真实异步阶段使用INFO/IN_PROGRESS。正确与错误分别对应SUCCESS和ERROR。业务函数不直接选择颜色资源。视觉映射不反推答案是否正确。文案、图标和颜色来源于同一个FeedbackState。中性、信息、成功、错误都具有稳定原因码。主动播报只在状态真正变化且策略允许时触发。颜色不是结果的唯一表达线索。下一题重置不会残留上一题反馈。旧异步回调不能覆盖当前题反馈。理论与实操复用结构但原因码保持领域区分。目标 ArkTS 写法、资源引用与辅助功能接口经过文档和编译核对。深色模式、字体放大、屏幕阅读与真机视觉分别检查。症状优先核对常见原因修复方向“请选择答案”仍使用成功色初始feedback.tone旧answerCorrect仍参与渲染让提示组件只消费FeedbackState下一题仍显示“回答正确”新题重置入口只清空了选项通过统一事件写入WAITING_FOR_ANSWER文案正确但图标错误视觉映射表组件分别维护条件返回完整FeedbackVisual页面刷新重复播报前后状态比较每次重渲染都触发比较code与终态阶段等待状态一直转圈SUBMIT_STARTED结束路径失败或成功未归约为每条异步出口写终态事件实操错误显示理论原因码领域事件映射复用了固定字符串为理论、灯光和实操保留前缀真机颜色含义不清资源和辅助线索只依靠颜色区分增加准确文案、图标并做设备检查旧题结果覆盖新题题目身份与修订号延迟回调无作用域加入questionId/revision守卫当前源码能够确认的是answerMessage初始为“请选择答案”answerCorrect初始为true提示文字根据该布尔值选择success或danger资源作答后该布尔值才表示答案是否正确。选项文字在未作答时另走中性色逻辑因此本文没有把问题扩大成整个题卡都使用成功色。FeedbackState、归约器、展示对象、播报决策、领域原因码和异步作用域都属于改进方案尚未写入The_kemusan。本次没有运行项目构建没有生成新的 HAP没有启动模拟器也没有在真机验证资源颜色、主题切换、屏幕阅读或动态内容提示。最终资源名称、ArkTS 类型约束和辅助功能调用必须以目标 SDK 文档、编译结果和设备行为为准。