HarmonyOS7单词闪卡复习器实战:卡片翻转与掌握状态推进

HarmonyOS7单词闪卡复习器实战:卡片翻转与掌握状态推进 文章目录前言Word 模型不只是单词文本当前单词用索引推导出来切换上一张和下一张时要顺手收起答案掌握和复习按钮会同时改状态并推进卡片card 和 list 是两个查看入口完整代码最后总结前言背单词页面很容易只做成“点一下显示答案”。这个示例比那种静态卡片多走了一步它记录当前复习到哪张卡、答案是否展开、这个词有没有掌握以及用户想用闪卡模式还是列表模式看全部单词。它没有做间隔重复算法也没有引入数据库但复习器最小闭环已经在了看单词显示释义选择“已掌握”或“再次复习”然后自动进入下一张。学习类页面只要这个闭环顺后面加错题本、每日任务、复习计划才有意义。Word 模型不只是单词文本Word里同时放了展示字段和学习状态。word、phonetic、meaning、example负责卡片内容mastered和reviewCount负责记录学习进度。interfaceWord{id:numberword:stringphonetic:stringmeaning:stringexample:stringmastered:booleanreviewCount:number}我会把mastered看成一个粗粒度状态这个词当前是否算掌握。reviewCount则更像行为计数不管用户点“已掌握”还是“再次复习”都说明这个词又被复习了一轮所以计数都会增加。这个模型的限制也明显它只有掌握和未掌握两档没有“模糊”“完全不会”“下次复习时间”这些更细的记忆信息。如果要做真正的背词应用后面大概率要把mastered升级成等级或记忆状态。当前单词用索引推导出来页面没有单独保存currentWord而是保存currentIndex再用 getter 从words里取当前单词。StatecurrentIndex:number0getcurrentWord():Word{returnthis.words[this.currentIndex]}这个写法很适合闪卡索引是导航状态单词列表是主数据源。只要currentIndex变了卡片展示就跟着变。旧稿里如果把它写成currentWord(): Word就和源码不一致了源码这里是 getter页面读取时直接用this.currentWord.word。顶部进度也是派生值不需要额外状态。masteredCount每次从words里筛出已掌握数量再用于进度条和百分比。getmasteredCount():number{returnthis.words.filter(ww.mastered).length}这种写法的好处是不会出现“某个词已经改成掌握但进度条忘了更新”的问题。学习进度只认words这一份数据。切换上一张和下一张时要顺手收起答案nextWord()和prevWord()都先把showAnswer设回false再移动索引。nextWord(){this.showAnswerfalsethis.currentIndex(this.currentIndex1)%this.words.length}prevWord(){this.showAnswerfalsethis.currentIndex(this.currentIndex-1this.words.length)%this.words.length}这个细节很重要。用户在上一张卡片看过释义后进入下一张时应该重新回到“只看单词”的状态。如果不重置showAnswer下一张会直接露出答案闪卡复习就失去意义了。两个索引公式也处理了循环边界最后一张再点下一张会回到第一张第一张点上一张会跳到最后一张。教学示例里这样体验更顺不需要额外禁用按钮。掌握和复习按钮会同时改状态并推进卡片用户只有在显示答案后才会看到“再次复习”和“已掌握”两个按钮。点击后都会更新当前词的状态和复习次数然后调用nextWord()进入下一张。markMastered(){this.wordsthis.words.map((w,i){if(ithis.currentIndex){w.masteredtrue;w.reviewCountw.reviewCount1}returnw})this.nextWord()}markReview(){this.wordsthis.words.map((w,i){if(ithis.currentIndex){w.masteredfalse;w.reviewCountw.reviewCount1}returnw})this.nextWord()}这里用了map()重新赋值给this.words这是为了让状态变化更明确地触发 UI 刷新。内部确实直接改了对象字段如果项目对不可变数据要求更严格可以返回一个新对象但作为 ArkUI 入门示例这种写法更容易看懂。markReview()把mastered设成false这意味着“再次复习”不是简单跳过而是明确告诉系统这个词还没掌握。进度条会立刻反映这个变化。card 和 list 是两个查看入口mode只有两个值card和list。顶部右侧文本负责切换模式build()里用if (this.mode card)决定渲染闪卡还是列表。Statemode:stringcard// card | list闪卡模式适合逐个复习列表模式适合快速回看所有单词。列表里会展示释义、音标、复习次数并在已掌握单词后面显示标记。这里列表只是展示入口不能从列表里直接跳到某个单词也不能修改掌握状态。如果后续要增强我会先把mode改成联合类型避免随手写出别的字符串然后给列表项加点击逻辑让用户从列表直接定位到某张卡。再往后才是更复杂的间隔复习算法。完整代码下面保留案例完整代码方便你直接对照学习、复制和重构。import{router}fromkit.ArkUIinterfaceWord{id:numberword:stringphonetic:stringmeaning:stringexample:stringmastered:booleanreviewCount:number}EntryComponentstruct WordFlashcardReview{Statewords:Word[][{id:1,word:Eloquent,phonetic:/ˈeləkwənt/,meaning:雄辩的口才流利的,example:She gave an eloquent speech.,mastered:false,reviewCount:2},{id:2,word:Resilience,phonetic:/rɪˈzɪliəns/,meaning:适应力弹性韧性,example:Children show great resilience.,mastered:true,reviewCount:5},{id:3,word:Ephemeral,phonetic:/ɪˈfemərəl/,meaning:短暂的朝生暮死的,example:Fame is ephemeral.,mastered:false,reviewCount:1},{id:4,word:Ubiquitous,phonetic:/juːˈbɪkwɪtəs/,meaning:无处不在的普遍存在的,example:Smartphones are ubiquitous.,mastered:false,reviewCount:3},{id:5,word:Serendipity,phonetic:/ˌserənˈdɪpɪti/,meaning:意外发现美好事物的能力,example:Finding that book was pure serendipity.,mastered:true,reviewCount:4},]StatecurrentIndex:number0StateshowAnswer:booleanfalseStatemode:stringcard// card | listgetcurrentWord():Word{returnthis.words[this.currentIndex]}getmasteredCount():number{returnthis.words.filter(ww.mastered).length}nextWord(){this.showAnswerfalsethis.currentIndex(this.currentIndex1)%this.words.length}prevWord(){this.showAnswerfalsethis.currentIndex(this.currentIndex-1this.words.length)%this.words.length}markMastered(){this.wordsthis.words.map((w,i){if(ithis.currentIndex){w.masteredtrue;w.reviewCountw.reviewCount1}returnw})this.nextWord()}markReview(){this.wordsthis.words.map((w,i){if(ithis.currentIndex){w.masteredfalse;w.reviewCountw.reviewCount1}returnw})this.nextWord()}build(){Column(){// HeaderRow(){Image($r(app.media.ic_back)).width(24).height(24).onClick(()router.back())Text(单词闪卡).fontSize(18).fontWeight(FontWeight.Bold).layoutWeight(1).textAlign(TextAlign.Center)Text(this.modecard?列表:闪卡).fontSize(14).fontColor(#007AFF).onClick(()this.modethis.modecard?list:card)}.width(100%).padding({left:16,right:16,top:12,bottom:12})// Progress barRow(){Text(\\${this.masteredCount}/\${this.words.length}已掌握\).fontSize(12).fontColor(#666)Blank()Text(\\${Math.round(this.masteredCount/this.words.length*100)}%\).fontSize(12).fontColor(#007AFF)}.width(calc(100% - 32vp)).margin({bottom:6})Progress({value:this.masteredCount,total:this.words.length}).width(calc(100% - 32vp)).color(#007AFF).backgroundColor(#E0E0E0).height(6).borderRadius(3).margin({bottom:16})if(this.modecard){// Flash card modeColumn(){Text(\\${this.currentIndex1}/\${this.words.length}\).fontSize(13).fontColor(#888).margin({bottom:16})// CardColumn(){Text(this.currentWord.word).fontSize(32).fontWeight(FontWeight.Bold).fontColor(#333).margin({bottom:8})Text(this.currentWord.phonetic).fontSize(16).fontColor(#888).margin({bottom:16})if(this.showAnswer){Divider().margin({bottom:16})Text(this.currentWord.meaning).fontSize(18).fontColor(#007AFF).margin({bottom:12})Text(\\${this.currentWord.example}\).fontSize(13).fontColor(#666).fontStyle(FontStyle.Italic)}else{Button(显示释义).width(140).backgroundColor(#007AFF).fontColor(#FFF).borderRadius(20).onClick(()this.showAnswertrue)}}.width(calc(100% - 32vp)).height(200).backgroundColor(#FFF).borderRadius(20).padding(28).shadow({radius:12,color:#0000001A,offsetY:6}).alignItems(HorizontalAlign.Center).justifyContent(FlexAlign.Center).margin({bottom:24})// Buttonsif(this.showAnswer){Row(){Button(再次复习).layoutWeight(1).backgroundColor(#FF4757).fontColor(#FFF).borderRadius(20).onClick(()this.markReview())Button(已掌握).layoutWeight(1).backgroundColor(#2ED573).fontColor(#FFF).borderRadius(20).margin({left:16}).onClick(()this.markMastered())}.width(calc(100% - 32vp))}else{Row(){Button(← 上一个).layoutWeight(1).backgroundColor(#F0F0F0).fontColor(#333).borderRadius(20).onClick(()this.prevWord())Button(下一个 →).layoutWeight(1).backgroundColor(#007AFF).fontColor(#FFF).borderRadius(20).margin({left:16}).onClick(()this.nextWord())}.width(calc(100% - 32vp))}}.layoutWeight(1).justifyContent(FlexAlign.Center).alignItems(HorizontalAlign.Center)}else{// List modeList({space:8}){ForEach(this.words,(w:Word){ListItem(){Row(){Column(){Row(){Text(w.word).fontSize(16).fontWeight(FontWeight.Bold).fontColor(#333)if(w.mastered){Text( ✓).fontSize(14).fontColor(#2ED573)}}Text(w.phonetic).fontSize(12).fontColor(#888).margin({top:2})Text(w.meaning).fontSize(13).fontColor(#666).margin({top:4})}.layoutWeight(1).alignItems(HorizontalAlign.Start)Text(\\${w.reviewCount}次\).fontSize(12).fontColor(#AAA)}.width(100%).backgroundColor(#FFF).borderRadius(12).padding(14)}})}.layoutWeight(1).padding({left:16,right:16})}}.width(100%).height(100%).backgroundColor(#F5F5F5)}}最后总结单词闪卡复习器 这个案例并不靠复杂技巧取胜它更像一个结构扎实、逻辑完整的业务页面样板。把这篇文章里的状态设计、函数组织和页面分层吃透之后你再去写同类型功能速度会明显快很多。