HarmonyOS APP《画伴梦工厂》开发第7篇:资源管理与国际化——`$r` 与 `resource` 体系

第1.7篇:资源管理与国际化------$rresource 体系

难度 :⭐ 入门 | 前置知识:1.2 ArkUI 声明式 UI 基础

涉及源文件

  • products/default/src/main/ets/pages/Index.ets
  • products/default/src/main/ets/constants/CommonConstants.ets
  • products/default/src/main/ets/viewmodel/CatalogueItemData.ets

1. 引言

在鸿蒙应用中,图片、字符串、颜色、多媒体等统称为"资源"。ArkUI 提供了一套完善的资源管理体系,支持编译时资源检查、多设备适配和多语言国际化。本篇将以「画伴梦工厂」项目为例,介绍 $r()$rawfile()Resource 类型的用法。

2. resources 目录结构

鸿蒙应用的资源文件统一存放在 resources 目录下,按类型和设备类型组织:

复制代码
resources/
├── base/                    # 默认资源(所有设备通用)
│   ├── element/             # 基础元素(字符串、颜色、浮点数等 JSON)
│   │   ├── string.json
│   │   ├── color.json
│   │   └── float.json
│   ├── media/               # 图片/音频等媒体文件
│   │   ├── hero_portal.png
│   │   ├── fantasy_world.svg
│   │   └── kid_dino_sketch.svg
│   └── profile/             # 配置文件
│       ├── main_pages.json
│       └── form_config.json
├── dark/                    # 深色模式覆盖
│   └── element/
│       └── color.json
├── en/                      # 英文国际化(若有)
│   └── element/
│       └── string.json
└── rawfile/                 # 原始文件(不经过编译处理)
    └── index/
        ├── demo1.png
        ├── demo1.mp4
        └── ...

关键概念

  • base 目录是兜底的资源目录,所有设备都会加载
  • darken 等限定词目录只在对应条件下覆盖 base 中的同名字资源
  • media 下的资源会被编译系统处理(压缩、格式转换等)
  • rawfile 下的文件保持原始状态,不做任何处理

3. $r()$rawfile() 的使用

ArkUI 提供了两种引用资源的方式:$r()$rawfile()

3.1 $r():引用编译时资源

$r() 用于引用 elementmedia 目录下的资源,语法为 $r('app.type.name')

typescript 复制代码
// 引用 media 下的图片
Image($r('app.media.hero_portal'))
  .width('100%')
  .objectFit(ImageFit.Cover)
  .borderRadius(26)

// 引用 element/string.json 中的字符串
Text($r('app.string.module_desc'))

// 引用 element/color.json 中的颜色
.backgroundColor($r('app.color.primary'))

$r() 返回的是 Resource 类型,它携带了资源的 ID 而非实际值。这意味着:

  1. 编译时检查:如果资源不存在,编译就会报错
  2. 多设备适配:系统会根据当前设备自动加载对应限定词的资源
  3. 延迟解析:资源值在运行时才解析,支持主题切换

3.2 $rawfile():引用原始文件

$rawfile() 用于引用 rawfile 目录下的资源,这类资源不会被编译处理:

typescript 复制代码
// 引用 rawfile 下的演示图片
private getWorkCover(index: number): Resource | string {
  switch (index) {
    case 0: return $rawfile('index/demo1.png');
    case 1: return $rawfile('index/demo2.png');
    case 2: return $rawfile('index/demo3.png');
    case 3: return $rawfile('index/demo4.png');
    default: return $rawfile('index/demo5.png');
  }
}

// 引用 rawfile 下的演示视频
private getWorkVideo(index: number): Resource | string {
  switch (index) {
    case 0:
    case 3: return $rawfile('index/demo1.mp4');
    case 1:
    case 4: return $rawfile('index/demo2.mp4');
    default: return $rawfile('index/demo3.mp4');
  }
}

3.3 $r() vs $rawfile() 对比

对比维度 $r() $rawfile()
资源目录 media/element/ rawfile/
编译处理 压缩、格式转换 保持原样
多语言适配 自动根据限定词加载 不支持
深色模式切换 支持(通过 color.json) 不支持
类型返回 Resource Resource
使用场景 图标、字符串、颜色 视频、PDF、GLTF 模型
编译时检查 检查资源是否存在 不检查

4. Resource 类型在数据模型中的应用

Resource 类型可以像普通类型一样用于数据模型字段,这使得组件能够延迟解析资源值:

typescript 复制代码
// CatalogueItemData.ets
export class CatalogueItemData {
  id: number = 0;
  title: Resource = $r('app.string.responsive_layout'); // Resource 类型字段
  uri: string = '';
  params?: string;

  constructor(id: number, title: Resource, uri: string, params?: string) {
    this.id = id;
    this.title = title;
    this.uri = uri;
    this.params = params;
  }
}

在组件中直接使用:

typescript 复制代码
// 假设有一个 GridItem 接收 CatalogueItemData
GridItem() {
  Text(item.title) // title 是 Resource 类型,Text 组件原生支持
    .fontSize(14)
}

注意Resource 类型不是 string,不能直接用 + 拼接或模板字符串。如果需要展示资源对应的文本内容,可以使用 getContext().resourceManager.getStringSync() 解析。

5. 项目中资源使用规模统计

在「画伴梦工厂」项目中,资源体系的使用情况:

media 资源(通过 $r() 引用):

资源名称 类型 用途
hero_portal.png PNG 首页 Hero 区背景图
fantasy_world.svg SVG 我的世界页面插图
fantasy_worldNew.png PNG 世界页顶部大图
kid_dino_sketch.svg SVG 画板区示例涂鸦
dino_animation.svg SVG 世界故事区插图
tab1.svg ~ tab4.svg SVG Tab 图标
background.png PNG 背景图片
startIcon.png PNG 启动图标

rawfile 资源(通过 $rawfile() 引用):

文件 类型 用途
index/demo1.png ~ demo5.png PNG 5 个演示作品的封面图
index/demo1.mp4 ~ demo5.mp4 MP4 演示作品的动画视频
assets/gltf/car.glb GLB 3D 模型测试文件

element 资源:

JSON 文件 内容
string.json 应用名称、权限说明、布局页面标题等 14 条字符串
color.json 颜色常量
float.json 浮点数常量
dark/element/color.json 深色模式颜色覆盖

6. 通过 resources 实现国际化

鸿蒙的资源系统天然支持国际化。实现思路非常简单:

6.1 目录结构

复制代码
resources/
├── base/element/string.json     # 默认语言(中文)
├── en/element/string.json       # 英文覆盖
├── ja/element/string.json       # 日文覆盖
└── ...

6.2 字符串定义

base/element/string.json 中定义默认(中文)字符串:

json 复制代码
{
  "string": [
    {
      "name": "module_desc",
      "value": "module description"
    },
    {
      "name": "ability_label",
      "value": "画伴梦工厂"
    },
    {
      "name": "camera_reason",
      "value": "用于拍摄儿童的绘画作品,在本地完成识别和动画生成。"
    },
    {
      "name": "microphone_reason",
      "value": "用于录制语音描述,辅助创作生成。"
    }
  ]
}

en/element/string.json 中提供英文版本:

json 复制代码
{
  "string": [
    {
      "name": "module_desc",
      "value": "module description"
    },
    {
      "name": "ability_label",
      "value": "Drawing Dream Factory"
    },
    {
      "name": "camera_reason",
      "value": "Used to capture children's drawings for local recognition and animation generation."
    },
    {
      "name": "microphone_reason",
      "value": "Used to capture voice descriptions for creative generation."
    }
  ]
}

6.3 代码中使用

在代码中始终通过 $r() 引用字符串资源,系统会根据当前语言环境自动选择:

typescript 复制代码
// 不需要写 if-else,系统自动切换
Text($r('app.string.ability_label'))
  .fontSize(20)
  .fontWeight(FontWeight.Bold)

注意string.json 中的 name 必须全局唯一,建议使用 模块名_用途 的命名风格。

7. 项目中的主题色管理

除了字符串和图片,项目中还大量使用了组件级常量来管理颜色主题:

typescript 复制代码
// Index.ets 中的组件内常量
private readonly brandPurple: string = '#7657F3';
private readonly ink: string = '#1E2442';
private readonly mint: string = '#42CDA3';
private readonly sunshine: string = '#FFB84D';
private readonly softBackground: string = '#F7F5FF';

这些常量在 @Builder 中广泛使用:

typescript 复制代码
@Builder
private NavIcon(index: number) {
  Column() {
    Text(NAV_ICONS[index])
      .fontColor(this.isPrimaryTabActive(index) ? this.brandPurple : '#8E94A6')
    Text(NAV_ITEMS[index])
      .fontColor(this.isPrimaryTabActive(index) ? this.brandPurple : '#747A92')
  }
  .backgroundColor(this.isPrimaryTabActive(index) ? '#FFFFFF' : Color.Transparent)
}

对比 :把颜色放在 element/color.json 中通过 $r() 引用,可以实现深色模式自动切换。组件内常量则适合那些在各组件间共享、但不需要随主题变化的颜色值。

8. 小结

本篇学习了鸿蒙资源管理体系的核心内容:

知识点 说明
resources 目录结构 base/限定词 + element/media/rawfile
$r('app.type.name') 引用编译时资源,支持多语言和主题切换
$rawfile('path/file') 引用原始文件,保持文件原样
Resource 类型 可延迟解析的资源引用,可用作数据模型字段
国际化实现 添加对应语言的限定词目录,覆盖 string.json
组件内常量 管理主题色等不随语言/主题变化的值

下篇预告 :第1.8篇 组合式 API 实践------从零搭建一个 Tab 主页。我们将综合运用前面 7 篇文章的知识,分析 Index.ets 如何组合使用 @State@BuilderRouter、多 Tab 切换等构建完整主页。


思考题 :项目中 Index.ets 里有大量硬编码的中文文本(如 '首页''创作'),如果要做国际化,需要把这些文本都迁移到 string.json 中。你能估算一下,完成全面的国际化大概需要迁移多少条字符串吗?