写在前面
如果你写过鸿蒙 ArkUI 应用,大概率遇到过这个场景:
你写个用户头像区,Image 加载网络 URL——结果网慢时一片白,加载失败也是一片白,用户体验崩。
你想「加个占位图」——查文档发现 Image 有.alt()设占位,还有.onComplete()/.onError()事件。你点进去发现 API 一脸懵。
你又想「资源图、本地图、网络图能不能用同一个 Image」——查文档发现 Image 源是ResourceStr统一类型,四源都吃。
这是「裸 Image 加载」和「Image 全流程」的分水岭。鸿蒙给的图片加载答案是 Image + alt + onComplete/onError——ResourceStr统一四源、.alt()设占位、.onComplete()监听加载完、.onError()监听失败。
本文就用一个真机可跑的「网络源 + 资源源 + 本地源 + alt 占位」demo,把 Image 多源加载从「听名字一脸懵」讲到「下个项目直接抄」。代码托管在 AtomGit,文末有链接,真机实拍截图作证。这是组件深水区系列篇。
适合人群:写过鸿蒙应用、被「Image 加载失败一片白」折磨过的同学。
不适合人群:还在学@State的同学——出门左转看我的入门篇。
一、先讲清楚:Image 多源加载到底是啥
一句话:Image 是鸿蒙图片展示组件,ResourceStr 统一四源 + alt 占位 + onComplete/onError 全流程监听。
你之前写前端<img src="url">是浏览器宿主标签——鸿蒙不是浏览器环境,没 HTML 标签。Image 是鸿蒙专门给图片展示的原生组件,能力对标前端的<img>+onload/onerror但更精细可控。
核心 API 一览:
| API | 作用 | 一句话理解 |
|---|---|---|
Image(src: ResourceStr) | 创建 Image | 「src 是 ResourceStr 统一四源」 |
.alt(占位图) | 加载失败占位 | 「加载不出来时显示啥」 |
.objectFit(ImageFit) | �缩放模式 | 「Contain/Cover/Fill/Center」 |
.onComplete(cb) | 加载完回调 | 「拿到真实 w/h」 |
.onError(cb) | 加载失败回调 | 「失败时触发」 |
记住这五个,往下看。
二、动手:一个 Image 四源切换的 demo
2.1 Image 基础:网络源 + alt 占位
@StateimgSrc:ResourceStr='https://placehold.co/300x200/4CAF50/white?text=Network+Image'Image(this.imgSrc).width('92%').height(180).objectFit(ImageFit.Contain).backgroundColor('#f5f5f5').borderRadius(8).alt($r('app.media.startIcon'))// alt 加载失败占位.onComplete((e)=>{constw:number=e?.width??0consth:number=e?.height??0this.logText=`加载完,w=${w}h=${h}`}).onError(()=>{this.logText='加载失败,alt 占位'})三个细节:
Image(src: ResourceStr)——src 是ResourceStr统一类型,网络 URL / 资源 $r / 本地路径都吃.alt($r('app.media.startIcon'))——加载失败时显示占位图(资源源),避免一片白.objectFit(ImageFit.Contain)——缩放模式,Contain 保比例留白、Cover 铺满裁剪、Fill 拉伸、Center 原大小居中
这是 Image 最核心的能力——一个 Image 组件吃四源,alt 保底,onComplete/onError 全流程可见。
2.2 资源源:$r 引用
privateresSrc:ResourceStr=$r('app.media.startIcon')Button('② 资源源($r app.media)').onClick(()=>{this.imgSrc=this.resSrcthis.sourceLabel='资源源($r app.media.startIcon)'})资源源用$r('app.media.xxx')引用——图打包进 hap,加载最快、不依赖网络。适合应用内固定图标/占位图。
2.3 本地源:沙箱路径
privatelocalSrc:string='/data/local/tmp/not-exist-demo.jpeg'Button('③ 本地源(沙箱路径)').onClick(()=>{this.imgSrc=this.localSrcthis.sourceLabel='本地源(沙箱路径,不存在触发 alt)'})本地源用沙箱路径字符串——应用沙箱内文件路径。demo 里故意写不存在的路径,触发onError+ alt 占位。
2.4 onComplete / onError:全流程监听
.onComplete((e)=>{constw:number=e?.width??0consth:number=e?.height??0this.logText=`加载完,w=${w}h=${h}`}).onError(()=>{this.logText='加载失败,alt 占位'})onComplete回调给真实宽高{width, height}——注意 ArkTS 强约束e可能 undefined,要e?.width ?? 0兜底。onError加载失败时触发,此时 alt 占位图顶上。
三、真机实拍:网络源加载成功 + 本地源触发 alt
我把这个 demo 補到真机上跑(鸿蒙 6.1.1.125, API 24),依次点②资源源 + ③本地源,下面两张都是真机实拍,没有任何 P 图。
网络源态:当前源「网络源(https URL)」+ 图片区显示绿色 placehold.co 占位图(网络加载成功)+ ①②③ 三按钮 + 日志区:
切到本地源后:当前源「本地源(沙箱路径,不存在触发 alt)」+ 图片区显示 startIcon 资源图(alt 占位生效)+ 日志区更新:
重点看第二张:本地路径不存在 → 图片区显示 startIcon 资源图——
.alt()占位真生效了,onError 真触发了。这是 Image 全流程的真机证明。
四、Image vs 前端<img>:啥差异
新手最容易纠结的问题:既然前端<img>那么标准,鸿蒙为啥要造 Image?
| 维度 | 前端<img> | �鸿蒙Image |
|---|---|---|
| 运行环境 | 浏览器宿主 | 鸿蒙原生运行环境 |
| 源类型 | url 字符串 | ResourceStr(网络/资源/本地/沙箱) |
| 占位图 | 无原生(要 CSS 替身) | .alt()原生支持 |
| 加载完事件 | onload | .onComplete()给真实 w/h |
| 失败事件 | onerror | .onError() |
| 缩放模式 | CSSobject-fit | .objectFit(ImageFit) |
一句话决策:鸿蒙应用图片展示必须用 Image,不能用<img>(不存在)。
五、常见坑(都是血泪)
| 坑 | 症状 | 解法 |
|---|---|---|
| Image src 用裸字符串期望资源 | 加载失败 | 资源源用$r('app.media.xxx'),不是字符串 |
忘了.alt() | 加载失败一片白 | .alt($r(...))设占位图 |
onComplete的e直接用 | 编译报「possibly undefined」 | e?.width ?? 0兜底 |
| 网络图加载慢 UI 卡 | 体验差 | 网络图 + alt 占位 + 异步加载 |
.objectFit忘设 | 图片拉伸变形 | 按 Contain/Cover/Fill 选 |
| 本地源路径写错 | 加载失败 | 沙箱路径要正确,或用 alt 保底 |
| 大图不限制 width/height | 内存炸 | Image 设固定 w/h,objectFit 缩放 |
六、ResourceStr 四源速查
| 源 | 写法 | 用途 | 性能 |
|---|---|---|---|
| 网络源 | 'https://...'URL 字符串 | 用户头像/动态图 | 慢(依赖网络) |
| 资源源 | $r('app.media.xxx') | 应用内固定图标 | 最快(打包进 hap) |
| 本地源 | '沙箱路径字符串' | 应用沙箱内文件 | 快 |
| 内存源 | PixelMap对象 | 代码生成的图 | 快 |
七、完整代码仓库
本文所有代码都已托管到AtomGit,欢迎 clone、提 issue、点 star:
🔗仓库地址:https://atomgit.com/JaneConan/arkui-image
仓库包含:
- 完整的「Image 四源切换 + alt + onComplete/onError」demo 工程
Index.ets主页面(网络源/资源源/本地源三按钮 + alt 占位 + 加载事件)- 可直接用 DevEco Studio 打开运行(真机装普通应用必能跑)
八、下一步该学什么?
跑通这个 demo 之后,你的鸿蒙图片加载就入门了。后续按这个顺序往下:
- List Section 分组吸顶(下一篇):大列表分组 + sticky 头
- Swiper 自动轮播:轮播 + Indicator + 自动播放
- ScrollView 嵌套滚动:Scroll 容器 + 嵌套滚动
- Slider 滑块控制:滑块 + onChange + step
- Grid 网格布局:网格 + GridLayout
写在最后
Image 的本质,是**「鸿蒙给图片展示的原生组件,ResourceStr 统一四源 + alt 保底 + onComplete/onError 全流程」**——不是前端<img>,是鸿蒙专门给图片的原生组件。代价是.alt()+ 事件监听多写几行。
一旦你开始用 Image 全流程思维写图片展示,你会发现大部分「用户头像」「动态图」「固定图标」「加载失败保底」的需求,都是 Image + alt + onComplete/onError 的自然结果。代码量比裸 Image 多三行,体验可控性高九成。
代码已经给你了,仓库链接在上面。现在关掉这篇文章,打开 DevEco Studio,把 demo 蜜起来,亲手点三源切换 + 看 alt 占位生效。
跑通了,回来评论区打个「1」,我看看有多少人真的动手了。🚀
作者:JaneConan
仓库:https://atomgit.com/JaneConan/arkui-image
协议:Apache-2.0,随便用,别告我