HarmonyOS 实战|中式美食图片资源注册表:ResourceStr 统一入口、卡片复用与空图兜底

📅 2026/7/4 3:45:40
HarmonyOS 实战|中式美食图片资源注册表:ResourceStr 统一入口、卡片复用与空图兜底
做菜谱类应用时图片是最容易被低估的一块。列表页、首页推荐、收藏页、详情页都要展示菜品图看起来只是把dish.image塞给Image()但真正落到 HarmonyOS 工程里会遇到一个很具体的问题$r(app.media.xxx)不能靠字符串在运行时随便拼出来。中式美食的做法是把图片处理拆成两层数据里只存稳定的图片 key界面组件不关心图片来自内置资源还是用户上传文件真正把 key 转成ResourceStr的事情统一交给ImageRegistry。这样卡片组件可以复用列表也不会因为某条数据缺图就塌掉。上图对应的是中式美食里几个真实界面首页推荐、菜品列表、详情页首屏。它们展示形式不一样但图片入口是一套。本文不讲泛泛的图片美化而是把这个资源注册表为什么要存在、怎么接到卡片组件、空图怎么兜底讲清楚。章节位置内容资源入口entry/src/main/ets/common/ImageRegistry.ets横向卡片entry/src/main/ets/view/components/DishCard.ets方形卡片entry/src/main/ets/view/components/DishTile.ets菜系列表卡片entry/src/main/ets/view/components/CuisineDishTile.ets数据来源Dish.image、内置 media key、用户上传 URI本文验证环境本文对应的是已上架的 HarmonyOS 应用中式美食不是单独 demo。项目版本与范围开发工具DevEco Studio 6.0.0 Release运行系统HarmonyOS NEXT / HarmonyOS 6.0SDK/APIHarmonyOS SDK API 20语言与框架ArkTS 6.0、ArkUI V2、Stage 模型核心组件Image、ResourceStr、Computed、ComponentV2验证方式首页、列表页、详情页、收藏页手动检查缺图数据兜底检查实际验证时我会做四件事打开首页看推荐图是否正常进入列表页看双列卡片是否等高进入详情页确认同一个菜品图片一致再临时制造一条没有图片 key 的数据确认界面显示首字兜底而不是空白或崩溃。本章导读中式美食里图片来源有两种。第一种是应用内置图片比如dish_d001、home_card_casserole、video_upload_card_v3_composite。这些图片在工程资源里需要通过$r(app.media.xxx)引用。第二种是用户后续可能上传的图片通常会变成file://、/data/或datashare://这类路径。它们本身就是字符串不能再去$r()。如果组件里到处写判断后面会很难维护。一个横向卡片写一遍方形卡片写一遍菜系列表再写一遍迟早会出现有的页面能显示、有的页面显示不出来。所以中式美食把规则收口到getImage()。为什么不能直接拼$r很多刚写 ArkUI 图片资源时会自然想到这样做constimageName:stringdish_d001;Image($r(app.media.imageName));这个思路在普通 Web 图片路径里很常见但在 HarmonyOS 资源系统里不合适。$r(app.media.xxx)是编译期资源引用不是运行时字符串拼接函数。图片名如果来自接口、JSON 或本地数据库就需要先把这些 key 显式注册出来。中式美食的资源注册表就是为了解这个问题constREG:Recordstring,Resource{dish_d001:$r(app.media.dish_d001),dish_d002:$r(app.media.dish_d002),home_card_casserole:$r(app.media.home_card_casserole),video_upload_card_v3_composite:$r(app.media.video_upload_card_v3_composite)};这样数据层可以继续存简单字符串UI 层也能拿到真正可用的资源对象。这个结构看起来笨但非常稳定尤其适合菜谱应用这种图片资源多、页面复用多的场景。getImage 做了什么getImage()的返回类型是ResourceStr | undefined这个类型刚好能被 ArkUI 的Image()接收。exportfunctiongetImage(key:string):ResourceStr|undefined{if(!key)returnundefined;if(key.startsWith(file://)||key.startsWith(/data/)||key.startsWith(datashare://)){returnkey;}returnREG[key];}这段代码分三种情况。第一key 是空字符串直接返回undefined。组件收到以后走空图兜底。第二key 是用户上传图片路径就原样返回。因为这类路径已经是运行时可访问的图片地址。第三key 是内置资源名就去注册表里查。查到了就返回Resource查不到也返回undefined让组件继续兜底。这里有一个工程收益组件不需要知道图片的来源。DishCard、DishTile、CuisineDishTile都只调用getImage(this.dish.image)剩下的交给统一入口。横向卡片怎么接横向卡片用于列表、推荐、搜索结果等场景。它的结构是左图右文图片宽高固定为116 x 116这样文字区不会被不同图片尺寸挤乱。Computedgetimg():ResourceStr|undefined{if(this.dishnull)returnundefined;returngetImage(this.dish.image);}卡片真正渲染图片时只看this.img是否存在。if(this.img!undefined){Image(this.img).width(116).height(116).objectFit(ImageFit.Cover).borderRadius(20);}else{Column(){Text((this.dish?.name??).charAt(0)).fontSize(AppFonts.xxl).fontColor(AppColors.textOnBrand).fontWeight(FontWeight.Bold);}.width(116).height(116).borderRadius(20).linearGradient({angle:135,colors:[[AppColors.brandPrimaryDark,0],[AppColors.brandPrimary,1]]});}这个兜底不是为了好看而已它解决的是数据质量问题。只要菜名还在即使图片缺失用户也能知道这张卡片对应哪道菜列表高度也不会变化。方形卡片为什么也复用同一套入口DishTile用在首页 2x2 推荐、收藏网格等位置展示形式和横向卡片不同但图片处理不应该重新发明一遍。Computedgetimg():ResourceStr|undefined{if(this.dishnull)returnundefined;returngetImage(this.dish.image);}不同的是方形卡片更强调图片本身所以它用了aspectRatio(1.0)保持正方形并在图片底部叠了一个渐变遮罩放评分和评论数。Image(this.img).width(100%).height(100%).objectFit(ImageFit.Cover).borderRadius({topLeft:24,topRight:24});这里的重点是图片入口统一展示策略各自负责。注册表不关心横向还是方形卡片组件不关心图片是资源还是文件。边界清楚以后改 UI 和换数据都比较稳。空图兜底比崩溃处理更重要菜谱数据一多图片缺失是早晚会出现的。可能是资源漏注册可能是用户上传路径失效也可能是接口返回了一个新 key。中式美食没有在每个页面上写复杂异常提示而是在卡片层做了轻量兜底Text((this.dish?.name??).charAt(0)).fontSize(AppFonts.hero).fontColor(AppColors.textOnBrand).fontWeight(FontWeight.Bold);这个兜底有三个好处。第一页面不空。用户至少看到菜名首字和完整文字信息。第二布局不跳。图片区域仍然占固定尺寸列表滚动体验稳定。第三问题容易定位。开发时一眼能看出是哪条数据没拿到图片而不是整页报错。注册表怎么维护中式美食的ImageRegistry.ets顶部说明了一个关键点它由工具生成。真实项目里不建议手写上百个资源 key因为漏一个、拼错一个都很难查。推荐的维护方式是动作做法新增菜品图先把图片放进 media 资源目录新增 key通过工具扫描资源并更新ImageRegistry.ets数据引用Dish.image只保存dish_d001这类稳定 keyUI 展示统一调用getImage()缺图处理组件内显示首字兜底这种方式比把图片路径散落在页面里更可控。后面要换一批图片、压缩资源、接入用户上传也不会牵扯所有页面。工程验收记录检查项结果首页推荐卡片内置图片正常展示点击态不改变布局列表横向卡片图片、菜名、摘要、标签、耗时信息稳定对齐方形网格卡片图片保持 1:1评分遮罩不遮挡主体收藏页缺图数据显示菜名首字兜底没有出现空白块用户图片路径file://、/data/、datashare://进入Image()前不再被资源表转换资源 key 缺失返回undefined组件走兜底不中断页面常见问题复盘一个容易犯的错是把ImageRegistry写成万能图片中心顺手塞进业务逻辑。这个文件只应该解决 key 到图片资源的转换不应该判断菜品是否热门也不应该决定卡片样式。另一个问题是空图兜底做得太弱只放一个灰色块。菜谱应用里用户真正关心的是这道菜叫什么、能不能继续点进去所以兜底里至少要保留菜名首字和完整文本信息。还有一个细节是图片尺寸。ImageFit.Cover能保证视觉饱满但一定要配合稳定宽高或比例。如果只设置宽度不设高度图片加载前后列表高度可能变化滚动会有轻微抖动。本章小结中式美食的图片处理没有走复杂路线而是把最容易出问题的地方收口数据层只保存图片 keyImageRegistry负责把 key 转成ResourceStr卡片组件负责展示和兜底。这个方案的价值在于稳定。首页、列表、收藏、详情都能用同一套入口用户上传图片也能自然接进来。对菜谱类应用来说图片不是装饰它直接影响用户是否愿意继续点开一道菜所以资源入口越清楚后面的页面体验越稳。