鸿蒙 OS 开发单词打卡 APP 项目实战 20240922 笔记和源码分享
import { AnswerStatus } from '../enums/AnswerStatus'
import { PracticeStatus } from '../enums/PracticeStatus'
import { getRandomQuestions, Question } from '../model/Question'
import { promptAction } from '@kit.ArkUI'
import { OptionButton } from '../components/OptionButton'
import { StatItem } from '../components/StatItem'
import { ResultDialog } from '../components/ResultDialog'
import { trustedAppService } from '@kit.DeviceSecurityKit'
@Entry
@Component
struct PracticePage {
// 练习状态
@State status: PracticeStatus = PracticeStatus.STOPPED
// 题目个数
@State totalQuestion: number = 3
// 题目数组
@State questions: Question[] = getRandomQuestions(this.totalQuestion)
// 当前题目的索引
@State currentIndex: number = 0
// 用户选中的选项
@State selectedOption: string = ""
// 作答状态
@State answerStatus: AnswerStatus = AnswerStatus.Answering
// 已作答个数
@State answeredCount: number = 0
// 答对的个数
@State rightCount: number = 0
// 控制定时器
timerController = new TextTimerController()
// 总用时时间
@State totalTime: number = 0
// 自定义的弹窗组件控制器
dialogController: CustomDialogController = new CustomDialogController({
builder: ResultDialog({
answeredCount: this.answeredCount,
rightCount: this.rightCount,
totalTime: this.totalTime,
onStartFunc: () => {
this.status = PracticeStatus.RUNNING
this.timerController.start()
},
onCloseFunc: () => {
this.questions = getRandomQuestions(this.totalQuestion)
this.currentIndex = 0
this.answeredCount = 0
this.rightCount = 0
this.totalTime = 0
this.timerController.reset()
this.answerStatus = AnswerStatus.Answering
this.status = PracticeStatus.STOPPED
},
}),
customStyle: true, // 使用自定义样式, 否则那个 x 出不来
autoCancel: false, // 点击空白区域不会被自动关闭
})
// 统计准确率
getRightPercent() {
if (this.rightCount === 0) {
return "0%"
}
return `${((this.rightCount / this.answeredCount) * 100).toFixed()}%`
}
// 停止练习
stopPractice() {
this.status = PracticeStatus.STOPPED
this.timerController.pause()
this.dialogController.open()
}
build() {
Column() {
// 统计面板
Column() {
// 准确率
StatItem({
icon: $r("app.media.ic_accuracy"),
name: "准确率",
fontColor: Color.Black,
}) {
Text(this.getRightPercent())
.width(100)
.textAlign(TextAlign.Center)
}
// 进度
StatItem({
icon: $r("app.media.ic_progress"),
name: "进度",
fontColor: Color.Black,
}) {
Progress({ value: this.answeredCount, total: this.totalQuestion })
.width(100)
}
// 题目个数
StatItem({
icon: $r("app.media.ic_count"),
name: "个数",
fontColor: Color.Black,
}) {
Button(this.totalQuestion.toString())
.width(100)
.height(25)
.backgroundColor("#EBEBEB")
.enabled(this.status === PracticeStatus.STOPPED)
.onClick(() => {
TextPickerDialog.show({
range: ["5", "10", "20", "50", "100"],
value: this.totalQuestion.toString(), // 默认值
onAccept: (result) => {
this.totalQuestion = parseInt(result.value.toString())
this.questions = getRandomQuestions(this.totalQuestion)
}
})
})
}
// 计时
StatItem({
icon: $r("app.media.ic_timer"),
name: "用时",
fontColor: Color.Black,
}) {
Row() {
TextTimer({ controller: this.timerController })
.onTimer((utc, elapsedTime) => {
this.totalTime = elapsedTime
})
}.width(100)
.justifyContent(FlexAlign.Center)
}
}.statBgStyle()
// 题目
Column() {
Text(this.questions[this.currentIndex].word).wordStyle()
Text(this.questions[this.currentIndex].sentence).sentenceStyle()
}
// 选项
Column({ space: 15 }) {
ForEach(
this.questions[this.currentIndex].options,
(item: string) => {
OptionButton({
option: item,
answer: this.questions[this.currentIndex].answer,
selectedOption: this.selectedOption,
answerStatus: this.answerStatus,
})
.enabled(this.answerStatus === AnswerStatus.Answering)
.onClick(() => {
// 判断练习状态
if (this.status !== PracticeStatus.RUNNING) {
promptAction.showToast({ message: "请先点击开始测试按钮" })
return
}
// 先将答题状态改为已作答
this.answerStatus = AnswerStatus.Answered
// 判断答案是否正确
this.selectedOption = item
this.answeredCount++
if (this.questions[this.currentIndex].answer === this.selectedOption) {
this.rightCount++
}
// 判断题目状态
if (this.currentIndex < this.questions.length - 1) {
setTimeout(() => {
this.currentIndex++
this.answerStatus = AnswerStatus.Answering
}, 500)
} else {
// 停止测试
this.stopPractice()
}
})
},
(item: string) => this.questions[this.currentIndex].word + "_" + item,
)
}
// 控制按钮
Row({ space: 20 }) {
Button("停止测试").controlButtonStyle(
Color.Transparent,
this.status === PracticeStatus.STOPPED ? Color.Gray : Color.Black,
this.status === PracticeStatus.STOPPED ? Color.Gray : Color.Black,
).enabled(this.status !== PracticeStatus.STOPPED)
.onClick(() => this.stopPractice())
Button(this.status === PracticeStatus.RUNNING ? "暂停测试" : "开始测试")
.controlButtonStyle(
this.status === PracticeStatus.RUNNING ? "#666666" : Color.Black,
this.status === PracticeStatus.RUNNING ? "#666666" : Color.Black,
Color.White,
)
.stateEffect(false)
.onClick(() => {
if (this.status === PracticeStatus.RUNNING) {
// 暂停测试
this.status = PracticeStatus.PAUSED
this.timerController.pause()
} else {
// 开始测试
this.status = PracticeStatus.RUNNING
this.timerController.start()
}
})
}
}.practiceBgStyle()
}
}
// 页面背景
@Extend(Column)
function practiceBgStyle() {
.width("100%")
.height("100%")
.backgroundImage($r("app.media.img_practice_bg"))
.backgroundImageSize({ width: "100%", height: "100%" })
.justifyContent(FlexAlign.SpaceEvenly)
}
// 统计面板背景
@Styles
function statBgStyle() {
.backgroundColor(Color.White)
.width("90%")
.borderRadius(10)
.padding(20)
}
// 单词样式
@Extend(Text)
function wordStyle() {
.fontSize(50)
.fontWeight(FontWeight.Bold)
}
// 例句样式
@Extend(Text)
function sentenceStyle() {
.height(40)
.fontSize(16)
.fontColor("#9BA1A5")
.fontWeight(FontWeight.Medium)
.width("80%")
.textAlign(TextAlign.Center)
}
// 控制按钮样式
@Extend(Button)
function controlButtonStyle(
bgColor: ResourceColor,
borderColor: ResourceColor,
fontColor: ResourceColor,
) {
.fontSize(16)
.borderWidth(1)
.backgroundColor(bgColor)
.borderColor(borderColor)
.fontColor(fontColor)
}