鸿蒙原生项目实战(五):深色主题适配与设置页功能完善

收官之作!本文覆盖鸿蒙深色模式适配机制、设置页完整实现、数据备份扩展、编码规范与性能优化,助你交付一个生产级品质的鸿蒙应用。


一、前言

一个完整的鸿蒙应用,不仅要功能可用,还要有良好的用户体验系统集成度。本文涉及三个核心主题:

  1. 深色主题适配 — 利用鸿蒙资源限定词机制,零代码实现暗黑模式
  2. 设置页面实现 — 信息展示 + 数据管理 + 使用说明
  3. 备份扩展能力 — 系统级备份恢复集成
  4. 项目回顾 — 编码规范、性能优化、常见坑点

二、鸿蒙深色主题适配机制

2.1 资源限定词(Resource Qualifiers)

HarmonyOS 的资源限定词机制参照了 Android 的配置限定符概念。通过在 resources/ 下建立特定目录,同名资源会在对应条件下自动覆盖。

entry/src/main/resources/
├── base/                       # 默认资源(所有条件下生效)
│   ├── element/
│   │   ├── color.json
│   │   ├── float.json
│   │   └── string.json
│   ├── media/
│   └── profile/
├── dark/                       # 深色模式限定词
│   └── element/
│       └── color.json
└── rawfile/

2.2 深色颜色定义

dark/element/color.json

{
  "color": [
    {
      "name": "start_window_background",
      "value": "#000000"
    }
  ]
}

为什么只有 start_window_background?

这是初始版本,只覆盖了启动窗口背景色。实际项目应该覆盖完整的颜色系统:

{
  "color": [
    { "name": "start_window_background", "value": "#000000" },
    { "name": "bg_page",                "value": "#1A1A2E" },
    { "name": "card_bg",                "value": "#16213E" },
    { "name": "text_primary",           "value": "#EEEEEE" },
    { "name": "text_secondary",          "value": "#AAAAAA" },
    { "name": "divider",                "value": "#333333" }
  ]
}

2.3 引用方式

// 正确方式:通过 $r() 引用资源
.backgroundColor($r('app.color.bg_page'))
.fontColor($r('app.color.text_primary'))

// 错误方式:硬编码颜色(不会跟随深色模式切换)
.backgroundColor('#F0F4F8')
.fontColor('#2D3436')

2.4 深色模式生命周期

EntryAbility.ets 中:

onCreate(want: Want, launchParam: AbilityConstant.LaunchParam): void {
  this.context.getApplicationContext()
    .setColorMode(ConfigurationConstant.ColorMode.COLOR_MODE_NOT_SET);
}

COLOR_MODE_NOT_SET 表示跟随系统设置。其他选项:

模式 行为
COLOR_MODE_NOT_SET 跟随系统(推荐)
COLOR_MODE_LIGHT 强制浅色
COLOR_MODE_DARK 强制深色
COLOR_MODE_AUTO 自动(根据时间)

2.5 验证方法

在 DevEco Studio 中,点击工具栏的 深色模式预览 图标,或在模拟器的系统设置中切换深色模式,即可实时查看效果。

如果有颜色没有正确切换,检查点:

  1. ✅ 是否全部使用了 $r('app.color.xxx') 引用而非硬编码?
  2. dark/element/color.json 中是否定义了同名的颜色?
  3. ✅ 自定义组件中的颜色是否也通过 @State$r() 引用?

三、设置页面实现

Settings.ets 是一个信息型页面,展示应用信息并提供数据管理功能。

3.1 页面状态

@Entry
@Component
struct Settings {
  @State totalHabits: number = 0;
  @State totalRecords: number = 0;

  private habitManager: HabitManager = HabitManager.getInstance();

  aboutToAppear(): void {
    this.loadStats();
  }

  async loadStats(): Promise<void> {
    const habits = await this.habitManager.getAllHabits();
    const records = await this.habitManager.getAllRecords();
    this.totalHabits = habits.length;
    this.totalRecords = records.length;
  }
}

3.2 UI 布局

Column
├── 顶部导航(← 返回 | 设置)
├── Scroll
│   ├── 应用信息卡片
│   │   ├── 🎯 习惯大师
│   │   ├── 每日习惯打卡与追踪助手
│   │   ├── 📋 习惯总数: N 个
│   │   ├── ✅ 总打卡次数: N 次
│   │   ├── 📱 应用版本: 1.0.0
│   │   └── 🎯 API 版本: API 23
│   ├── 数据管理卡片
│   │   ├── 说明文字
│   │   └── 🗑️ 重置所有数据(红色按钮)
│   └── 使用说明卡片
│       ├── 1. 点击「+新习惯」创建习惯
│       ├── 2. 在首页点击圆形区域打卡
│       ├── 3. 点击习惯卡片查看详情和日历
│       ├── 4. 在统计页面查看完成率趋势
│       └── 5. 在习惯详情页可删除习惯

3.3 应用信息卡片

@Builder
infoRow(icon: string, label: string, value: string) {
  Row() {
    Text(icon).fontSize(18).margin({ right: 12 });
    Text(label).fontSize(15).fontColor($r('app.color.text_primary')).layoutWeight(1);
    Text(value).fontSize(14).fontColor($r('app.color.text_secondary'));
  }
  .width('100%')
  .padding({ top: 6, bottom: 6 });
}

3.4 数据重置功能

resetAllData(): void {
  AlertDialog.show({
    title: '确认重置',
    message: '确定要清除所有习惯和打卡数据吗?\n此操作不可恢复!',
    primaryButton: {
      value: '取消',
      action: () => {}
    },
    secondaryButton: {
      value: '确认清除',
      fontColor: '#FF6B6B',  // 红色文字强调危险操作
      action: () => {
        this.doReset();
      }
    }
  });
}

async doReset(): Promise<void> {
  await this.habitManager.clearAll();
  await this.loadStats();
  AlertDialog.show({
    title: '已重置',
    message: '所有数据已清除',
    confirm: { value: '确定', action: () => {} }
  });
}

安全设计要点

设计 说明
二次确认弹窗 防止误触
红色强调按钮 视觉提示危险性
操作后刷新 UI 立即反映数据变化
操作后反馈 弹窗告知结果

3.5 使用说明卡片

Text('1. 点击首页底部「+新习惯」创建习惯')
  .fontSize(14).fontColor($r('app.color.text_primary'))
  .alignSelf(ItemAlign.Start).margin({ bottom: 6 });
Text('2. 在首页点击圆形区域打卡')
  .fontSize(14).fontColor($r('app.color.text_primary'))
  .alignSelf(ItemAlign.Start).margin({ bottom: 6 });
// ...

四、备份扩展能力

4.1 什么是 ExtensionAbility?

ExtensionAbility 是鸿蒙系统的扩展机制,允许应用在特定场景下运行后台任务。EntryBackupAbility 就是其中之一,用于系统备份和恢复。

4.2 注册扩展

module.json5 中注册:

"extensionAbilities": [
  {
    "name": "EntryBackupAbility",
    "srcEntry": "./ets/entrybackupability/EntryBackupAbility.ets",
    "type": "backup",
    "exported": false,
    "metadata": [
      {
        "name": "ohos.extension.backup",
        "resource": "$profile:backup_config"
      }
    ]
  }
]

4.3 实现备份/恢复

import { BackupExtensionAbility, BundleVersion } from '@kit.CoreFileKit';

export default class EntryBackupAbility extends BackupExtensionAbility {
  async onBackup() {
    hilog.info(0x0000, 'testTag', 'onBackup ok');
    await Promise.resolve();
  }

  async onRestore(bundleVersion: BundleVersion) {
    hilog.info(0x0000, 'testTag', 'onRestore ok %{public}s', JSON.stringify(bundleVersion));
    await Promise.resolve();
  }
}

当用户在「设置 → 系统和更新 → 备份与恢复」中触发备份操作时,系统会自动调用 onBackup() 方法。

4.4 备份配置

resources/base/profile/backup_config.json 定义了哪些文件需要备份:

{
  "allowToBackup": true,
  "includes": [
    "data/storage/el2/base/haps/entry/preferences/habit_store"
  ]
}

⚠️ Preferences 默认存储路径在 el2 分区,需要明确配置才能被系统备份工具识别。


五、项目编码规范回顾

5.1 目录规范

ets/
├── entryability/         # Ability 层(生命周期)
├── entrybackupability/   # 扩展能力(备份)
├── model/                # 数据模型 + 业务逻辑
└── pages/                # 页面组件

5.2 命名规范

类型 规范 示例
文件 PascalCase DataManager.ets
PascalCase HabitManager
接口 PascalCase HabitRecord
枚举 PascalCase HabitCategory
函数 camelCase getToday()
变量 camelCase totalHabits
常量 UPPER_SNAKE KEY_HABITS
组件 PascalCase Index

5.3 导入规范

// 系统 Kit 导入
import { router } from '@kit.ArkUI';
import { preferences } from '@kit.DataKit';

// 项目模块导入
import { HabitManager } from '../model/DataManager';

// 数据模型导入
import { Habit, HabitCategory, ALL_CATEGORIES } from '../model/FinanceData';

六、常见坑点与解决方案

6.1 Preferences 内存泄漏

问题:每次操作都调用 getPreferences 打开新实例

解决:缓存 Promise,复用同一个 Preferences 实例:

private preferencesPromise: Promise<preferences.Preferences> | null = null;

private async getStore(): Promise<preferences.Preferences> {
  if (!this.preferencesPromise) {
    this.preferencesPromise = preferences.getPreferences(this.context, STORE_NAME);
  }
  return this.preferencesPromise;
}

6.2 事件冒泡导致导航冲突

问题:打卡区域的 onClick 冒泡到卡片容器的 onClick,导致点击打卡同时跳转详情

解决:使用 event.stopPropagation()

Column()
  .onClick((event: ClickEvent) => {
    event.stopPropagation();
    this.toggleCheck(card);
  })

6.3 ForEach 缺少 key

问题:列表增删时 ArkUI 难以做 diff 更新

解决:给 ForEach 提供 key 生成函数:

ForEach(this.habitCards, 
  (card: HabitCardInfo) => { this.habitCard(card); },
  (card: HabitCardInfo) => card.habit.id  // ⬅ key
)

6.4 页面路由未注册

问题:新增页面后未在 main_pages.json 注册,运行时报错找不到页面

解决:新增页面后立即在配置中添加:

{
  "src": [
    "pages/Index",
    "pages/AddHabit",
    "pages/HabitDetail",
    "pages/Statistics",
    "pages/Settings"
  ]
}

七、性能优化清单

优化项 当前状态 建议
ForEach key ⚠️ 未提供 添加 key 生成器
事件冒泡处理 ⚠️ 未处理 添加 stopPropagation
深色模式完整覆盖 ⚠️ 不完整 补充全部颜色资源
列表增量刷新 ✅ N/A(数据量小) 暂可不优化
图片资源压缩 ✅ 无大图 保持
@State 粒度 ✅ 合理 保持
async 错误处理 ⚠️ 部分缺失 建议统一 catch
首屏加载优化 ✅ 轻量数据 保持

八、项目全貌

8.1 应用架构图

┌──────────────────────────────────────┐
│          EntryAbility                │
│   (生命周期管理 / Context 初始化)      │
├──────────────────────────────────────┤
│              Pages                   │
│  ┌─────┬───────┬──────┬──────┬────┐ │
│  │Index│AddHabit│Detail│Statis│Set.│ │
│  └──┬──┴───┬───┴──┬───┴──┬───┴──┬─┘ │
│     │      │      │      │      │    │
├─────┴──────┴──────┴──────┴──────┴────┤
│         HabitManager (单例)           │
│   CRUD / 统计 / 连续天数 / 分类分布    │
├──────────────────────────────────────┤
│     Preferences (轻量持久化)           │
│     habits[] + records[] → JSON      │
└──────────────────────────────────────┘

8.2 数据流

用户操作 → 页面事件 → HabitManager.xxx()
                                    ↓
                   更新 Preferences(flush)
                                    ↓
                   返回数据到页面
                                    ↓
                   @State 变量更新
                                    ↓
                   UI 自动渲染

九、发布与部署

9.1 构建 Release 包

DevEco Studio → Build → Build Hap(s)/App(s) → Release

9.2 签名配置

build-profile.json5 中配置签名信息(需先在 DevEco Studio 中申请证书和密钥):

"signingConfigs": [
  {
    "name": "default",
    "material": {
      "certificatePath": ".../debug.cer",
      "keyStorePath": ".../debug.p12",
      "keyStorePassword": "******",
      "keyAlias": "key0",
      "keyAliasPassword": "******"
    }
  }
]

9.3 上架华为应用市场

  1. 在 AppGallery Connect 创建应用
  2. 上传 HAP 包
  3. 填写应用描述、分类、隐私说明
  4. 提交审核

十、总结

通过五篇文章,我们完整走完了鸿蒙原生应用「习惯大师」的全开发流程:

篇章 核心内容
第一篇 项目初始化、工程架构、核心配置
第二篇 数据模型、Preferences 持久化、单例 Manager
第三篇 首页 UI、声明式布局、打卡交互
第四篇 统计图表、日历热力图、连续天数算法
第五篇 深色主题、设置页、备份扩展、编码规范

在这里插入图片描述
在这里插入图片描述

推荐学习路径

  1. 📘 HarmonyOS 官方开发文档
  2. 🎓 ArkTS 语言基础(TypeScript 语法 + 声明式 UI)
  3. 🔧 实践本文项目 → 尝试添加新功能
  4. 🚀 接入更多系统能力(通知、传感器、地图等)

本系列完结。感谢阅读!如果对本文有任何疑问,欢迎在评论区留言交流。

关于作者:一名专注于鸿蒙原生开发的技术博主,持续分享 HarmonyOS NEXT 实战经验。

Logo

作为“人工智能6S店”的官方数字引擎,为AI开发者与企业提供一个覆盖软硬件全栈、一站式门户。

更多推荐