第十六节:图片展示组件Image核心讲解与实战(基础篇)

第十六节:图片展示组件Image核心讲解与实战(基础篇)

【学习目标】

  1. 理解Image组件的核心定位,掌握png/jpg/svg/gif等主流格式的支持特性,明确各类格式的适配场景;
  2. 精通本地资源、网络图片、媒体库、Base64等基础数据源的加载方式与权限配置;
  3. 掌握objectFit、interpolation、renderMode等核心属性的用法,能根据业务需求自定义基础图片展示效果。

一、Image组件核心认知

1.1 组件定义与定位

Image是鸿蒙图片展示的核心基础组件,负责将不同来源、不同格式的图片资源渲染到界面上,是应用开发中高频使用的组件之一。它承接了从图片加载、解码到渲染展示的全流程能力,同时支持丰富的属性配置和进阶扩展,可满足日常静态展示、动态效果呈现、性能优化等多场景需求。

1.2 支持图片格式与适配说明

支持格式 格式后缀 适用场景 特殊说明
位图基础格式 png、jpg、bmp、jpeg 日常静态图片展示(图标、背景、内容图) png支持透明通道,适合图标/按钮;jpg压缩率高,适合大尺寸内容图
矢量图格式 svg 自适应缩放场景(LOGO、简易图标、插画) 无像素失真,支持动态修改颜色,无原始尺寸时需手动设置宽高
动态图片格式 gif、heif 短动态效果展示(表情包、简易动画) 支持多帧播放,ArkTS卡片中gif仅播放一次
不支持格式 apng、svga - 如需实现复杂动态效果,可通过AnimatedDrawableDescriptor

1.3 典型适用场景

场景类型 具体示例 核心实现要点
基础静态展示 页面图标、背景图、商品图片 本地资源/Resource加载 + objectFit适配 + 基础样式配置
网络图片展示 新闻配图、头像、远程资源图 网络路径加载 + INTERNET权限 + 缓存优化 + 加载/失败占位
自适应矢量展示 应用LOGO、功能图标、导航栏图标 SVG格式 + fillColor动态改色 + 固定宽高配置
动态效果展示 表情包、操作反馈动画、轮播小动画 GIF/HEIF格式 + AnimatedDrawableDescriptor + 循环/时长配置
复杂定制展示 圆形头像、带徽章图片、分层背景 objectFit(Cover) + 裁剪 + LayeredDrawableDescriptor分层叠加
性能优化场景 长列表图片、大尺寸图片展示 sourceSize解码降分辨率 + 异步加载 + 缓存策略

二、工程结构与环境准备

2.1 工程基础配置

  • 基础环境:鸿蒙API 12+、Stage模型
  • 权限声明 :网络图片需在module.json5中添加ohos.permission.INTERNET;媒体库访问需申请ohos.permission.READ_MEDIA_IMAGES权限(PhotoViewPicker安全控件方式无需声明)

2.2 工程目录结构

复制代码
ImageApplication/
├── AppScope/
│   └── app.json5  // 应用全局配置
├── entry/
│   ├── src/
│   │   ├── main/
│   │   │   ├── ets/
│   │   │   │   ├── entryability/
│   │   │   │   │   └── EntryAbility.ets  // 应用入口
│   │   │   │   ├── pages/  // 功能演示页面
│   │   │   │   │   ├── Index.ets  // 导航主页面
│   │   │   │   │   ├── ImageBasicPage.ets  // 基础用法&核心属性
│   │   │   │   │   ├── ImageSourcePage.ets // 多数据源加载
│   │   │   │   │   ├── Base64ImagePage.ets // Base64图片加载
│   │   │   │   │   ├── ImageMediaLibrary.ets // 媒体库加载
│   │   │   ├── resources/
│   │   │   │   ├── media/  // $r引用资源
│   │   │   │   └── rawfile/ // $rawfile引用原生资源
│   │   │   └── module.json5  // 权限声明

2.3 导航主页面(Index.ets)

javascript 复制代码
import { router } from '@kit.ArkUI';

interface RouterButton {
  title: string;
  url: string;
}

@Entry
@Component
struct Index {
  private buttonList: RouterButton[] = [
    { title: "Image基础用法&核心属性", url: 'pages/ImageBasicPage' },
    { title: "Image多数据源加载", url: 'pages/ImageSourcePage' },
    { title: "Base64图片加载", url: 'pages/Base64ImagePage' },
    { title: "MediaLibrary图片加载", url: 'pages/ImageMediaLibrary' },
  ];

  build() {
    Column({ space: 15 }) {
      Text("Image组件实战教程")
        .fontSize(30)
        .fontWeight(FontWeight.Bold)
        .margin({ bottom: 40 })
        .textAlign(TextAlign.Center);

      ForEach(
        this.buttonList,
        (item: RouterButton) => {
          Button(item.title)
            .width('80%')
            .height(45)
            .backgroundColor($r('sys.color.brand'))
            .fontColor(Color.White)
            .fontSize(16)
            .borderRadius(8)
            .onClick(() => {
              router.pushUrl({
                url: item.url,
                params: { title: item.title }
              });
            })
        },
        (item:RouterButton) => item.url
      );
    }
    .width('100%')
    .height('100%')
    .justifyContent(FlexAlign.Center)
    .backgroundColor('#F5F5F5')
    .padding(20);
  }
}

三、核心属性与实战用法

3.1 必配属性(避免布局异常)

javascript 复制代码
Image(数据源)
  .width(数值)
  .height(数值)
  .objectFit(ImageFit.Contain)

3.2 objectFit(最核心)

枚举值 通俗描述 视觉效果 适用场景
Contain 完整显示 图片完整,容器可能留白 图标、LOGO、需完整展示的内容
Cover 填充裁剪 容器无空白,图片裁剪 头像、背景图、填满容器的图片
Auto 自适应 同Contain 默认场景
Fill 拉伸填充 图片变形 慎用
ScaleDown 缩小不放大 小图不变,大图缩小 小图防模糊

3.3 其他核心属性

  • interpolation:图片放大插值,抗锯齿
  • objectRepeat:图片重复平铺
  • renderMode + fillColor:SVG图标改色
  • borderRadius:圆角、圆形头像
  • alt:加载失败/加载中占位图

3.4 基础属性实战页面(ImageBasicPage.ets)

javascript 复制代码
import { LengthMetrics } from '@kit.ArkUI';

// 1. 定义ImageFit配置项的接口
interface ImageFitItem {
  name: string;        // 枚举名称(用于展示)
  desc: string;        // 枚举说明(用于展示)
  value: ImageFit;     // ImageFit枚举值
}

@Entry
@Component
struct ImageBasicPage {
  private imageFitList: ImageFitItem[] = [
    { name: 'Contain', desc: '完整显示', value: ImageFit.Contain },
    { name: 'Cover', desc: '填充裁剪', value: ImageFit.Cover },
    { name: 'Auto', desc: '自适应', value: ImageFit.Auto },
    { name: 'Fill', desc: '拉伸填充', value: ImageFit.Fill },
    { name: 'ScaleDown', desc: '缩小不放大', value: ImageFit.ScaleDown }
  ];

  build() {
    Scroll() {
      Column({ space: 25 }) {
        // 页面标题
        Text("Image核心属性演示")
          .fontSize(24)
          .fontWeight(FontWeight.Bold)
          .width('100%')
          .textAlign(TextAlign.Center);

        // 1. objectFit枚举演示
        Text("1. objectFit核心缩放枚举")
          .fontSize(16)
          .fontWeight(FontWeight.Medium)
          .width('90%')
          .textAlign(TextAlign.Start);

        // Flex布局:自动换行,统一间距
        Flex({
          wrap: FlexWrap.Wrap,
          justifyContent: FlexAlign.Center,
          alignItems: ItemAlign.Center,
          space:{
            main:LengthMetrics.vp(10),
            cross:LengthMetrics.vp(10)
          }
        }) {
          // ForEach批量渲染
          ForEach(
            this.imageFitList,
            (item: ImageFitItem) => { // 显式指定类型,提升可读性
              Column({ space: 8 }) {
                Text(`${item.name}\n(${item.desc})`)
                  .fontSize(16)
                  .fontColor('#666')
                  .textAlign(TextAlign.Center)

                Image($r('app.media.banner'))
                  .width(160)
                  .height(120)
                  .objectFit(item.value) // 类型安全:只能传ImageFit枚举值
                  .backgroundColor(Color.Pink)
                  .border({ width: 1, color: '#eee' });
              }
              .flexGrow(1)
              .flexShrink(1);
            },
            (item:ImageFitItem) => item.name // 唯一标识
          );
        }
        .width('100%')
        .padding({ left: 10, right: 10 });

        // 2. 圆角图片
        Text("2. 圆角头像(borderRadius + Cover)")
          .fontSize(16)
          .width('90%')
          .textAlign(TextAlign.Start);

        Image($r('app.media.avatar'))
          .width(120)
          .height(120)
          .objectFit(ImageFit.Cover)
          .borderRadius(20)
          .backgroundColor('#f5f5f5');

        // 3. 图标动态改色
        Text("3. 图标改色(Template + fillColor)")
          .fontSize(16)
          .width('90%')
          .textAlign(TextAlign.Start);

        Row(){
          Image($r('app.media.icon_setting'))
            .width(40)
            .height(40)
            .backgroundColor('#999');

          Image($r('app.media.icon_setting'))
            .width(40)
            .height(40)
            .renderMode(ImageRenderMode.Template)
            .fillColor(Color.Blue)
            .backgroundColor('#999');
        }.width('100%')
        .justifyContent(FlexAlign.Center)

        // 4. 图片重复
        Text("4. objectRepeat:XY(双向重复)")
          .fontSize(16)
          .width('90%')
          .textAlign(TextAlign.Start);
        Image($r('app.media.love'))
          .width(200)
          .height(200)
          .objectRepeat(ImageRepeat.XY)
          .objectFit(ImageFit.ScaleDown)
          .backgroundColor(Color.Pink)
          .overlay('ImageRepeat.XY', { align: Alignment.Bottom, offset: { x: 0, y: 20 } });

        // 5. 放大抗锯齿
        Text("5. interpolation:High(放大抗锯齿)")
          .fontSize(16)
          .width('90%')
          .textAlign(TextAlign.Start);
        Row(){
          Image($r('app.media.love'))
            .width(100)
            .height(100)
            .interpolation(ImageInterpolation.None)
            .backgroundColor('#f5f5f5');
          Image($r('app.media.love'))
            .width(100)
            .height(100)
            .interpolation(ImageInterpolation.High)
            .backgroundColor('#f5f5f5');
        }.width('100%')
        .justifyContent(FlexAlign.Center)

      }
      .backgroundColor('#F5F5F5')
      .width('100%')
    }
    .width('100%')
    .height('100%')
  }
}

运行效果

四、多数据源加载

4.1 本地资源加载

(1)media 目录($r)

javascript 复制代码
Image($r('app.media.harmonyOS'))

(2)rawfile 目录($rawfile)

javascript 复制代码
Image($rawfile('development.webp'))

4.2 网络图片加载

步骤1:声明权限(module.json5)

json 复制代码
{
  "module": {
    "requestPermissions": [
      { "name": "ohos.permission.INTERNET" }
    ]
  }
}

步骤2:加载网络图片

javascript 复制代码
Image('https://res.vmallres.com/FssCdnProxy/vmall_product_uom/pmsCdn/09EFE73EA18A31D59FF3D60F7F54566A.jpg')
  .width(300)
  .objectFit(ImageFit.Contain)

4.3 多数据源页面(ImageSourcePage.ets)

javascript 复制代码
@Entry
@Component
struct ImageSourcePage {
  build() {
    Column({ space: 15 }) {
      // 1. Resource资源
      Text("1. Resource资源($r)")
        .fontSize(16)
        .width('90%')
        .textAlign(TextAlign.Start);
      Image($r('app.media.harmonyOS'))
        .width(300)
        .aspectRatio(16/9)
        .objectFit(ImageFit.Contain)
        .backgroundColor('#f5f5f5');

      // 2. rawfile资源
      Text("2. rawfile资源($rawfile)")
        .fontSize(16)
        .width('90%')
        .textAlign(TextAlign.Start);
      Image($rawfile('development.webp'))
        .width(300)
        .aspectRatio(16/9)
        .objectFit(ImageFit.Cover)
        .backgroundColor('#f5f5f5');

      // 3. 网络资源
      Text("3. 网络资源(URL)")
        .fontSize(16)
        .width('90%')
        .textAlign(TextAlign.Start);
      Image('https://res.vmallres.com/FssCdnProxy/vmall_product_uom/pmsCdn/09EFE73EA18A31D59FF3D60F7F54566A.jpg')
        .width(300)
        .syncLoad(false) // 异步加载(默认值,避免阻塞UI)
        .objectFit(ImageFit.Contain)
        .backgroundColor('#f5f5f5');

    }
    .width('100%')
    .height('100%')
    .backgroundColor('#F5F5F5')
    .justifyContent(FlexAlign.Center);
  }
}

运行效果

4.4 媒体库图片加载(ImageMediaLibrary.ets)

javascript 复制代码
import { photoAccessHelper } from '@kit.MediaLibraryKit';
import { BusinessError } from '@kit.BasicServicesKit';

@Entry
@Component
struct ImageMediaLibrary {
  @State imgUris: string[] = [];

  // 获取图库图片URI
  getAllImages() {
    try {
      const selectOptions = new photoAccessHelper.PhotoSelectOptions();
      selectOptions.MIMEType = photoAccessHelper.PhotoViewMIMETypes.IMAGE_TYPE;
      selectOptions.maxSelectNumber = 3; // 最多选择3张
      const photoPicker = new photoAccessHelper.PhotoViewPicker();

      photoPicker.select(selectOptions)
        .then((result) => {
          this.imgUris = result.photoUris; // 存储图片URI
        })
        .catch((err: BusinessError) => {
          console.error(`图库选择失败:${err.code} - ${err.message}`);
        });
    } catch (err) {
      console.error(`图库访问异常:${err.message}`);
    }
  }

  build() {
    Column({ space: 20 }) {
      Button("选择图库图片")
        .onClick(() => this.getAllImages())
        .margin({ top: 20 });

      // 展示选中的图片
      ForEach(
        this.imgUris,
        (uri: string) => {
          Image(uri)
            .width(120)
            .height(120)
            .objectFit(ImageFit.Cover)
            .borderRadius(8)
            .backgroundColor('#f5f5f5');
        },
        (uri: string) => uri
      );
    }
    .width('100%')
    .height('100%')
    .backgroundColor('#F5F5F5');
  }
}

运行效果

4.5 Base64 图片加载(Base64ImagePage.ets)

javascript 复制代码
import util from '@ohos.util';

@Entry
@Component
struct Base64ImagePage {
  @State base64image: string = ''

  aboutToAppear(): void {
    // base64 字符串由于很长,所以我们将图片并转换为Base64字符串并加载
    this.base64image = this.imageToBase64('harmonyOSCar.jpeg')
  }

  /**
   * 读取rawfile图片并转换为Base64字符串
   * @param imageName rawfile下的图片文件名(含后缀)
   * @returns 标准Base64图片字符串
   */
  imageToBase64(imageName: string): string {
    try {
      // 1. 获取资源管理器
      const uiContext = this.getUIContext();
      const hostContext = uiContext.getHostContext();
      const resMgr = hostContext?.resourceManager;
      if (!resMgr) {
        console.error('获取resourceManager失败');
        return '';
      }

      // 2. 同步读取rawfile文件内容
      const imageData = resMgr.getRawFileContentSync(imageName)
      if (!imageData || imageData.length === 0) {
        console.error('读取图片数据为空');
        return '';
      }

      // 3. 转换为Base64字符串(确保入参类型正确)
      const base64Helper = new util.Base64Helper();
      const base64Str = base64Helper.encodeToStringSync(imageData);
      if (!base64Str) {
        console.error('Base64编码失败');
        return '';
      }

      // 4. 动态生成正确的MIME前缀(根据文件后缀匹配)
      const ext = imageName.split('.').pop()?.toLowerCase() || 'png';
      // 映射常见图片格式的MIME类型
      const mimeMap: Record<string, string> = {
        'png': 'image/png',
        'jpg': 'image/jpeg',
        'jpeg': 'image/jpeg',
        'webp': 'image/webp',
        'gif': 'image/gif'
      };
      const mimeType = mimeMap[ext] || 'image/png';
      // 5. 拼接标准Base64图片格式
      const base64ImageStr = `data:${mimeType};base64,${base64Str}`;
      console.log('生成的Base64字符串:', base64ImageStr.substring(0, 50) + '...'); // 打印前50位验证
      return base64ImageStr;
    } catch (error) {
      console.error('图片转Base64失败:', error);
      return '';
    }
  }

  build() {
    Column({ space: 15 }) {
      Text("Base64格式图片展示")
        .fontSize(16)
        .width('90%')
        .textAlign(TextAlign.Start);
      Image(this.base64image)
        .width(200)
        .height(200)
        .objectFit(ImageFit.Contain)
        .backgroundColor('#f5f5f5');
    }
    .width('100%')
    .height('100%')
    .backgroundColor('#F5F5F5')
    .justifyContent(FlexAlign.Center);
  }
}

运行效果

4.6 数据源选型建议

数据源类型 适用场景 优势
$r 本地图标、常规图片 适配强、使用简单
$rawfile 特殊格式、原始文件 保留原始结构
网络URL 远程图片、头像、内容图 动态更新、无需打包进应用
媒体库URI 相册选择图片 安全、无需权限
Base64 小图标、内嵌图片 无网络请求、加载快

五、内容总结

  1. 核心定位:Image是鸿蒙系统最常用的图片展示组件,支持png、jpg、svg、gif等主流格式,可满足静态展示、圆角头像、图标改色等日常开发需求。
  2. 必掌握属性width/height必须显式设置;objectFit是图片缩放核心,优先使用ContainCoverborderRadius实现圆角/圆形效果;renderMode+fillColor用于SVG图标改色。
  3. 多数据源加载 :本地资源用$r/$rawfile;网络图片需配置INTERNET权限;媒体库图片使用PhotoViewPicker无需申请权限;Base64适合小型图标内嵌展示。
  4. 开发规范 :网络图片建议配置alt占位图,媒体库选择优先使用系统控件,避免权限风险与异常崩溃。

六、代码仓库

七、下节预告

下一节我们将学习 第十七节:Image组件进阶实战,内容包括:

  • PixelMap 像素级图片处理
  • 图片加载事件与异常兜底
  • 分层图片、GIF 动画合成与播放
  • 图片性能优化与内存管理