前言前面九篇已经把喵汪星球的核心功能拆完了档案、记录、日历、卡片、录音、陪玩、健康。最后这篇讲一个经常被低估的问题应用从“功能能跑”到“像一个完整产品”差的往往不是一个大 API而是主题、暗色模式、空态、长文本、权限说明、多设备布局和发布检查。这些东西不够炫但决定用户对 App 的第一印象。主题系统先行不要在页面里散落色值项目把颜色、字号、间距、圆角集中在Theme.etsexport class Theme { static readonly bg $r(app.color.mw_bg) static readonly bgElevated $r(app.color.mw_bg_elevated) static readonly surface $r(app.color.mw_surface) static readonly surfaceSoft $r(app.color.mw_surface_soft) static readonly surfaceWarm $r(app.color.mw_surface_warm) static readonly surfaceBlue $r(app.color.mw_surface_blue) static readonly textPrimary $r(app.color.mw_text_primary) static readonly textSecondary $r(app.color.mw_text_secondary) static readonly textMuted $r(app.color.mw_text_muted) static readonly primary $r(app.color.mw_primary) static readonly emergency $r(app.color.mw_emergency) static readonly warning $r(app.color.mw_warning) static readonly border $r(app.color.mw_border) static readonly radiusS: number 8 static readonly radiusM: number 10 static readonly radiusL: number 14 static readonly radiusXL: number 22 static readonly spaceS: number 8 static readonly spaceM: number 12 static readonly spaceL: number 16 static readonly spaceXL: number 24 static readonly title: number 28 static readonly headline: number 22 static readonly subhead: number 18 static readonly body: number 15 static readonly small: number 13 static readonly caption: number 12 }页面里只使用语义变量.backgroundColor(Theme.bg) .fontColor(Theme.textPrimary) .border({ width: 1, color: Theme.border }) .borderRadius(Theme.radiusL) .padding(Theme.spaceL)这个习惯越早建立越好。否则当你要适配暗色模式、服务卡片、大屏时会发现页面里到处都是硬编码色值改一次像考古。明暗色通过资源文件解决浅色资源在resources/base/element/color.json{ name: mw_bg, value: #FAF8F2 }, { name: mw_surface, value: #FFFFFF }, { name: mw_primary, value: #21665B }, { name: mw_emergency, value: #C95F57 }暗色资源在resources/dark/element/color.json{ name: mw_bg, value: #0D1211 }, { name: mw_surface, value: #18221F }, { name: mw_primary, value: #6DB8A6 }, { name: mw_emergency, value: #EF8D84 }同一个资源名在不同模式下取不同颜色。页面代码不用判断当前是不是暗色模式。入口 Ability 中也没有强制颜色而是让应用跟随系统this.context .getApplicationContext() .setColorMode(ConfigurationConstant.ColorMode.COLOR_MODE_NOT_SET)工具型 App 更适合跟随系统。用户开了暗色模式通常就是希望所有常用应用都别突然亮起来。多设备适配phone、tablet、2in1 不只是配置module.json5中声明deviceTypes: [ phone, tablet, 2in1 ]声明了不等于适配了。页面必须根据视口宽度调整布局。项目通过onAreaChange获取宽度.onAreaChange((_oldArea, newArea) { this.updateViewportWidth(newArea.width.toString()) })解析宽度private updateViewportWidth(widthText: string): void { const parsed Number(widthText.replace(vp, ).replace(px, )) if (!Number.isNaN(parsed) parsed 0) { this.viewportWidth parsed } }根据宽度调整页面边距private pageHorizontalPadding(): number { if (this.isLargeScreen()) { return 56 } return Theme.spaceL }大屏上最常见的错误是把手机卡片直接拉满屏幕。看起来不高级也不利于阅读。适当增加横向留白通常比强行做复杂分栏更稳。底部导航也要考虑主功能突出项目的底部导航没有简单五等分而是把“翻译器”做成中间主按钮Builder BottomNav() { Stack({ alignContent: Alignment.Top }) { Row() { this.NavItem(首页, $r(app.media.tab_home)) this.NavItem(记录, $r(app.media.tab_record)) Column().width(78) this.NavItem(健康, $r(app.media.tab_health)) this.NavItem(我的, $r(app.media.tab_profile)) } Column({ space: Theme.spaceXS }) { Stack() { Image($r(app.media.tab_translator_light)) } .width(62) .height(62) .backgroundColor(Theme.primary) .borderRadius(31) Text(翻译器) } .onClick(() { this.switchTab(翻译器) }) } }这不是纯装饰。宠物叫声分析是产品最有记忆点的功能中间突出按钮能让用户形成入口记忆。长文本保护正式产品的基本功用户输入和业务数据都不可控。比如宠物名字、任务标题、任务备注、症状描述、翻译结果都可能很长。服务卡片中就做了保护Text(this.nextTitle) .fontSize(20) .fontWeight(FontWeight.Bold) .maxLines(1) .textOverflow({ overflow: TextOverflow.Ellipsis }) Text(this.nextDetail) .fontSize(11) .maxLines(1) .textOverflow({ overflow: TextOverflow.Ellipsis })玩法列表也限制注意事项最多两行Text(item.caution) .fontSize(Theme.caption) .fontColor(Theme.textMuted) .maxLines(2) .textOverflow({ overflow: TextOverflow.Ellipsis })长文本不是边缘情况。只要有用户输入就一定会遇到。发布前要专门测长名字、长备注、长症状。空态要像引导不要像错误喵汪星球很多模块都有空态没有宠物档案首页显示“先添加宠物”。没有任务提示先在“我的”添加宠物。没有录音翻译器提供试用样本。症状输入为空提示输入示例。卡片没有数据展示“先添加宠物”和“建档”。首页空态不是白板而是一个可操作入口if (!this.hasPetProfile) { return this.buildCareTask( empty-profile, 先添加宠物, 建档, 创建宠物档案后开始照护、记录和提醒, 待建档, primary, false ) }这类空态能降低新用户的迷茫感。很多应用留存差不是功能少而是第一次打开时不知道下一步点哪里。权限说明必须和使用场景绑定项目申请的权限{ name: ohos.permission.MICROPHONE, reason: $string:microphone_permission_reason }, { name: ohos.permission.READ_CALENDAR, reason: $string:calendar_permission_reason }, { name: ohos.permission.WRITE_CALENDAR, reason: $string:calendar_permission_reason }对应的文案{ name: microphone_permission_reason, value: 用于录制宠物叫声并在本机生成仅供娱乐参考的叫声翻译结果。 }, { name: calendar_permission_reason, value: 用于把照护项目同步到系统日历并生成提醒。 }权限申请时机也很重要麦克风点击开始录音时申请。日历开启系统日历同步时申请。图片选择头像时进入系统图片选择器。音频导入音频时进入系统音频选择器。不要启动 App 就弹一堆权限。用户还不知道你要做什么当然会拒绝。本地优先也要考虑备份项目配置了备份扩展{ allowToBackupRestore: true }并在module.json5中声明{ name: EntryBackupAbility, srcEntry: ./ets/entrybackupability/EntryBackupAbility.ets, type: backup, exported: false, metadata: [ { name: ohos.extension.backup, resource: $profile:backup_config } ] }当前EntryBackupAbility还是基础实现export default class EntryBackupAbility extends BackupExtensionAbility { async onBackup() { await Promise.resolve() } async onRestore(bundleVersion: BundleVersion) { const _bundleVersion: BundleVersion bundleVersion await Promise.resolve() } }这说明项目已经预留了备份能力。后续如果要做正式发布可以进一步梳理 Preferences、录音文件、头像文件的备份策略。离线优先不等于数据不重要。宠物档案和健康记录对用户来说是有价值的。发布前回归清单我会按真实用户路径检查而不是只按页面检查。首次启动无宠物、无任务、无历史时首页、我的、记录、健康、翻译器都能给出明确引导。建档流程新增、编辑、取消、头像选择、最多三只限制、切换宠物都正常。照护流程完成、延后、跳过、删除、自定义任务、前后日期切换都能持久化。日历流程授权、拒绝、提醒时间已过、编辑已同步任务、删除已同步任务都能正确反馈。翻译器流程权限拒绝、录音启动失败、短录音、正常录音、音频导入、内置样本、反馈记录都能走通。健康流程空输入、未匹配、高风险匹配、生成就医卡、无档案时引导建档。卡片流程无宠物、有宠物、未完成、已完成、点击进入我的、点击进入记录。暗色模式所有文本对比度可读边框、卡片、按钮不糊成一片。大屏横向留白正常底部导航不变形主要卡片不被拉得过宽。长文本长宠物名、长任务名、长备注、长症状输入不会撑坏布局。构建和发布材料开发收口时建议执行hvigorw assembleHap项目已有构建产物build/outputs/default/ miaowangxingqiu-default-signed.app miaowangxingqiu-default-unsigned.app发布前还要准备应用名称、图标、启动图。应用介绍和关键词。权限用途说明。隐私政策尤其说明录音、图片、健康记录默认本地保存。手机截图、平板截图。服务卡片截图。健康内容免责声明。应用市场上架资料。技术功能完成只是发布的一半。资料准备、合规文案、截图质量同样影响审核和转化。这套系列的亮点回顾喵汪星球虽然是一个单机养宠工具但覆盖了不少 HarmonyOS 实战能力Stage 模型和 UIAbility 生命周期。ArkUI 状态驱动多 Tab 页面。Preferences 复杂状态持久化。Photo Picker 和 Audio Picker。CalendarKit 系统日历同步。AVRecorder 录音与峰值采样。FormKit 服务卡片。AppStorage 跨入口状态联动。资源化主题和暗色模式。多设备响应式适配。健康内容的本地知识库和边界设计。更重要的是这些能力不是堆在一起而是围绕“离线养宠助手”的业务闭环组织起来的。本篇小结最后这一篇讲的都是细节但这些细节会决定应用质感主题要资源化不要页面里散落色值。明暗色用base/dark资源解决。大屏适配要控制横向留白。长文本必须做截断保护。空态要变成下一步引导。权限申请要绑定真实场景。本地数据要考虑备份和恢复。发布前要按用户路径做回归。到这里喵汪星球 HarmonyOS 技术实战系列就完整了。它不是一个孤立 API Demo而是一个从产品闭环、状态管理、系统能力到发布打磨的完整案例。对于想做 HarmonyOS 实战项目的同学这类“真实小应用”的含金量往往比照着文档调用一个 API 更高。
【喵汪星球HarmonyOS 6.0】技术实战 10:主题系统、暗色模式与发布前体验打磨
发布时间:2026/7/6 14:28:50
前言前面九篇已经把喵汪星球的核心功能拆完了档案、记录、日历、卡片、录音、陪玩、健康。最后这篇讲一个经常被低估的问题应用从“功能能跑”到“像一个完整产品”差的往往不是一个大 API而是主题、暗色模式、空态、长文本、权限说明、多设备布局和发布检查。这些东西不够炫但决定用户对 App 的第一印象。主题系统先行不要在页面里散落色值项目把颜色、字号、间距、圆角集中在Theme.etsexport class Theme { static readonly bg $r(app.color.mw_bg) static readonly bgElevated $r(app.color.mw_bg_elevated) static readonly surface $r(app.color.mw_surface) static readonly surfaceSoft $r(app.color.mw_surface_soft) static readonly surfaceWarm $r(app.color.mw_surface_warm) static readonly surfaceBlue $r(app.color.mw_surface_blue) static readonly textPrimary $r(app.color.mw_text_primary) static readonly textSecondary $r(app.color.mw_text_secondary) static readonly textMuted $r(app.color.mw_text_muted) static readonly primary $r(app.color.mw_primary) static readonly emergency $r(app.color.mw_emergency) static readonly warning $r(app.color.mw_warning) static readonly border $r(app.color.mw_border) static readonly radiusS: number 8 static readonly radiusM: number 10 static readonly radiusL: number 14 static readonly radiusXL: number 22 static readonly spaceS: number 8 static readonly spaceM: number 12 static readonly spaceL: number 16 static readonly spaceXL: number 24 static readonly title: number 28 static readonly headline: number 22 static readonly subhead: number 18 static readonly body: number 15 static readonly small: number 13 static readonly caption: number 12 }页面里只使用语义变量.backgroundColor(Theme.bg) .fontColor(Theme.textPrimary) .border({ width: 1, color: Theme.border }) .borderRadius(Theme.radiusL) .padding(Theme.spaceL)这个习惯越早建立越好。否则当你要适配暗色模式、服务卡片、大屏时会发现页面里到处都是硬编码色值改一次像考古。明暗色通过资源文件解决浅色资源在resources/base/element/color.json{ name: mw_bg, value: #FAF8F2 }, { name: mw_surface, value: #FFFFFF }, { name: mw_primary, value: #21665B }, { name: mw_emergency, value: #C95F57 }暗色资源在resources/dark/element/color.json{ name: mw_bg, value: #0D1211 }, { name: mw_surface, value: #18221F }, { name: mw_primary, value: #6DB8A6 }, { name: mw_emergency, value: #EF8D84 }同一个资源名在不同模式下取不同颜色。页面代码不用判断当前是不是暗色模式。入口 Ability 中也没有强制颜色而是让应用跟随系统this.context .getApplicationContext() .setColorMode(ConfigurationConstant.ColorMode.COLOR_MODE_NOT_SET)工具型 App 更适合跟随系统。用户开了暗色模式通常就是希望所有常用应用都别突然亮起来。多设备适配phone、tablet、2in1 不只是配置module.json5中声明deviceTypes: [ phone, tablet, 2in1 ]声明了不等于适配了。页面必须根据视口宽度调整布局。项目通过onAreaChange获取宽度.onAreaChange((_oldArea, newArea) { this.updateViewportWidth(newArea.width.toString()) })解析宽度private updateViewportWidth(widthText: string): void { const parsed Number(widthText.replace(vp, ).replace(px, )) if (!Number.isNaN(parsed) parsed 0) { this.viewportWidth parsed } }根据宽度调整页面边距private pageHorizontalPadding(): number { if (this.isLargeScreen()) { return 56 } return Theme.spaceL }大屏上最常见的错误是把手机卡片直接拉满屏幕。看起来不高级也不利于阅读。适当增加横向留白通常比强行做复杂分栏更稳。底部导航也要考虑主功能突出项目的底部导航没有简单五等分而是把“翻译器”做成中间主按钮Builder BottomNav() { Stack({ alignContent: Alignment.Top }) { Row() { this.NavItem(首页, $r(app.media.tab_home)) this.NavItem(记录, $r(app.media.tab_record)) Column().width(78) this.NavItem(健康, $r(app.media.tab_health)) this.NavItem(我的, $r(app.media.tab_profile)) } Column({ space: Theme.spaceXS }) { Stack() { Image($r(app.media.tab_translator_light)) } .width(62) .height(62) .backgroundColor(Theme.primary) .borderRadius(31) Text(翻译器) } .onClick(() { this.switchTab(翻译器) }) } }这不是纯装饰。宠物叫声分析是产品最有记忆点的功能中间突出按钮能让用户形成入口记忆。长文本保护正式产品的基本功用户输入和业务数据都不可控。比如宠物名字、任务标题、任务备注、症状描述、翻译结果都可能很长。服务卡片中就做了保护Text(this.nextTitle) .fontSize(20) .fontWeight(FontWeight.Bold) .maxLines(1) .textOverflow({ overflow: TextOverflow.Ellipsis }) Text(this.nextDetail) .fontSize(11) .maxLines(1) .textOverflow({ overflow: TextOverflow.Ellipsis })玩法列表也限制注意事项最多两行Text(item.caution) .fontSize(Theme.caption) .fontColor(Theme.textMuted) .maxLines(2) .textOverflow({ overflow: TextOverflow.Ellipsis })长文本不是边缘情况。只要有用户输入就一定会遇到。发布前要专门测长名字、长备注、长症状。空态要像引导不要像错误喵汪星球很多模块都有空态没有宠物档案首页显示“先添加宠物”。没有任务提示先在“我的”添加宠物。没有录音翻译器提供试用样本。症状输入为空提示输入示例。卡片没有数据展示“先添加宠物”和“建档”。首页空态不是白板而是一个可操作入口if (!this.hasPetProfile) { return this.buildCareTask( empty-profile, 先添加宠物, 建档, 创建宠物档案后开始照护、记录和提醒, 待建档, primary, false ) }这类空态能降低新用户的迷茫感。很多应用留存差不是功能少而是第一次打开时不知道下一步点哪里。权限说明必须和使用场景绑定项目申请的权限{ name: ohos.permission.MICROPHONE, reason: $string:microphone_permission_reason }, { name: ohos.permission.READ_CALENDAR, reason: $string:calendar_permission_reason }, { name: ohos.permission.WRITE_CALENDAR, reason: $string:calendar_permission_reason }对应的文案{ name: microphone_permission_reason, value: 用于录制宠物叫声并在本机生成仅供娱乐参考的叫声翻译结果。 }, { name: calendar_permission_reason, value: 用于把照护项目同步到系统日历并生成提醒。 }权限申请时机也很重要麦克风点击开始录音时申请。日历开启系统日历同步时申请。图片选择头像时进入系统图片选择器。音频导入音频时进入系统音频选择器。不要启动 App 就弹一堆权限。用户还不知道你要做什么当然会拒绝。本地优先也要考虑备份项目配置了备份扩展{ allowToBackupRestore: true }并在module.json5中声明{ name: EntryBackupAbility, srcEntry: ./ets/entrybackupability/EntryBackupAbility.ets, type: backup, exported: false, metadata: [ { name: ohos.extension.backup, resource: $profile:backup_config } ] }当前EntryBackupAbility还是基础实现export default class EntryBackupAbility extends BackupExtensionAbility { async onBackup() { await Promise.resolve() } async onRestore(bundleVersion: BundleVersion) { const _bundleVersion: BundleVersion bundleVersion await Promise.resolve() } }这说明项目已经预留了备份能力。后续如果要做正式发布可以进一步梳理 Preferences、录音文件、头像文件的备份策略。离线优先不等于数据不重要。宠物档案和健康记录对用户来说是有价值的。发布前回归清单我会按真实用户路径检查而不是只按页面检查。首次启动无宠物、无任务、无历史时首页、我的、记录、健康、翻译器都能给出明确引导。建档流程新增、编辑、取消、头像选择、最多三只限制、切换宠物都正常。照护流程完成、延后、跳过、删除、自定义任务、前后日期切换都能持久化。日历流程授权、拒绝、提醒时间已过、编辑已同步任务、删除已同步任务都能正确反馈。翻译器流程权限拒绝、录音启动失败、短录音、正常录音、音频导入、内置样本、反馈记录都能走通。健康流程空输入、未匹配、高风险匹配、生成就医卡、无档案时引导建档。卡片流程无宠物、有宠物、未完成、已完成、点击进入我的、点击进入记录。暗色模式所有文本对比度可读边框、卡片、按钮不糊成一片。大屏横向留白正常底部导航不变形主要卡片不被拉得过宽。长文本长宠物名、长任务名、长备注、长症状输入不会撑坏布局。构建和发布材料开发收口时建议执行hvigorw assembleHap项目已有构建产物build/outputs/default/ miaowangxingqiu-default-signed.app miaowangxingqiu-default-unsigned.app发布前还要准备应用名称、图标、启动图。应用介绍和关键词。权限用途说明。隐私政策尤其说明录音、图片、健康记录默认本地保存。手机截图、平板截图。服务卡片截图。健康内容免责声明。应用市场上架资料。技术功能完成只是发布的一半。资料准备、合规文案、截图质量同样影响审核和转化。这套系列的亮点回顾喵汪星球虽然是一个单机养宠工具但覆盖了不少 HarmonyOS 实战能力Stage 模型和 UIAbility 生命周期。ArkUI 状态驱动多 Tab 页面。Preferences 复杂状态持久化。Photo Picker 和 Audio Picker。CalendarKit 系统日历同步。AVRecorder 录音与峰值采样。FormKit 服务卡片。AppStorage 跨入口状态联动。资源化主题和暗色模式。多设备响应式适配。健康内容的本地知识库和边界设计。更重要的是这些能力不是堆在一起而是围绕“离线养宠助手”的业务闭环组织起来的。本篇小结最后这一篇讲的都是细节但这些细节会决定应用质感主题要资源化不要页面里散落色值。明暗色用base/dark资源解决。大屏适配要控制横向留白。长文本必须做截断保护。空态要变成下一步引导。权限申请要绑定真实场景。本地数据要考虑备份和恢复。发布前要按用户路径做回归。到这里喵汪星球 HarmonyOS 技术实战系列就完整了。它不是一个孤立 API Demo而是一个从产品闭环、状态管理、系统能力到发布打磨的完整案例。对于想做 HarmonyOS 实战项目的同学这类“真实小应用”的含金量往往比照着文档调用一个 API 更高。