HarmonyOS 应用开发《掌上英语》第54篇:设置页面全解析——从主题选择到数据清除
设置页面全解析——从主题选择到数据清除
引言
设置页面是每个应用都不可或缺的"控制中心",它汇聚了用户偏好配置、账户管理、应用信息等关键功能。一个设计良好的设置页面应当做到:功能分类清晰、交互反馈即时、配置项可持久化。本文将以SetUpPage为例,深入解析其在 HarmonyOS 上的完整实现。
SetUpPage位于features/minePage模块的views/SetupPage.ets中,是"我的"模块中的核心页面之一。它通过@Builder函数SetUpPageBuilder导出,注册在路由表中供全局导航使用。
设置项的模型设计
每个设置项对应一个SetupItem模型:
@ObservedV2exportclassSetupItem{@Tracemsg:string// 设置项标题@Traceicon:ResourceStr// 右侧图标@Tracecache?:string// 缓存大小(仅"清除缓存"项使用)@Traceurl?:string// 点击跳转的目标路由}SetupItem使用@ObservedV2和@Trace装饰器,保证属性的变化(如缓存大小的动态更新)能自动触发 UI 重绘。cache字段为可选字段,只有缓存相关的设置项才会使用;url字段绑定路由标识,使得设置项的点击行为可以通过数据驱动而非硬编码。
设置项列表的实现
列表数据初始化
设置项列表在组件内部直接定义:
@LocalsetupList:SetupItem[]=[newSetupItem('个人信息',$r('app.media.iconRight'),'',RouterMap.PERSONAL_CENTER_PAGE),newSetupItem('夜间模式',$r('app.media.iconRight'),'',''),newSetupItem('学习计划',$r('app.media.iconRight'),'','LearningPlanSettingPage'),newSetupItem('学习提醒',$r('app.media.iconRight'),'','ReminderSettingPage'),newSetupItem('隐私协议',$r('app.media.iconRight'),'','PrivacyStatementPage'),newSetupItem('关于',$r('app.media.iconRight'),'',RouterMap.ABOUT_PAGE),newSetupItem('清除缓存',$r('app.media.iconRight'),'0.0M',''),];这里涉及的路由标识既有RouterMap枚举值(如RouterMap.PERSONAL_CENTER_PAGE),也有直接字符串(如'LearningPlanSettingPage')。使用RouterMap枚举的好处是编译期类型检查,避免字符串拼写错误;而直接使用字符串则更为灵活,适用于尚未纳入枚举的路由。
UI 渲染
每个设置项由SetupComponent组件渲染:
struct SetupComponent{@Param@RequiresetupItem:SetupItem;@Param@RequireselectItem:string='跟随系统';build(){Stack(){Row(){Text(this.setupItem.msg).fontColor($r('sys.color.font_primary')).fontSize($r('sys.float.Body_L'));Row(){if(this.setupItem.cache){Text(this.setupItem.cache)// 显示缓存大小.fontColor($r('sys.color.font_primary')).fontWeight(FontWeight.Medium)}Image(this.setupItem.icon)// 右侧箭头图标}}.width('100%').backgroundColor($r('sys.color.comp_background_list_card')).borderRadius($r('app.float.vp_16')).justifyContent(FlexAlign.SpaceBetween)}}}组件采用左右布局:左侧是设置项标题文本,右侧是缓存数值(如有)加箭头图标。justifyContent(FlexAlign.SpaceBetween)保证左右内容自动撑开两端对齐。
点击事件分发
外层通过ForEach循环为每个设置项绑定点击事件:
ForEach(this.setupList,(item:SetupItem)=>{SetupComponent({...}).onClick(()=>{if(item.msg==='清除缓存'){this.showAlert(AlertType.clean);}elseif(item.msg==='夜间模式'){this.darkDialog.open();}elseif(item.msg==='个人信息'){if(item.url&&this.logoUser.isLogin){RouterModule.push({url:item.url});}else{RouterModule.push({url:RouterMap.LOGIN_PAGE,param:false});}}else{if(item.url){RouterModule.push({url:item.url});}}})})事件分发策略是:特殊项(清除缓存、夜间模式)弹出对话框,个人信息项需要检查登录状态,其余常规项直接通过RouterModule.push导航。这种"数据驱动 + 特殊判断"的混合模式,在保持代码简洁的同时也覆盖了页面特有的交互逻辑。
夜间模式切换
夜间模式是设置页面的核心功能之一,实现涉及三个层面:UI 交互、系统 API 调用和偏好持久化。
选择弹窗
夜间模式的设置通过DarkColorDialog自定义弹窗实现:
@CustomDialogexportstruct DarkColorDialog{selectItem:string='';selectItemBack:(selectItem:string)=>void=()=>{};cancel:()=>void=()=>{};}弹窗内使用 Grid 布局展示三个选项:跟随系统、普通模式、夜间模式。每个选项左侧显示文字,右侧显示选中标记:
Row(){Text(item.label)Image(item.label===this.selectItem?$r('app.media.icon_Checked'):$r('app.media.icon_ans_common'))}当前选中的选项显示勾选图标(icon_Checked),未选中的显示普通圆形图标(icon_ans_common),视觉对比清晰。
模式切换逻辑
当用户选择某个模式后,SetUpPage的handleDark方法被调用:
handleDark(item:string){letcolorMode:number=1;if(item==='跟随系统'){colorMode=-1;this.getUIContext()?.getHostContext()?.getApplicationContext().setColorMode(ConfigurationConstant.ColorMode.COLOR_MODE_NOT_SET);}elseif(item==='普通模式'){colorMode=1;this.getUIContext()?.getHostContext()?.getApplicationContext().setColorMode(ConfigurationConstant.ColorMode.COLOR_MODE_LIGHT);}else{colorMode=0;this.getUIContext()?.getHostContext()?.getApplicationContext().setColorMode(ConfigurationConstant.ColorMode.COLOR_MODE_DARK);}PreferenceUtil.getInstance().put(PreferConstant.COLOR_MODE,colorMode);}核心是调用ApplicationContext.setColorMode()方法设置主题模式,该方法接受ConfigurationConstant.ColorMode枚举值:
COLOR_MODE_NOT_SET(-1):跟随系统COLOR_MODE_LIGHT(1):浅色模式COLOR_MODE_DARK(0):深色模式
设置完成后,通过PreferenceUtil将选择持久化到首选项文件中。下次应用启动时,aboutToAppear会读取该值并恢复:
aboutToAppear():void{letcolorMode=PreferenceUtil.getInstance().get(PreferConstant.COLOR_MODE,-1)asnumber;this.selectItem=this.getColorItem(colorMode);}getColorItem方法将数字映射为对应的描述字符串,完成从存储到展示的闭环。
缓存清除
缓存清除是典型的"风险操作",需要谨慎确认后再执行。SetUpPage通过两步确认机制实现:先弹框确认,再执行清除。
确认弹框
使用系统级showAlertDialogAPI:
showAlert(type:AlertType){this.getUIContext().showAlertDialog({message:type===AlertType.clean?'请确认是否清除缓存':'确定要退出登录吗',autoCancel:false,buttons:[{value:'取消',action:()=>{}},{value:'确定',action:()=>{if(type===AlertType.clean)this.clean();elsethis.loginOut();}}]});}AlertType枚举区分"清除缓存"和"退出登录"两种弹框类型,共享同一个弹框方法但展示不同的文案和执行逻辑。
缓存大小读取
在页面初始化时,通过storageStatistics.getCurrentBundleStats获取应用的存储统计数据:
getClearSize(){storageStatistics.getCurrentBundleStats((error,bundleStats)=>{this.setupList.forEach(eme=>{if(eme.msg==='清除缓存'){letisClean=PreferenceUtil.getInstance().isClean()&&PreferenceUtil.getInstance(PreferConstant.TOPIC_NOTES).isClean();if(isClean){eme.cache='0KB';}else{eme.cache=(bundleStats.dataSize/1024).toFixed(2)+'KB';}}});});}这里有一个巧妙的设计:通过PreferenceUtil的isClean()方法判断是否已经执行过清除。如果两个首选项实例均为初始状态(未写入过任何数据),则直接显示0KB,否则从bundleStats.dataSize计算真实缓存大小。
执行清除
clean方法的清除策略涉及多个目录:
asyncclean(){letpaths:Array<string>=[];letcontext=this.getUIContext()?.getHostContext()!;letmoduleContext=awaitapplication.createModuleContext(context,'entry');// 收集需要清除的目录paths.push(moduleContext.cacheDir);paths.push(context.cacheDir);paths.push(moduleContext.preferencesDir);paths.push(context.preferencesDir);// EL1 加密区域的 cache 和 preferences 目录moduleContext.area=contextConstant.AreaMode.EL1;context.area=contextConstant.AreaMode.EL1;paths.push(moduleContext.cacheDir);paths.push(context.cacheDir);paths.push(moduleContext.preferencesDir);paths.push(context.preferencesDir);// 清除所有 PreferenceUtil 的数据PreferenceUtil.getInstance().clear();PreferenceUtil.getInstance(PreferConstant.TOPIC_NOTES).clear();// 逐文件删除for(letpathofpaths){letfilenames=awaitfileIo.listFile(path);for(letfilenameoffilenames){letdirPath=path+'/'+filename;letisDirectory=fileIo.statSync(dirPath).isDirectory();if(isDirectory){fileIo.unlinkSync(dirPath);// 目录用同步删除}else{fileIo.unlink(dirPath);// 文件用异步删除}}}this.getClearSize();// 重新计算缓存大小}清除范围覆盖了主工程模块和 entry 模块的cacheDir和preferencesDir,同时针对 EL1(加密等级 1)区域也进行了清除。对于目录使用unlinkSync同步删除,对于文件使用unlink异步删除——这种差异化处理既避免了异步删除目录可能引发的并发问题,又利用异步删除提升文件删除效率。最后重新调用getClearSize()刷新 UI 上的缓存数值。
隐私协议与关于页面
对于隐私协议这类静态展示页面,SetUpPage通过路由跳转的方式将它们分发到各自的独立页面。路由表中注册了多个相关页面:
PrivacyStatementPage:隐私声明PrivacyAgreementBuilder:隐私协议PrivacyUseAlertBuilder:隐私使用说明AboutPage:关于页面(展示应用名称、版本号、图标等信息)FeedbackSubmitPage:意见反馈页面
这些页面通过RouterMap统一管理,在设置项中按需引用。
退出登录
退出登录位于设置列表底部,作为一个独立的大按钮展示。点击后同样通过showAlertDialog确认,确认后将UserInfo.isLogin置为 false,弹出 Toast 提示"退出成功",然后调用RouterModule.pop()返回上一页。
总结
SetUpPage是一个功能完整、设计成熟的设置页面实现。其技术亮点可以归纳为:
- 数据驱动:设置项列表通过
SetupItem模型定义,增删改设置项只需修改数据数组,无需改动 UI 代码。 - 主题切换:利用
ApplicationContext.setColorMode()系统 API 实现夜间模式,搭配DarkColorDialog提供友好的交互选择界面。 - 缓存管理:通过
storageStatistics读取缓存大小,使用fileIo实现多目录递归删除,覆盖 EL1 加密区域。 - 安全机制:清除缓存和退出登录都通过 AlertDialog 二次确认,避免误操作。
- 状态持久化:主题选择通过
PreferenceUtil持久化存储,应用重启后自动恢复用户偏好。
对于需要构建设置页面的 HarmonyOS 应用,SetUpPage提供了一份可参考的完整实现方案。
