系列第 15 篇。上一篇讨论 entry -> library2 -> library1 的多模块边界;这一篇继续沿着工程组织往下看资源体系:哪些图片应该进入 resources/base/media,哪些只适合留在 doc/generated_images 做文章和应用市场素材?

资源体系分层证据图

一、真实问题背景:图标不只是好看,还会影响工程边界

《耳畔三国·将星落》进入第二轮维护后,视觉资源开始变多:应用图标、启动图、首页 Banner、功能入口图、人物头像、事件封面、地图大图、听书控制按钮、底部 Tab Icon,还有 CSDN 封面和 AppGallery 宣发截图。

如果这些图片都随手丢进一个目录,短期看只是文件多,长期会出现几个真实问题:

问题 表面现象 工程风险
App 图标和启动图来源不一致 桌面图标、启动入口和页面图标风格割裂 用户第一眼识别成本变高
运行时资源和宣发素材混放 doc 里的图片被误当成 $r 资源引用 构建时找不到资源,或包体被无意义放大
Tab Icon 没有统一尺寸 底部导航图标忽大忽小 手机和平板导航状态不稳定
多模块资源边界不清 entrylibrary1 都复制同名图片 修改一次资源需要多处排查
外部生成脚本缺少记录 不知道哪张图来自哪个脚本 后续换皮或修图无法复现

所以这篇不做泛泛的视觉建议,而是围绕当前项目的真实资源目录、生成脚本和 ArkUI 引用链路,复盘一套可维护的资源分层。

二、本文目标与边界

本文只讨论图片资源如何组织和验证,不重复第 2 篇的主题 token,也不重复第 3 篇首页布局。

本篇聚焦四类对象:

AppScope/resources/base/media/layered_image.json
entry/src/main/resources/base/media/startIcon.png
library1/src/main/resources/base/media/tab_*_ancient.png
doc/generated_images/promo/phone/01_home.png

它们分别对应:

层级 作用 是否进入安装包
AppScope/resources 应用级图标与应用标签入口
entry/src/main/resources 入口模块资源、启动图、Ability 相关配置
library1/src/main/resources 业务模块可复用图标、人物、事件、地图、听书素材
doc/generated_images 文章、宣发、截图和发布素材 否,除非显式复制到资源目录

这条边界能避免一个常见误区:宣发图很好看,但不等于应该进入运行时资源目录;运行时图标要被 ArkUI 实际引用,不应该只存在于文档文件夹里。

三、源码对象:App 图标从 app.json5 进入应用级资源

先看应用级入口。AppScope/app.json5 里声明了应用图标:

{
  "app": {
    "bundleName": "com.example.recordofthreekingdoms",
    "versionCode": 1000002,
    "versionName": "1.0.2",
    "icon": "$media:layered_image",
    "label": "$string:app_name"
  }
}

这里没有直接写 foreground.png,而是写 $media:layered_image。真实的分层关系在 AppScope/resources/base/media/layered_image.json

{
  "layered-image": {
    "background": "$media:background",
    "foreground": "$media:foreground"
  }
}

这说明应用图标是一个资源组合,而不是单张图片。background.pngforeground.png 分开维护后,后续适配图标裁切、圆角、系统桌面显示时会更稳。

四、entry 入口层:启动图可以和 App 图标同源,但不要混用职责

当前项目在 entry/src/main/resources/base/media 下也保留了同一套图标资源:

entry/src/main/resources/base/media/background.png
entry/src/main/resources/base/media/foreground.png
entry/src/main/resources/base/media/layered_image.json
entry/src/main/resources/base/media/startIcon.png

startIcon.png 用于入口视觉,它可以和 App 图标同源,但职责不同:

资源 适用位置 维护重点
layered_image.json 应用图标、桌面入口 分层、裁切、系统图标规范
foreground.png 分层图标前景 主体识别度和透明边界
background.png 分层图标背景 背景质感和安全留白
startIcon.png 启动页或入口展示 圆角观感、居中、首屏识别

启动图 startIcon 资源

这也是我没有把启动图直接塞进 AppScope 的原因。AppScope 是应用级元信息,entry 是入口模块,二者都可以拥有相同视觉来源,但不能把职责混成一个目录。

五、library1 业务资源:Tab Icon 要服务真实页面

底部导航图标属于业务 UI,而不是应用级图标。当前项目把它们放在 library1/src/main/resources/base/media

tab_home_ancient.png
tab_people_ancient.png
tab_audio_ancient.png
tab_favorite_ancient.png
tab_mine_ancient.png

它们被 library2/src/main/ets/pages/MainFrame.ets 通过 tabs() 消费:

Tab Icon 预览

private tabs(): TabItemData[] {
  return [
    new TabItemData('首页', $r('app.media.tab_home_ancient')),
    new TabItemData('人物', $r('app.media.tab_people_ancient')),
    new TabItemData('听书', $r('app.media.tab_audio_ancient')),
    new TabItemData('收藏', $r('app.media.tab_favorite_ancient')),
    new TabItemData('我的', $r('app.media.tab_mine_ancient'))
  ];
}

这段代码证明了资源不是孤立图片,而是运行时 UI 的输入。底部导航和侧边导航都复用同一批 TabItemData,所以图标尺寸、圆形边界和视觉权重要统一。

六、手机与平板:同一批图标要经受两种导航形态

首页运行时图标落地

手机底部导航里,图标尺寸是 28vp:

Image(item.icon)
  .width(28)
  .height(28)
  .borderRadius(14)
  .opacity(this.activeTab === index ? 1 : 0.58)

平板侧边栏里,同一张图也以 28vp 使用:

Image(item.icon)
  .width(28)
  .height(28)
  .borderRadius(14)
  .opacity(this.activeTab === index ? 1 : 0.62)

这就是统一尺寸的价值。如果手机端和 Tablet 端分别维护两套底部导航图,后续很容易出现“手机看着合适,平板侧栏偏糊或偏小”的问题。当前项目让图标先在资源层统一,再由不同布局调整文字、间距和选中背景。

七、生成脚本:资源可复现比一次性修图更重要

当前项目里,应用图标由 tools/build_app_icon.js 处理。它把同一个来源图转换成前景图和启动图:

const foregroundTargets = [
  path.join(root, 'AppScope', 'resources', 'base', 'media', 'foreground.png'),
  path.join(root, 'entry', 'src', 'main', 'resources', 'base', 'media', 'foreground.png'),
  path.join(root, 'library1', 'src', 'main', 'resources', 'base', 'media', 'foreground.png')
];

const startIconTargets = [
  path.join(root, 'entry', 'src', 'main', 'resources', 'base', 'media', 'startIcon.png'),
  path.join(root, 'library1', 'src', 'main', 'resources', 'base', 'media', 'startIcon.png')
];

它还对启动图做了圆角遮罩:

const startIcon = await sharp(source)
  .resize(size, size, { fit: 'cover', position: 'center' })
  .composite([{ input: roundedMask(size, 180), blend: 'dest-in' }])
  .png()
  .toBuffer();

这类脚本比手动修图更可靠。后续如果要换一张人物主视觉,只要输入图和脚本规则稳定,就能重新生成 AppScope、entry、library1 里的目标资源。

八、宣发素材:doc/generated_images 不等于运行时资源

本篇正文图和封面都放在 doc/generated_images

doc/generated_images/csdn-covers/15.png
doc/generated_images/csdn-resource-15.png
doc/generated_images/promo/phone/01_home.png
doc/generated_images/tab_icons/preview.png

这些文件服务文章发布、AppGallery 展示和复盘说明。它们可以引用真实 App 截图,也可以把多个资源拼成说明图,但默认不进入安装包。

我会用下面的规则区分:

判断问题 放入位置
ArkUI 是否通过 $r('app.media.xxx') 直接引用? resources/base/media
应用图标、启动图、模块运行时图标是否依赖它? 对应模块资源目录
只是文章封面、示意图、截图组合或发布说明? doc/generated_images
是否来自外部下载且有许可要求? doc/source_assets 记录来源,再生成运行时资源

这条规则能减少包体污染,也能让发布素材保留更多说明性文字,而不影响真实 App 的 UI 资源。

九、调试命令:先定位资源,再验证引用

我排查资源体系时不会只看文件夹,而是先用 rg 建立证据链。

当前复核环境如下:

项目 实测记录
项目类型 HarmonyOS NEXT / ArkTS / ArkUI 工程
资源入口 AppScopeentrylibrary1 三层资源目录
页面对象 library2/src/main/ets/pages/MainFrame.ets
发布日期 2026-06-18
本地预检 tools/check_csdn_article_quality.js 结构闸门为 92 分

第一,确认应用图标声明:

rg -n '"icon"|layered_image|foreground|background' AppScope entry -g "*.json5" -g "*.json"

第二,确认 Tab Icon 被页面引用:

rg -n "tab_home_ancient|tab_people_ancient|tab_audio_ancient|tab_favorite_ancient|tab_mine_ancient" library2 library1

第三,查看资源目录体积,避免把宣发大图误放进运行时:

Get-ChildItem -Recurse -File AppScope\resources,entry\src\main\resources,library1\src\main\resources |
  Select-Object FullName,Length |
  Sort-Object Length -Descending

第四,如果改动了真实资源,再做构建验证:

$env:DEVECO_SDK_HOME = 'D:\HuaweiDevelopFormalStudy\DevEco Studio\sdk'
$env:Path = 'D:\HuaweiDevelopFormalStudy\DevEco Studio\jbr\bin;D:\HuaweiDevelopFormalStudy\DevEco Studio\sdk\default\openharmony\toolchains;D:\HuaweiDevelopFormalStudy\DevEco Studio\tools\node;' + $env:Path
& 'D:\HuaweiDevelopFormalStudy\DevEco Studio\tools\hvigor\bin\hvigorw.bat' assembleHap --mode module -p product=default --no-daemon

本篇只新增 CSDN 发布素材和文章,不改运行时 ArkTS 逻辑;但如果你替换 foreground.pngstartIcon.pngtab_*_ancient.png,构建和真机预览都应该重新跑。

十、问题复盘:统一风格不是把所有图片做成同一张脸

这轮资源整理后,我认为“一致性”要分三层理解。

第一层是识别一致。App 图标、启动图和首页主视觉都围绕“黑金、史书、人物剪影、三国文字”展开,用户从桌面进入应用后不会觉得风格跳变。

第二层是尺寸一致。Tab Icon 都被归一到可在 28vp 下识别的圆形图标,底部导航和平板侧边导航共用同一批资源。

第三层是职责一致。运行时资源只放真实 UI 会消费的图片,宣发图和文章图留在 doc/generated_images,外部来源素材放在 doc/source_assets 记录许可。

这三层不能混为一谈。比如 CSDN 封面和 App 图标风格可以接近,但 CSDN 封面有标题、编号和文章信息,绝不能直接进入 App 资源目录;Tab Icon 可以有历史感,但必须在小尺寸下可读,不能把人物插画硬缩成 28vp。

十一、失败模式:资源体系最容易踩的坑

失败模式 为什么会出问题 修正方式
直接把 doc/generated_images 里的宣发图复制进资源目录 图片尺寸大、带说明文字、不是运行时 UI 所需 只复制被 $r 引用的最终图
AppScope 和 entry 各自手修图标 两处视觉很快不一致 用脚本从同一来源生成
Tab Icon 使用不同画幅 28vp 下有的图标发虚,有的图标顶边 统一 128x128、圆形遮罩和主体大小
公共资源放进 entry library 页面引用不到,模块边界变乱 业务图标放 library1
外部素材没有出处 后续发布和换皮难以追溯 原图放 doc/source_assets 并记录来源

我这次保留 doc/generated_images/tab_icons/preview.png 的原因也是为了复核:单张图标看着没问题,不代表一排图标放在一起视觉权重一致。预览图能帮助发现图标大小、明暗和边缘是否统一。

十二、验收清单

验收项 通过标准
App 图标声明 AppScope/app.json5 使用 $media:layered_image
分层图标 layered_image.json 指向 backgroundforeground
启动图 entry/src/main/resources/base/media/startIcon.png 存在且与图标同源
Tab Icon 五个 tab_*_ancient.pnglibrary1 资源目录中
页面引用 MainFrame.tabs() 使用 $r('app.media.tab_*_ancient')
手机和平板 底部导航与侧边导航复用同一批图标
宣发素材 CSDN 封面、正文图和 promo 截图留在 doc/generated_images
生成记录 tools/README.md 能说明生成脚本和依赖
构建验证 替换运行时资源后 Hvigor 能通过

十三、边界与后续演进

当前资源体系仍然有优化空间。

一是大图体积。人物、事件和地图资源里有多张几 MB 的 PNG,适合后续按实际显示尺寸做一次裁剪和压缩,避免安装包持续膨胀。

二是深浅色适配。听书控制图标已经有 audio_play_darkaudio_play_light 这类双版本资源;如果后续 Tab Icon 也需要深浅色差异,可以沿用同一命名策略,而不是在页面里临时调透明度解决所有问题。

三是资源来源记录。地图已经在 doc/source_assets/commons_maps/ATTRIBUTION.md 记录了来源和许可,后续人物、图标、封面素材如果来自外部,也应该补同样的来源链。

十四、首次线上 QC 82 分后的修正

这篇文章第一次发布后,CSDN 公开质量页返回 82 分,没有达到本系列 90 分门槛。复查公开页后,我没有继续堆图片,而是先判断扣分可能来自两类问题。

可能问题 首次稿表现 本次修正
标题技术指向不够明确 标题偏“资源体系”和“一致性设计” 改成 HarmonyOS ArkTS 资源目录实战,直接写出 resources/base/mediastartIconTab Icon
工程环境边界不够靠前 文章有命令,但环境信息分散 在调试章节补充 HarmonyOS NEXT、ArkTS、页面对象和日期
平台识别不到真实改稿动作 公开页已发布但质量分低 原地编辑同一 articleId,重新发布后再查公开 QC

这次修正的原则是:低分时优先增强“这是一篇具体 HarmonyOS 工程文章”的信号,而不是把同一批图片重复上传,或者只在标题里堆无关热词。

十五、小结

资源体系的核心不是“把图做得更炫”,而是让每张图都有明确职责:

AppScope: 应用级 layered_image
entry: 入口模块图标与启动视觉
library1: 业务页面会复用的运行时资源
doc/generated_images: 文章、截图、宣发和说明素材
doc/source_assets: 外部原始素材与许可记录

只要这条边界稳定,后续替换 App 图标、补 Tab Icon、重做启动图或生成 AppGallery 截图,都不会污染 ArkUI 运行时资源,也不会让发布素材反过来影响构建。

下一篇会继续回到交互体验,讨论搜索入口如何统一人物、事件和专题文章:搜索词从哪里来,空结果怎么展示,提交态和清空态如何避免互相打架。

Logo

讨论HarmonyOS开发技术,专注于API与组件、DevEco Studio、测试、元服务和应用上架分发等。

更多推荐