一行 Image 背后的四份资源:HarmonyOS 6 限定符练习
1. 前言
同一张明信片,白天想用浅色天空,夜间想换成深色山景;放到二合一设备上,还想多留一点横向风景。第一反应可能是在页面里加几个判断,再给 Image 传不同路径。图少时还能记住,页面一多,条件就容易散到各个角落。
这次把选择交给资源目录。页面只引用 app.media.postcard,标题只引用 app.string.postcard_scene。名字不变,不同限定符目录提供不同内容,让资源管理器根据运行设备和外观配置匹配。
2. 限定符目录
| 目录 | 作用 | 图片与文案 |
| base | 默认版本 | 默认日景 |
| dark | 深色外观版本 | 默认夜景 |
| 2in1 | 二合一设备版本 | 二合一日景 |
| 2in1-dark | 二合一设备的深色版本 | 二合一夜景 |
每个目录下面都有 media/postcard.svg 和对应的 element/string.json。限定符属于目录名,资源文件名仍然相同,不需要把文件改成 postcard_pc_dark 之类的一长串名字。
base 不能省。它负责提供基础资源,也承担无法匹配到特定版本时的回退。特定目录只放需要变化的内容,不必把应用全部资源复制四遍;复制得越多,后面改一个公共文案越容易遗漏。
本例组合目录使用 2in1-dark,设备类型限定符在前,颜色模式限定符在后。目录名不是随意拼接的标签,顺序不符合资源规则会在打包阶段报错。遇到这种错误,应先核对限定符目录,而不是改 Image 的引用来绕过去。
示例里的二合一版本还增加了前景树木,默认版本使用水面细节。日夜版本改变天空和远山颜色,但仍保留相同的资源用途。这样,不同资源不是为了凑目录而复制四份同样的文件,读代码和看运行结果都能理解它们各自负责的变化。
可以把资源引用想成一张索引卡:页面写的是用途相同的名字,目录补充适用条件。以后更换二合一夜景,只修改那一份对应文件,引用它的页面保持原样。这个组织方式的好处,在第四张图出现时就能看出来——页面没有再增加第四个文件路径判断。
2.1 四组同名资源
下面按目录列出本例使用的文件。默认字符串表保留模板原有的应用名称和描述,增加 postcard_scene;不要只留下新增文案,把入口标签等原资源删掉。其他限定目录只定义需要覆盖的场景文案即可。
resources/base/media/postcard.svg:
<svg xmlns="http://www.w3.org/2000/svg" width="1000" height="550" viewBox="0 0 1000 550"><rect width="1000" height="550" fill="#C7DFDB"/><circle cx="790" cy="120" r="55" fill="#EED499"/><path d="M0 360L220 170L480 390L720 200L1000 350V550H0Z" fill="#6D9D83"/><path d="M0 450L280 330L560 470L820 330L1000 420V550H0Z" fill="#496F62"/><path d="M450 550L620 360L680 385L570 550Z" fill="#DBD3B1"/><ellipse cx="250" cy="475" rx="170" ry="25" fill="#8CB4AB"/></svg>
resources/base/element/string.json:
{
"string": [
{
"name": "module_desc",
"value": "module description"
},
{
"name": "EntryAbility_desc",
"value": "description"
},
{
"name": "EntryAbility_label",
"value": "四时明信片"
},
{
"name": "postcard_scene",
"value": "默认日景 · 山野来信"
}
]
}
resources/dark/media/postcard.svg:
<svg xmlns="http://www.w3.org/2000/svg" width="1000" height="550" viewBox="0 0 1000 550"><rect width="1000" height="550" fill="#182D47"/><circle cx="790" cy="120" r="55" fill="#E8E9CA"/><path d="M0 360L220 170L480 390L720 200L1000 350V550H0Z" fill="#34556B"/><path d="M0 450L280 330L560 470L820 330L1000 420V550H0Z" fill="#496F62"/><path d="M450 550L620 360L680 385L570 550Z" fill="#DBD3B1"/><ellipse cx="250" cy="475" rx="170" ry="25" fill="#8CB4AB"/></svg>
resources/dark/element/string.json:
{
"string": [
{
"name": "postcard_scene",
"value": "默认夜景 · 山野来信"
}
]
}
resources/2in1/media/postcard.svg:
<svg xmlns="http://www.w3.org/2000/svg" width="1000" height="550" viewBox="0 0 1000 550"><rect width="1000" height="550" fill="#C7DFDB"/><circle cx="790" cy="120" r="55" fill="#EED499"/><path d="M0 360L220 170L480 390L720 200L1000 350V550H0Z" fill="#6D9D83"/><path d="M0 450L280 330L560 470L820 330L1000 420V550H0Z" fill="#496F62"/><path d="M450 550L620 360L680 385L570 550Z" fill="#DBD3B1"/><path d="M110 420L150 290L190 420ZM180 440L220 310L260 440Z" fill="#2E6054"/></svg>
resources/2in1/element/string.json:
{
"string": [
{
"name": "postcard_scene",
"value": "二合一日景 · 山野来信"
}
]
}
resources/2in1-dark/media/postcard.svg:
<svg xmlns="http://www.w3.org/2000/svg" width="1000" height="550" viewBox="0 0 1000 550"><rect width="1000" height="550" fill="#182D47"/><circle cx="790" cy="120" r="55" fill="#E8E9CA"/><path d="M0 360L220 170L480 390L720 200L1000 350V550H0Z" fill="#34556B"/><path d="M0 450L280 330L560 470L820 330L1000 420V550H0Z" fill="#496F62"/><path d="M450 550L620 360L680 385L570 550Z" fill="#DBD3B1"/><path d="M110 420L150 290L190 420ZM180 440L220 310L260 440Z" fill="#2E6054"/></svg>
resources/2in1-dark/element/string.json:
{
"string": [
{
"name": "postcard_scene",
"value": "二合一夜景 · 山野来信"
}
]
}
3. 固定资源名的页面实现
页面 pages/Index.ets 的全部代码如下。Image 与 Text 引用的资源名固定,外观按钮只改变应用颜色模式,选择匹配文件的工作仍然属于资源管理器。
import { deviceInfo } from '@kit.BasicServicesKit';
import { common, ConfigurationConstant } from '@kit.AbilityKit';
@Entry
@Component
struct Index {
@State private sent: boolean = false;
@State private dark: boolean = false;
private toggle(): void {
this.dark = !this.dark;
(this.getUIContext().getHostContext() as common.UIAbilityContext).getApplicationContext().setColorMode(
this.dark ? ConfigurationConstant.ColorMode.COLOR_MODE_DARK : ConfigurationConstant.ColorMode.COLOR_MODE_LIGHT);
}
build() {
Scroll() {
Column({ space: 22 }) {
Row() {
Text('四时明信片').fontSize(28).fontWeight(FontWeight.Bold).fontColor('#384449');
Blank();
Text('POSTCARD').fontSize(12).letterSpacing(3).fontColor('#80938F');
}.width('100%');
Image($r('app.media.postcard')).width('100%').aspectRatio(1.8).objectFit(ImageFit.Contain)
.backgroundColor('#DDE7DE').borderRadius(18).id('postcard');
Text($r('app.string.postcard_scene')).fontSize(24).fontWeight(FontWeight.Bold).fontColor('#3F5E53').width('100%').id('scene');
Text('同一张卡片,同一个资源名。设备类型与外观改变时,由资源管理器挑选匹配版本。')
.fontSize(18).lineHeight(29).fontColor('#5F6F67').width('100%');
Row({ space: 12 }) {
Text(`运行设备类型:${deviceInfo.deviceType}`).fontSize(14).fontColor('#6A7C71').id('deviceType');
Blank();
}.width('100%');
Button(this.dark ? '切换日景' : '切换夜景').id('sceneToggle').backgroundColor('#4B7360').onClick(() => this.toggle());
Button(this.sent ? '已放入寄送清单' : '收下这张明信片').id('send').backgroundColor('#A2835D')
.onClick(() => { this.sent = !this.sent; });
Text('图形资源为示例内的 SVG,运行时按限定符自动选择。').fontSize(12).fontColor('#80938F').width('100%');
}.padding(28).constraintSize({ maxWidth: 980 }).width('100%');
}.width('100%').height('100%').backgroundColor('#F4F3EA');
}
}

资源文件放对目录,并不代表页面一定使用了它。若 Image 仍然写着 rawfile 下的固定路径,新增的限定版本就不会参与这次引用。排查时可以沿着 $r 的资源名,确认默认资源、特定目录和最终组件使用的是同一个名字。
还有一条很实用的区分:rawfile 适合按原始文件组织内容,资源限定符参与的是资源表匹配。把图片放进 rawfile,再在代码里拼出 dark 目录名,并不会自动获得这里的选择机制。需要哪一种能力,应先确定,再决定文件放置方式。
还要照顾资源尺寸和显示方式。本例使用固定比例的展示区域与 Contain,不裁掉图形边缘。真实照片或封面若要铺满区域,可以考虑 Cover,但需要检查主体是否被裁切。限定符只决定拿哪份资源,图片在容器里如何显示仍由组件属性决定。
3.1 设备条件与窗口尺寸
资源限定符适合“同一种用途,在不同运行条件下需要不同内容”的情况,例如外观配色、插画、图标和本地化文案。它不会自动把一列布局改成三列,也不会替页面判断当前自由窗口究竟有多宽。
二合一设备仍然可能打开一个很窄的窗口。设备类型没有变,2in1 资源仍会匹配,但排版应继续看容器尺寸。反过来,手机横屏后可用空间增大,也不意味着它变成了二合一设备。资源选择与布局适配可以配合使用,判断依据却不能混为一谈。

4. 日夜资源匹配结果

演示来自 HarmonyOS 6.1.1、API 24 的二合一模拟器。页面显示实际设备类型,再通过应用外观设置切换日景与夜景。下方文案会出现“二合一日景”或“二合一夜景”,便于确认拿到的是哪个资源版本,而不是只凭天空颜色猜测。
这些 SVG 是示例项目中的矢量资源,图形由源码定义。动图中的图片由应用实际加载,页面没有在切换按钮里给 Image 手动换文件路径。收藏状态单独保存,换景时仍然留在原处。

文案是一个很方便的观察点。给各个版本安排清楚的场景文字,运行后就能核对匹配结果。正式产品不一定要保留这些诊断信息,但开发阶段留下它们,比同时打开几张颜色相近的插画来回比较更省事。
本次在二合一模拟器上观察日夜组合资源。默认版本保留在 base 目录,作为其他支持设备匹配与回退的基础。验证不同设备时,可以继续读取系统报告的设备类型与匹配文案,沿着资源名、限定目录和最终显示结果核对选择过程。
如果运行时始终显示默认文案,先核对实际设备类型,再检查组合目录顺序与字符串资源名。不要为了让画面看起来正确,把显示文案手动改成“二合一版本”;那只会把用于检查匹配的线索也改掉。让诊断文字来自同一套资源管理流程,才能帮助定位问题。
图案和场景文字一起切换,提供了两条可以互相核对的线索:树木与水面的区别来自图形文件,日夜版本的文字来自字符串表。若文字变了而图没有变,就沿图片资源名继续查;两者都没变,再看当前外观与目录条件。这比只看“整体颜色似乎深了一点”更容易定位到具体文件。
5. 总结
最后回到最初那一行 Image:它始终只认识 postcard。限定条件放在目录,运行结果通过图片与文案共同确认,页面便能专心处理展示与收藏。需要增加新版本时,先明确是哪一种运行条件,再补对应资源,不必让每个页面各自决定应该去哪儿找图。
继续扩展时,可以加入语言、方向或密度等限定条件,但应按实际需求增加,避免准备一堆无法验证的组合。目录规则与回退方式见 华为资源分类与访问说明。
更多推荐
所有评论(0)