灯光模拟HarmonyOS应用实战-10-科二灯光基础如何按图标、上车检查、夜间场景切换

科二灯光基础覆盖图标认知、上车检查、夜间场景。源码用 subject2Category 和 CategoryChip 控制筛选条件。
本文只看科二分类练习。科二灯光基础被拆成图标认知、上车检查、夜间场景三类,当前分类直接决定抽题范围。
先看这篇要解决的具体问题
科二分类 的核心不是某个 ArkTS 语法点,而是边界划分。一个练习功能至少有四个层面:入口从哪里来,数据在哪里定义,用户动作怎样改变状态,结果是否保存。只看 UI 会漏掉判题,只看题库会漏掉重置,只看存储又解释不了按钮为什么这样设计。
更稳的做法是把本文的功能拆成下面六步:默认图标认知 -> 点击分类 Chip -> 更新分类状态 -> 重启科二练习 -> 按 category 过滤题 -> 答题写记录。后面所有代码片段都服务于这条线,不额外展开无关模块。

源码边界和文件分工
| 源码位置 | 作用 |
|---|---|
entry/src/main/ets/pages/Index.ets |
页面状态、Builder、入口分发和业务处理 |
entry/src/main/ets/models/QuestionBank.ets |
模块入口、题库、按钮动作和命令定义 |
这些节选不是为了贴完整文件,而是为了把当前链路看清楚。
Index.ets 维护 subject2Category 并用 Chip 切换,QuestionBank.ets 的科二种子提供分类题源,selectQuestions 执行过滤。
源码路径:D:\ProgramData\huawei\lesson\The_kemusan
阅读顺序:入口 -> 数据 -> 状态 -> UI -> 本地记录
写作边界:只解释当前源码能证实的功能,不替代真机运行和发布验证
subject2Category 决定当前题池
源码节选:entry/src/main/ets/pages/Index.ets:370
@Builder
SubjectTwoTabs() {
Row({ space: 8 }) {
this.CategoryChip('图标认知')
this.CategoryChip('上车检查')
this.CategoryChip('夜间场景')
}
.width('100%')
.padding({ left: 20, right: 20 })
.margin({ bottom: 12 })
}
@Builder
CategoryChip(label: string) {
Text(label)
.fontSize(14)
.fontWeight(FontWeight.Medium)
.fontColor(this.subject2Category === label ? Color.White : $r('app.color.text_secondary'))
.textAlign(TextAlign.Center)
.padding({ left: 12, right: 12, top: 8, bottom: 8 })
.backgroundColor(this.subject2Category === label ? $r('app.color.accent') : $r('app.color.panel_bg'))
.borderRadius(16)
.border({ width: 1, color: $r('app.color.border_subtle') })
.layoutWeight(1)
.onClick(() => {
this.subject2Category = label;
this.startTheoryPractice(SUBJECT_TWO, '科二灯光基础', label);
})
}
源码节选:entry/src/main/ets/models/QuestionBank.ets:408
const SUBJECT_TWO_SEEDS: GeneratedQuestionSeed[] = [
{
category: '图标认知',
scenario: '看到仪表盘绿色近光灯图标',
correct: '确认近光灯处于开启状态',
wrongOne: '判断远光灯处于开启状态',
wrongTwo: '判断双闪处于开启状态',
wrongThree: '认为灯光系统故障',
explanation: '科二基础认知需要能区分近光、远光、雾灯和双闪图标。'
},
{
category: '图标认知',
scenario: '看到两个转向指示箭头同时闪烁',
correct: '通常表示危险报警闪光灯开启',
wrongOne: '表示远光灯开启',
wrongTwo: '表示近光灯关闭',
wrongThree: '表示制动灯开启',
explanation: '左右转向指示同时闪烁一般对应危险报警闪光灯。'
},
{
category: '图标认知',
scenario: '识别雾灯图标时',
correct: '注意前后雾灯图标方向和颜色差异',
wrongOne: '只看车速表指针',
wrongTwo: '把雾灯图标都当作远光灯',
wrongThree: '不需要识别雾灯图标',
explanation: '雾灯在恶劣天气中很关键,应能通过仪表图标确认状态。'
},
{
category: '上车检查',
scenario: '上车准备开始灯光项目',
correct: '先确认灯光开关和仪表状态',
wrongOne: '直接开启所有灯光',
wrongTwo: '忽略灯光状态直接起步',
wrongThree: '只检查座椅不看仪表',
explanation: '上车检查要先确认车辆初始状态,避免带灯或误灯操作。'
},
{
category: '上车检查',
scenario: '发现远光灯处于开启状态',
correct: '切回近光或关闭不必要灯光',
wrongOne: '保持远光灯继续练习',
wrongTwo: '开启左转向灯代替处理',
wrongThree: '开启车内照明灯即可',
explanation: '远光误开会影响安全,应及时切换到正确状态。'
},
{
category: '上车检查',
scenario: '准备模拟夜间灯光考试',
correct: '听到指令后再按场景操作',
wrongOne: '提前连续点击多个灯光按钮',
wrongTwo: '只开启双闪等待',
wrongThree: '不看指令随意操作',
explanation: '灯光考试重在听清指令,抢答和乱点容易造成错误。'
},
{
category: '夜间场景',
scenario: '夜间起步前需要确认车辆被看见',
correct: '按指令开启前照灯或近光灯',
wrongOne: '只开启远光灯照射前方',
wrongTwo: '不开灯直接起步',
wrongThree: '只开启倒车灯',
explanation: '夜间起步应保证车辆照明状态正确,不能误用远光。'
},
{
category: '夜间场景',
scenario: '夜间通过人行横道前',
correct: '减速观察并按要求远近光交替示意',
wrongOne: '关闭灯光快速通过',
wrongTwo: '只开启雾灯',
wrongThree: '持续使用远光灯不变',
explanation: '夜间通过人行横道应减速礼让,灯光示意用于提醒。'
},
{
category: '夜间场景',
scenario: '雨雪天视线变差时',
correct: '开启近光灯并提高可见性',
wrongOne: '关闭灯光避免耗电',
wrongTwo: '只使用远光灯',
wrongThree: '只开转向灯行驶',
explanation: '雨雪天气应使用合适灯光提高车辆可见性。'
}
];
科二题不是简单混合题池。图标认知和夜间场景训练目标不同,如果混在一起,用户很难集中补短板。
subject2Category 是题池输入,不只是 UI 选中状态。点击 Chip 后必须重新调用 startTheoryPractice。
分类 Chip 触发重新抽题
源码节选:entry/src/main/ets/pages/Index.ets:1116
private startTheoryPractice(subject: string, mode: string, category: string): void {
this.activeSubject = subject;
this.activeMode = mode;
this.activeQuestions = this.selectTestQuestions(subject, category);
this.activeQuestionIndex = 0;
this.selectedAnswer = '';
this.answerMessage = '请选择答案';
this.answerCorrect = true;
}
源码节选:entry/src/main/ets/pages/Index.ets:1200
private selectQuestions(subject: string, category: string): QuestionItem[] {
const source = this.getAllQuestions();
const result: QuestionItem[] = [];
for (let index = 0; index < source.length; index++) {
const question = source[index];
if (question.subject === subject && (category.length === 0 || question.category === category)) {
result.push(question);
}
}
return result;
}
private selectTestQuestions(subject: string, category: string): QuestionItem[] {
const source = this.selectQuestions(subject, category);
const shuffled: QuestionItem[] = [];
for (let index = 0; index < source.length; index++) {
shuffled.push(source[index]);
}
for (let index = shuffled.length - 1; index > 0; index--) {
const target = Math.floor(Math.random() * (index + 1));
const current = shuffled[index];
shuffled[index] = shuffled[target];
shuffled[target] = current;
}
const result: QuestionItem[] = [];
const maxCount = Math.min(THEORY_TEST_COUNT, shuffled.length);
for (let index = 0; index < maxCount; index++) {
result.push(shuffled[index]);
}
return result;
}
分类 Chip 的职责很清楚:更新分类状态,然后用该分类重启科二练习。它不需要自己过滤题目。
真正的过滤发生在 selectQuestions:科目要等于 subject2,分类为空或等于当前分类才进入结果。

过滤逻辑必须同时看科目和分类
源码节选:entry/src/main/ets/pages/Index.ets:1266
private answerTheoryQuestion(answer: string): void {
if (this.selectedAnswer.length > 0 || this.activeQuestions.length === 0) {
return;
}
const question = this.getCurrentQuestion();
const correctAnswer = getQuestionAnswer(question);
const correct = answer === correctAnswer;
const correctText = this.getOptionText(question, correctAnswer);
const selectedText = this.getOptionText(question, answer);
this.selectedAnswer = answer;
this.answerCorrect = correct;
this.answerMessage = correct ? '回答正确' : `回答错误,正确选项:${correctAnswer}. ${correctText}`;
this.addSimpleRecord(question.subject, this.activeMode, correct, correct ? `答对:${question.title}` : `答错:${question.title}`,
correct ? '' : question.id, correct ? '' : correctText, correct ? '' : selectedText);
}
分类状态会影响 activeQuestions、当前题索引、已选答案和反馈文案。切换分类时这些状态都要回到新题池的初始状态。
迁移时把分类作为抽题参数,而不是作为显示标签。这样“重新开始”和“下一题”都会留在同一分类里。
迁移到自己的 HarmonyOS 项目
迁移顺序建议保持保守:先抽模型,再接入口,然后接页面,最后接本地记录。不要一上来就把所有 Builder、题库和存储混在一个文件里。当前主题 科二分类 可以按下面的最小切片实现:
1. 定义模型:Subject、QuestionItem、PracticeRecord、action 常量。
2. 定义数据:模块入口、题库、灯光命令或历史记录结构。
3. 定义状态:当前页面、当前题、当前灯光、反馈文案和记录列表。
4. 定义 UI:状态行、题面、按钮区、历史卡片或搜索结果。
5. 定义验证:正确流、错误流、重置流和边界流都要能复现。
分类输入稳定后,后续增加“灯光开关位置”或“仪表提示”也只是新增分类和题源。
验证路线
- 进入科二默认图标认知
- 切分类后题目随之变化
- 下一题不跳到其他分类
除了上面的业务核对,还建议记录一个最小验证表:
主题:科二分类
输入:入口点击、按钮点击、题目选择或关键词输入
状态:currentPage、lightState、timeLeft、selectedAnswer、records
输出:页面提示、状态高亮、历史记录、搜索结果
边界:返回首页、重置、重复点击、空记录、超时
这里的验证是面向源码链路的核对,不等同于真机兼容性、性能或上架流程。如果要证明设备行为,还需要单独构建安装并操作页面。
常见问题与处理方式
| 现象 | 常见原因 | 处理方式 |
|---|---|---|
| 页面能点但结果不对 | 按钮 action 与题库 action 没有对齐 | 先看模型常量,再看题库命令和处理函数 |
| 返回后状态残留 | 计时器或闪烁器没有释放 | 核对 stopTimer、stopAltFlash 和 reset 方法 |
| 历史记录不完整 | 写记录时只保存了展示文案 | 补齐 subject、mode、correctOption、selectedOption |
科二题不变时先查 Chip 点击是否重新抽题,再查题源的 category 文案是否和筛选条件完全一致。
小结
科二分类 这条线把灯光模拟从“能点按钮”推进到“可练习、可判题、可复盘”。The_kemusan 的代码没有把所有能力做成复杂框架,而是用清晰的模型、页面状态和本地记录完成闭环。写 CSDN 文章时,把这条闭环讲清楚,比堆更多 API 名词更有价值。
](https://i-blog.csdnimg.cn/direct/ec4a4f74b93a4a018066e9706ef9fe32.png#pic_center)

更多推荐

所有评论(0)