HarmonyOS 应用开发《掌上英语》第49篇:答题练习模式——两种答题 UI 的设计与实现
答题练习模式——两种答题 UI 的设计与实现
一、两种答题模式的场景定位
本项目中实现了两种答题页面,分别适用于不同的练习场景:
| 页面 | 文件名 | 适用场景 | 特点 |
|---|---|---|---|
| AnswerQuestionsPage | features/topicPage/…/AnswerQuestionsPage.ets | 日常练习 | 单列模式、逐题作答 |
| AnswerQuestionsTwoPage | features/topicPage/…/AnswerQuestionsTwoPage.ets | 模考/测试 | 双栏模式、倒计时、答题卡 |
两种模式共享相同的数据源和答题逻辑,但 UI 布局和交互体验各有特色。
二、AnswerQuestionsPage——日常练习模式
2.1 页面结构
build(){AnswerQuestionsComponent({isCollectionEvent:()=>{this.isCollectionEvent();},confirmAddNote:(note:string)=>{this.confirmAddNote(note);},storageWrongClick:()=>{this.storageWrongClick();},storageCollectClick:()=>{this.storageCollectClick();},viewReport:()=>{this.viewReport();},onClickBack:()=>{if(this.sourceRouterModel?.sourceType==='我的错题'||...){RouterModule.pop();}else{this.backDialogController.open();}}});}AnswerQuestionsPage 本身是一个轻量壳,将主要逻辑委托给AnswerQuestionsComponent(位于 answer_questions 组件中)。这种设计使得答题逻辑可以复用——无论是来自"我的错题"、“我的收藏"还是"日常练习”,都使用同一套答题组件。
2.2 数据初始化
aboutToAppear():void{letnavParam=RouterModule.getNavParam({url:RouterMap.ANSWER_QUESTIONS_PAGE});if(navParam!==null&&navParam!==undefined){if(navParaminstanceofQuestionsRouterModel){this.sourceRouterModel=navParamasQuestionsRouterModel;}else{letparamStr=String(navParam);if(paramStr==='1'){this.questionBankData=WORD_QUESTION_DATA;}elseif(paramStr==='2'){this.questionBankData=LISTENING_QUESTION_DATA;}elseif(paramStr==='3'){this.questionBankData=READING_QUESTION_DATA;}elseif(paramStr==='5'){this.questionBankData=GRAMMAR_QUESTION_DATA;}}}}路由参数支持两种传入方式:
- QuestionsRouterModel:带有完整题目列表的对象(从错题本、收藏进入)
- 字符串标识:
'1'单词拼写、'2'听力、'3'阅读、'5'语法
2.3 练习结束统计
viewReport(){this.recordStudyData();lettestReport:TestReportModel=newTestReportModel();testReport.ques=this.ques;testReport.quesType=1;testReport.practiceDuration=this.practiceDuration;RouterModule.replace({url:RouterMap.ANSWER_REPORT_PAGE,param:testReport});}recordStudyData(){try{lettopicItemTypeList=this.toTopicItemTypeList(this.ques);StatisticsManager.getInstance().calculateStatistics(topicItemTypeList,this.practiceDuration);letplanManager=LearningPlanManager.getInstance();letansweredCount=this.ques.filter(item=>item.isAnswer).length;planManager.recordWordStudy(answeredCount);}catch(e){Logger.error('AnswerQuestionsPage',`记录学习数据失败:${JSON.stringify(e)}`);}}练习完成后,调用StatisticsManager.calculateStatistics计算统计数据,同时更新学习计划。
三、AnswerQuestionsTwoPage——模考模式
3.1 页面结构
AnswerQuestionsTwoPage 是一个完整的独立页面,包含更多的模考专属功能:
build(){Column(){this.topBuilder();// 顶部导航this.countdownBuilder();// 倒计时this.answerPanelBuilder();// 作答面板this.bottomOperationBuilder();// 底部操作栏}.justifyContent(FlexAlign.SpaceBetween).backgroundColor($r('sys.color.comp_background_primary')).expandSafeArea([SafeAreaType.SYSTEM],[SafeAreaEdge.TOP,SafeAreaEdge.BOTTOM]);}3.2 倒计时功能
@BuildercountdownBuilder(){Row(){Image($r('app.media.ic_public_alarm_clock'));Text($r('app.string.time_remaining'));TextTimer({isCountDown:true,count:this.timerCount,controller:this.textTimerController}).format('HH:mm:ss').fontColor($r('sys.color.multi_color_09')).onTimer((_utc:number,elapsedTime:number)=>{this.practiceDuration=elapsedTime;}).onAppear(()=>{this.textTimerController.start();});}}使用 ArkUI 的TextTimer组件实现 30 分钟倒计时:
isCountDown: true启用倒计时模式count: 30 * 60 * 1000设置 30 分钟(毫秒)format: 'HH:mm:ss'显示格式onTimer回调中记录已用时间
3.3 答题卡(AnswerSheet)
@BuilderanswerSheetBuilder(){Column(){Image($r('app.media.icon_answer_sheet'));Text($r('app.string.answer_sheet'));}.bindSheet($$this.isShowAnswerSheet,this.answerSheet(),{height:560,backgroundColor:$r('sys.color.background_secondary'),showClose:false,}).onClick(()=>{this.isShowAnswerSheet=true;});}答题卡通过bindSheet实现底部弹出面板,用于快速跳转到任意题目。
3.4 左右滑动切题
.gesture(PanGesture(this.panOption).onActionUpdate((event:GestureEvent)=>{if(event){this.offsetX=this.positionX+event.offsetX;}}).onActionEnd(()=>{this.onActionEnd();}))onActionEnd(){if(this.offsetX>this.positionX){// 右滑 → 上一题this.currentIndex--;}else{// 左滑 → 下一题this.currentIndex++;}this.currentModel=this.ques[this.currentIndex];this.positionX=this.offsetX;}使用PanGesture手势识别实现左右滑动切换题目,PanDirection 设置为Left | Right。
3.5 交卷功能
nextQuestionBuilder(){Button(this.currentIndex===this.ques.length-1?$r('app.string.submit_exam'):$r('app.string.next_question')).onClick(()=>{if(this.currentIndex>=this.ques.length-1){this.submitExamDialogController.open();}else{this.currentIndex++;}});}最后一题时按钮文字变为"交卷",点击弹出确认对话框。
四、两种模式的共同点
4.1 单选逻辑
answerEvent(item:AnswerItem){this.currentModel!.isAnswer=true;this.currentModel?.selectQues.splice(0);this.currentModel?.selectQues.push(item.ans);for(letindex=0;index<this.currentModel!.ques.length;index++){letelement=this.currentModel!.ques[index];element.isSelect=(element.ans===item.ans);}}单选逻辑:清空已选、设置新选、标记选项状态。
4.2 错题记录
storageWrongClick(){if(this.currentModel.selectQues.toString()!==this.currentModel.rightQues.toString()){letcurrentItem=this.transTopicItemType();letarr:TopicItemType[]=PreferenceUtil.getInstance().get(PreferConstant.ERROR_RECORDS,[])asTopicItemType[];if(arr.length===0){arr.push(currentItem);}else{letisExit=arr.some(item=>item.keyID===currentItem.keyID);if(!isExit){arr.push(currentItem);}}PreferenceUtil.getInstance().put(PreferConstant.ERROR_RECORDS,arr);}}答错时自动将题目写入PreferConstant.ERROR_RECORDS存储。
五、选择哪种模式
| 比较维度 | AnswerQuestionsPage | AnswerQuestionsTwoPage |
|---|---|---|
| 布局方式 | 单列(组件化) | 双栏(独立页面) |
| 当前题目数量 | 由数据源决定 | 固定 30 分钟 |
| 题目导航 | 顺序作答 | 答题卡 + 滑动 |
| 时间限制 | 无 | 30 分钟倒计时 |
| 生词本 | 支持 | 支持 |
| 交卷方式 | 手动报告 | 弹框确认交卷 |
前者适合日常碎片化学习,后者适合模拟考试场景。两种模式的并存,满足了用户"学"和"测"的不同需求。