文章目录
- 一、目标
- 二、基础搭建
- 2.1 定义数据
- 2.2 mock数据
- 2.3 主页面布局
- 2.3.1 布局规划
- 2.3.2 标题栏
- 2.3.3 进度条
- 2.3.4 答题模块
- 2.3.5 底部按钮
- 2.4 主页面逻辑
- 2.4.1 加载数据及定义变量
- 2.4.2 上一题、下一题
- 三、选项点击及高亮
- 3.1 声明对象及变量
- 3.2 给选项注册点击事件
- 3.3 处理背景和文本颜色
- 四、小结
一、目标
二、基础搭建
2.1 定义数据
// 题目
export interface ExamItem {
id: number
title: string
options: OptionItem[]
}
// 答案
export interface OptionItem {
letter: string
content: string
}
2.2 mock数据
export const mockExamDataList: ExamItem[] = [
{
id: 1,
title: 'Android系统的构建系统叫什么名字?',
options: [
{ letter: 'A', content: 'Gradle' },
{ letter: 'B', content: 'Maven' },
{ letter: 'C', content: 'Ant' },
{ letter: 'D', content: 'Make' },
],
},
{
id: 2,
title: '以下哪个组件不是Android架构的一部分?',
options: [
{ letter: 'A', content: 'Activity(活动)' },
{ letter: 'B', content: 'Service(服务)' },
{ letter: 'C', content: 'Content Provider(内容提供者)' },
{ letter: 'D', content: 'Fragment(片段)' },
],
},
{
id: 3,
title: 'Android中的RecyclerView控件有什么用途?',
options: [
{ letter: 'A', content: '显示一个可滚动的元素列表' },
{ letter: 'B', content: '在不同活动之间导航' },
{ letter: 'C', content: '播放视频内容' },
{ letter: 'D', content: '绘制自定义的形状和路径' },
],
},
];
2.3 主页面布局
2.3.1 布局规划
将主页面布局抽取,封装对应如下:
2.3.2 标题栏
@Builder
getTitleBar() {
Stack({ alignContent: Alignment.Start }) {
Image($r('app.media.ic_left_arrow'))
.width(24)
Text('在线考试')
.width('100%')
.textAlign(TextAlign.Center)
}
.padding({ left: 12, right: 12 })
.width('100%')
.height(52)
.backgroundColor(Color.White)
.borderWidth({
bottom: 0.5
})
.borderColor('#e5e5e5')
}
2.3.3 进度条
@Builder
getProgressView() {
Row() {
Progress({ value: this.currentIndex + 1, total: this.questionList.length })
.padding({ left: 12, right: 12 })
Text() {
Span(`${this.currentIndex + 1}`)
.fontColor(Color.Black)
Span('/' + this.questionList.length)
.fontColor(Color.Gray)
}
.layoutWeight(1)
}
.width('100%')
}
2.3.4 答题模块
Column() {
Column({ space: 5 }) {
Text(this.currentQuestion.title)
.margin({ bottom: 6, top: 12 })
ForEach(this.currentQuestion.options, (item: OptionItem) => {
Row() {
Text(item.letter + '. ')
Text(item.content)
}
.width('100%')
.height(40)
.padding({ left: 12 })
.backgroundColor('#F9F9F9')
})
}
.alignItems(HorizontalAlign.Start)
.width('100%')
.padding({ left: 15, right: 15 })
}.layoutWeight(1)
2.3.5 底部按钮
@Builder
getBottomView() {
Row() {
Row({ space: 3 }) {
Image($r('app.media.ic_arrow_left'))
.width(15)
.fillColor(this.getPreEnable() ? Color.Black : '#BABABA')
Text('上一题')
.fontColor(this.getPreEnable() ? Color.Black : '#BABABA')
}
.onClick(() => {
this.onPreClick()
})
Row({ space: 3 }) {
Text('下一题')
.fontColor(this.getNextEnable() ? Color.Black : '#BABABA')
Image($r('app.media.ic_arrow_right'))
.width(15)
.fillColor(this.getNextEnable() ? Color.Black : '#BABABA')
}
.onClick(() => {
this.onNextClick()
})
}
.width('100%')
.justifyContent(FlexAlign.SpaceBetween)
.backgroundColor(Color.White)
.height(50)
.padding({
left: 12, right: 12
})
.border({
color: '#e5e5e5',
width: { top: 1 }
})
}
2.4 主页面逻辑
2.4.1 加载数据及定义变量
// 题目列表
@State questionList: ExamItem[] = []
// 当前显示第N题
@State currentIndex: number = 0
// 当前题目
@State currentQuestion: ExamItem = {} as ExamItem
aboutToAppear(): void {
this.loadData()
}
async loadData() {
// 模拟网络获取数据
this.questionList = await new Promise<ExamItem[]>((resolve, reject) => {
setTimeout(() => {
resolve(mockExamDataList)
}, 500)
});
// 默认展示第一条
this.currentQuestion = this.questionList[this.currentIndex]
}
2.4.2 上一题、下一题
onPreClick() {
if (this.getPreEnable()) {
this.currentIndex--
this.currentQuestion = this.questionList[this.currentIndex]
}
}
onNextClick() {
if (this.getNextEnable()) {
this.currentIndex++
this.currentQuestion = this.questionList[this.currentIndex]
}
}
getPreEnable() {
return this.currentIndex > 0
}
getNextEnable() {
return this.currentIndex < this.questionList.length - 1
}
三、选项点击及高亮
3.1 声明对象及变量
export interface UserAnswer {
questionId: number // 问题ID
userAnswer: string // 用户选择项
}
在主页面中,定义变量,存储用户做题数据,如下:
// 存储题目和用户答案
@State userAnswerList: Record<number, UserAnswer> = {}
3.2 给选项注册点击事件
ForEach(this.currentQuestion.options, (item: OptionItem) => {
Row() {
Text(item.letter + '. ')
Text(item.content)
}
.width('100%')
.height(40)
.padding({ left: 12 })
.onClick(() => {
this.onUserAnswerClick(item)
})
})
处理点击事件:
onUserAnswerClick(option: OptionItem) {
this.userAnswerList[this.currentQuestion.id] = {
questionId: this.currentQuestion.id,
userAnswer: option.letter
}
}
3.3 处理背景和文本颜色
Row() {
Text(item.letter + '. ')
.fontColor(this.getOptionColor(item, 'font'))
Text(item.content)
.fontColor(this.getOptionColor(item, 'font'))
}
...
.backgroundColor(this.getOptionColor(item))
/**
* 获取选择项背景或文本颜色
* @param option 当前选择项
* @param type 类型
* @returns
*/
getOptionColor(option: OptionItem, type: 'back' | 'font' = 'back') {
if (this.currentQuestion) {
const userAnswer = this.userAnswerList[this.currentQuestion.id]
if (userAnswer?.userAnswer === option.letter) {
return type === 'back' ? Color.Pink : Color.White
}
}
return type === 'back' ? '#e5e5e5' : Color.Black
}
四、小结
- UI布局
- 题目切换处理
- 做题标记及高亮展示