news 2026/10/27 18:36:36

【笔下生辉|12】HarmonyOS ArkTS 设置页实战:保存显示、学习和清理偏好

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
【笔下生辉|12】HarmonyOS ArkTS 设置页实战:保存显示、学习和清理偏好

设置页在学习类 HarmonyOS 应用里不是简单的“关于我们”页面。它同时连接用户偏好、学习数据、跳转入口和风险操作。偏好要能保存,显示要能立刻刷新;学习数据要能看见数量,点进去要能定位到对应页面;清理数据必须有确认,不能一次误触就把收藏、笔记、错题和进度全删掉。笔下生辉的设置页围绕这些问题做了一套本地化实现:页面通过@StorageLink读取 AppStorage 中的偏好和学习记录,通过UserDataManager持久化或清理数据,通过底部选择器修改学习设置,并用tipText给用户即时反馈。

本文基于com.jiaweikang.one17的真实 HarmonyOS ArkTS 源码复盘,主线文件是entry/src/main/ets/pages/SettingsPage.ets,关联librarya/src/main/ets/utils/UserDataManager.ets、entry/src/main/ets/pages/PracticePage.ets、entry/src/main/ets/views/FavoritePage.ets、entry/src/main/ets/pages/LearningStatsPage.ets与entry/src/main/ets/views/MinePage.ets。文章只讨论源码可复核的能力:保存每日提醒时间、挑战时长、答题后自动下一题;展示收藏、笔记、错题、学习进度、挑战历史数量;点击数据项跳转到对应页面;二次确认后清理本地学习记录。当前源码没有账号设置、云同步、服务器备份、联网权限管理或远程配置,本文不会把这些写成已经实现。

页面职责:本地偏好和学习数据入口

SettingsPage一上来订阅了两类状态。第一类是学习记录:

@StorageLink('favoriteRecords') favRecords: FavoriteRecord[] = [] @StorageLink('wrongRecords') wrongRecords: WrongRecord[] = [] @StorageLink('noteRecords') noteRecords: NoteRecord[] = [] @StorageLink('bankProgress') progressList: BankProgress[] = [] @StorageLink('examHistory') examHistory: ExamHistory[] = [] @StorageLink('chapterProgress') chapterProgressList: ChapterProgress[] = []

第二类是学习偏好和导航状态:

@StorageLink('dailyReminderTime') dailyReminderTime: string = '09:00' @StorageLink('examDurationSec') examDurationSec: number = 1800 @StorageLink('autoNextQuestion') autoNextQuestion: boolean = false @StorageLink('currentTabIndex') currentTabIndex: number = 0 @StorageLink('favoriteTabIndex') favoriteTabIndex: number = 0

这说明设置页不是独立表单,而是应用全局本地状态的控制台。它既能改偏好,也能根据学习数据数量提供入口。它没有直接操作 Preferences,而是通过UserDataManager。这点很重要:页面负责交互和展示,持久化由服务层统一处理。

aboutToAppear:把链接状态同步到显示态

设置页还有一组display*状态,例如displayReminderTime、displayExamDurationSec、displayAutoNextQuestion、displayFavCount等。页面出现时会把@StorageLink的真实状态同步到展示态:

aboutToAppear(): void { this.displayReminderTime = this.dailyReminderTime this.displayExamDurationSec = this.examDurationSec this.displayAutoNextQuestion = this.autoNextQuestion this.syncDataCounts() }

syncDataCounts只做一件事:把数组长度写入显示计数。

private syncDataCounts(): void { this.displayFavCount = this.favRecords.length this.displayNoteCount = this.noteRecords.length this.displayWrongCount = this.wrongRecords.length this.displayProgressCount = this.progressList.length this.displayExamCount = this.examHistory.length this.dataRevision++ }

这里的settingsRevision和dataRevision是用于触发 UI 刷新的轻量辅助状态。源码在LearningSettingItem和DataSettingItem里放了透明文本读取这些 revision,目的是让设置项在状态变化后稳定刷新。这个写法不优雅,但它是源码里真实存在的刷新辅助机制,不能把它解释成框架级推荐模式。

偏好保存:先改 AppStorage,再交给 UserDataManager 持久化

每日提醒、挑战时长和自动下一题分别由三个方法保存。以每日提醒为例:

private selectReminder(value: string): void { this.dailyReminderTime = value this.displayReminderTime = value UserDataManager.saveDailyReminderTime(value) this.settingsRevision++ this.activePicker = '' this.showTip(value === '关闭' ? '每日学习提醒已关闭' : `每日学习提醒已设置为 ${value}`) }

挑战时长和自动下一题类似:

private selectExamDuration(value: number): void { this.examDurationSec = value this.displayExamDurationSec = value UserDataManager.saveExamDurationSec(value) this.settingsRevision++ this.activePicker = '' this.showTip(`挑战时长已设置为 ${Math.floor(value / 60)} 分钟`) } private selectAutoNext(value: boolean): void { this.autoNextQuestion = value this.displayAutoNextQuestion = value UserDataManager.saveAutoNextQuestion(value) this.settingsRevision++ this.activePicker = '' this.showTip(value ? '答题后自动下一题已开启' : '答题后自动下一题已关闭') }

这三个方法的共同顺序是:更新全局链接状态,更新页面展示状态,调用持久化方法,递增 revision,关闭 Picker,显示提示。这个顺序对用户体验比较直接:用户点选后页面立即变化,底部选择器收起,提示条说明结果。

UserDataManager:偏好写入 Preferences

设置页调用的保存方法在UserDataManager里只是薄封装:

static saveDailyReminderTime(value: string): void { UserDataManager.persist(UserDataManager.K_DAILY_REMINDER, value) } static saveExamDurationSec(value: number): void { UserDataManager.persist(UserDataManager.K_EXAM_DURATION, value) } static saveAutoNextQuestion(value: boolean): void { UserDataManager.persist(UserDataManager.K_AUTO_NEXT, value) }

真正写入发生在persist:

private static persist(key: string, value: Object | string | number | boolean): void { if (UserDataManager.prefs === null) return try { UserDataManager.prefs.putSync(key, JSON.stringify(value)) UserDataManager.prefs.flushSync() } catch (_) {} }

这说明设置页保存的是本地 Preferences,而不是远程用户设置。UserDataManager.init在应用启动时会从 Preferences 读取dailyReminderTime、examDurationSec、autoNextQuestion,并写入AppStorage。因此设置页改动可以跨重启保留。

当前源码没有实现系统通知能力,也没有注册真实提醒通知。dailyReminderTime只是本地偏好值,页面文案表达为每日学习提醒设置。文章不能把它写成已经接入系统通知或后台提醒。

SettingPicker:一个底部选择器复用三类设置

activePicker决定当前打开哪个设置项。点击学习设置项时,openPicker会关闭清理确认状态、清空提示,并记录类型:

private openPicker(type: string): void { this.pendingClear = false this.tipText = '' this.activePicker = type }

SettingPicker用Stack({ alignContent: Alignment.Bottom })形成底部弹层:上层是半透明遮罩,点击关闭;下方是选项容器。不同activePicker分支渲染不同选项。

if (this.activePicker === 'reminder') { this.PickerOption('每天 09:00', this.displayReminderTime === '09:00', () => { this.selectReminder('09:00') }) this.PickerOption('每天 12:00', this.displayReminderTime === '12:00', () => { this.selectReminder('12:00') }) this.PickerOption('每天 21:00', this.displayReminderTime === '21:00', () => { this.selectReminder('21:00') }) this.PickerOption('关闭提醒', this.displayReminderTime === '关闭', () => { this.selectReminder('关闭') }) }

挑战时长提供 20、30、45、60 分钟,对应秒数 1200、1800、2700、3600。自动下一题提供开启和关闭。Picker 复用PickerOption组件,选中项使用主色和“已选”标记,未选项使用普通背景。

.backgroundColor(selected ? Colors.PRIMARY_LIGHT : Colors.BACKGROUND_ALT) .onClick(() => { onTap() })

这个实现没有用系统 Picker 组件,而是自定义了一个简单底部面板。它的优点是样式可控,和应用卡片风格一致;缺点是如果选项变多,需要考虑滚动和键盘焦点。当前选项数量很少,使用自定义面板是可接受的。

学习偏好如何影响练习页

设置页保存的偏好不是孤立值。PracticePage也订阅了examDurationSec和autoNextQuestion:

@StorageLink('examDurationSec') examDurationSec: number = 1800 @StorageLink('autoNextQuestion') autoNextQuestion: boolean = false

限时挑战剩余时间使用examDurationSec:

return Math.max(0, this.examDurationSec - this.timerSec)

答题选择后,如果开启自动下一题,页面会通过setTimeout延迟推进:

if (this.autoNextQuestion) { setTimeout(() => { this.nextQuestion() }, 500) }

这说明设置页里的“挑战时长”和“答题后自动下一题”确实会影响练习体验。设置页负责保存偏好,练习页负责消费偏好。两者通过AppStorage连接,不需要互相引用。

数据管理:只显示数量,不直接展示明细

设置页的数据管理区展示五类数据:收藏记录、笔记记录、错题记录、学习进度、挑战历史。它们的显示值由dataValueText生成:

private dataValueText(type: string): string { if (type === 'fav') return `${this.displayFavCount} 条` if (type === 'note') return `${this.displayNoteCount} 条` if (type === 'wrong') return `${this.displayWrongCount} 条` if (type === 'progress') return `${this.displayProgressCount} 个题库` return `${this.displayExamCount} 次` }

设置页没有直接渲染收藏或错题列表,而是作为入口跳转。点击收藏、笔记、错题时,会设置favoriteTabIndex和currentTabIndex,然后router.back()回到主页面的错题本 Tab。

private openFavoriteTab(tabIndex: number): void { this.pendingClear = false this.favoriteTabIndex = tabIndex this.currentTabIndex = 3 router.back() }

学习进度跳转到LearningStatsPage,挑战历史切到主 Tab 的挑战页:

if (type === 'progress') { router.pushUrl({ url: 'pages/LearningStatsPage' }) } else { this.openMainTab(2) }

这个设计让设置页保持轻量。它不复制数据列表 UI,只展示统计数量和跳转入口。具体明细由已有页面承载。

清理数据:pendingClear 实现二次确认

清理所有学习数据是高风险操作。源码没有直接点击即清理,而是通过pendingClear做二次确认。

private requestClearAll(): void { if (this.pendingClear) { this.clearAllLearningData() } else { this.pendingClear = true this.showTip('再次点击红色按钮确认清除所有学习数据') } }

第一次点击红色按钮只进入确认状态,并显示提示;第二次点击才真正清理。按钮文案和边框也会随pendingClear变化,增强风险提示。这个设计比普通弹窗更轻,但仍然满足“误触不立即清除”的基本要求。

真正清理时,页面逐项调用UserDataManager:

private clearAllLearningData(): void { this.favRecords = UserDataManager.clearFavorites() this.noteRecords = UserDataManager.clearNotes() this.wrongRecords = UserDataManager.clearWrong() this.progressList = UserDataManager.clearProgress() this.chapterProgressList = UserDataManager.clearChapterProgress() this.examHistory = UserDataManager.clearExamHistory() this.displayFavCount = 0 this.displayNoteCount = 0 this.displayWrongCount = 0 this.displayProgressCount = 0 this.displayExamCount = 0 this.dataRevision++ this.pendingClear = false this.showTip('所有学习数据已清除') }

清理范围很明确:收藏、笔记、错题、题库进度、章节进度、挑战历史。它不清理每日提醒、挑战时长和自动下一题偏好。也就是说,“清理学习数据”不是恢复出厂设置,它不会把所有设置项重置。

清理服务:数组置空并持久化

UserDataManager中的清理方法都返回空数组,并写回 Preferences。

static clearFavorites(): FavoriteRecord[] { const result: FavoriteRecord[] = [] UserDataManager.persist(UserDataManager.K_FAV, result) return result } static clearProgress(): BankProgress[] { const result: BankProgress[] = [] UserDataManager.persist(UserDataManager.K_PROGRESS, result) return result } static clearExamHistory(): ExamHistory[] { const result: ExamHistory[] = [] UserDataManager.persist(UserDataManager.K_EXAM, result) return result }

设置页把返回值重新赋给@StorageLink数组,使全局状态和本地持久化保持一致。这样 FavoritePage、LearningStatsPage、HomePage 等订阅相同 AppStorage key 的页面都会看到变化。

需要注意的是,当前源码没有撤销清理,也没有备份导出。二次确认就是主要防误触机制。如果后续要支持备份或恢复,必须设计清楚本地文件、隐私说明、用户确认和存储权限边界。

安全区:底部提示和弹层避让系统导航

设置页订阅了navigationIndicatorHeightPx,并通过bottomSafePadding计算底部避让:

private bottomSafePadding(): number { return Math.max(Sizes.BOTTOM_NAV_MIN_PADDING, this.getUIContext().px2vp(this.navigationIndicatorHeightPx)) }

底部提示条使用这个值:

.margin({ left: Sizes.PADDING_LARGE, right: Sizes.PADDING_LARGE, bottom: 12 + this.bottomSafePadding() })

底部 Picker 同样在 padding 中加上安全区:

.padding({ left: Sizes.PADDING_LARGE, right: Sizes.PADDING_LARGE, top: 18, bottom: 22 + this.bottomSafePadding() })

这对 HarmonyOS 多设备和手势导航体验很重要。设置页底部有提示条和弹层操作,如果不避让导航指示区域,用户可能看得到但点不到,或者被系统手势区域挡住。源码通过统一方法处理底部安全区,比固定写死底部间距更稳。

提示反馈:tipText 是轻量状态反馈

设置页没有在每次保存后弹系统 Toast,而是在页面底部显示tipText。只要tipText.length > 0,页面就渲染一条可点击关闭的提示。

if (this.tipText.length > 0) { Text(this.tipText) .fontSize(Sizes.CAPTION_FONT) .fontColor(Colors.PRIMARY) .backgroundColor(Colors.PRIMARY_LIGHT) .onClick(() => { this.tipText = '' }) }

这种反馈方式和页面内设置操作更一致。用户选择提醒时间、挑战时长、自动下一题,或者执行清理操作后,都能看到结果。它不是全局消息系统,也没有自动消失定时器,用户可以点击提示清空。

当前实现有一个边界:提示不会自动消失,可能持续占据底部空间。对于设置页问题不大,因为这是一个低频操作页面;如果要在高频交互页复用这种提示,就需要增加自动清理或队列机制。

关于区:只展示本地事实

设置页的“关于”区域包含版本、平台和框架。点击后只是显示提示:

this.ActionSettingItem('版本', 'v1.0.0', () => { this.showTip('当前已经是 v1.0.0,本地题库持续完善中。') }) this.ActionSettingItem('平台', 'HarmonyOS', () => { this.showTip('应用已按手机、平板、2in1 多设备布局做适配。') }) this.ActionSettingItem('框架', 'ArkTS / ArkUI', () => { this.showTip('页面采用 ArkTS 与 ArkUI 构建,数据保存在本机。') })

这部分没有外链、没有账号、没有隐私页面跳转,也没有在线更新检测。它更像一个本地说明入口。文章不能把它扩展成完整账号中心或在线版本检查。

如果后续准备全平台发布或上架复核,版本号最好从统一配置读取,避免源码写死v1.0.0与 AGC 版本材料不一致。当前源码版本和队列里的1.0.0 已上架一致,但后续版本更新时仍要注意同步。

当前实现的工程边界和改进点

第一,dailyReminderTime当前只是偏好保存,没有真实通知调度。若要实现每日提醒,需要接入通知能力、权限说明、用户授权和后台行为边界,不能只改设置页文案。

第二,清理数据使用二次点击确认,但没有弹窗列出清理范围。当前按钮和提示已经有基础防误触,若面向更多用户,可以增加确认弹层,把“收藏、笔记、错题、进度、挑战历史”逐项列出。

第三,display*状态和@StorageLink状态存在一定重复。当前写法便于立即刷新和展示,但后续可以考虑把显示值改成方法派生,减少双写风险。

第四,settingsRevision和dataRevision通过透明文本参与渲染,这属于工程权宜手段。若页面刷新问题已由状态绑定解决,可以逐步去掉这种隐藏节点。

第五,设置页锁定在本地数据管理范畴。任何账号、云同步、导出备份或隐私设置都需要新设计数据流和合规说明,不能直接复用当前清理逻辑。

面向 HarmonyOS 5.0+ 的实践价值

从 HarmonyOS ArkTS 工程角度看,这个设置页有几个值得保留的实践点。它把 Preferences 写入封装在UserDataManager,避免 UI 直接碰持久化 API;它用@StorageLink连接设置页和练习页,偏好变化可以被业务页面消费;它通过底部 Picker 统一设置选项,不把三个设置项写成三套弹层;它对清理操作做了二次确认;它考虑了底部安全区,避免提示和弹层被系统导航区域挡住。

这些点都不是复杂技术,但它们决定设置页是否可靠。学习应用里的设置页最怕两类问题:一类是保存了但业务页不生效,另一类是清理操作误触且无法恢复。笔下生辉当前源码围绕这两类问题做了比较清晰的本地闭环。

结语:设置页要把低频操作做稳

笔下生辉的设置页实现展示的是一个本地学习应用常见但重要的闭环:SettingsPage订阅本地偏好和学习记录,页面出现时同步显示状态;用户通过底部 Picker 修改每日提醒、挑战时长和自动下一题,UserDataManager.save*写入 Preferences;数据管理区展示收藏、笔记、错题、进度和挑战历史数量,并跳转到对应页面;清理全部学习数据必须二次确认,再逐项清空本地记录并刷新显示。

这套实现没有伪造成账号中心,也没有声称支持云同步。它的价值在于把低频但高风险的本地设置操作做得清楚:保存路径可复核,消费页面可追踪,清理范围明确,用户反馈及时,底部区域有安全区避让。对于 HarmonyOS 5.0+ 的 ArkTS 应用来说,这就是设置页最基本也最值得守住的工程质量。

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/10/27 18:34:49

HiVG快速上手教程:5分钟安装部署,生成你的第一张SVG矢量图

【免费下载链接】HiVG Hierarchical SVG Tokenization: Learning Compact Visual Programs for Scalable Vector Graphics Modeling 项目地址: https://gitcode.com/gh_mirrors/hiv/HiVG 点击查看 免费下载 HiVG 是一款开源的分层 SVG 分词与生成模型(H…

作者头像 李华
网站建设 2026/10/27 18:34:15

营销技能的核心:从用户理解到数据归因的闭环系统

我接触过不少做市场、做运营的朋友,大家聊到最后经常会回到同一个话题:marketing skills到底指什么。有人说是写文案的能力,有人说是投流的手感,有人说是搞活动的执行力。这些说法都对,但都不完整。我自己的理解是&…

作者头像 李华
网站建设 2026/10/27 18:31:49

2003-2024年 上市公司-企业股东股权质押数据+代码

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华
网站建设 2026/10/27 18:29:19

艾尔登法环存档换机无法加载?三步修改 SteamID 完成迁移

艾尔登法环存档换机无法加载?三步修改 SteamID 完成迁移 【免费下载链接】ER-Save-Editor Elden Ring Save Editor. Compatible with PC and Playstation saves. 项目地址: https://gitcode.com/GitHub_Trending/er/ER-Save-Editor ER-Save-Editor 是一款开源…

作者头像 李华
网站建设 2026/10/27 18:23:28

Go量化交易系统实战:从行情管道到风控与性能优化

简介:基于Go语言实现的量化交易系统完整源码包,面向量化开发者、Go后端工程师及金融科技学习者。系统针对实时行情处理与多策略并发执行场景,利用goroutine和channel构建轻量级并发模型,覆盖交易接口封装、行情流解析、策略调度、…

作者头像 李华