适合谁看

  • 正在做 Flutter 鸿蒙项目图片资源管理的开发者

  • 遇到"ArkTS 卡片图片和 Flutter 应用内图片不一致"问题的人

  • 想理解鸿蒙资源目录和 Flutter assets 目录差异的开发者

问题背景

在纯 Flutter 项目中,图片资源统一放在 assets/ 目录下。但在 Flutter 鸿蒙项目中:

  1. ArkTS 卡片(如 DailyRecommendCard)使用 $r('app.media.dish_beef_curry') 加载本地资源

  2. Flutter 应用使用 CachedNetworkImage 从网络加载,或从 assets/ 加载本地资源

  3. 两套资源体系的目录结构、命名规则、加载方式完全不同

这导致了几个问题:

  • 同一张菜品图需要在两个地方维护

  • ArkTS 资源名和 Flutter 资源名可能冲突

  • 卡片数据(RecommendData.ets)中的 imageResName 需要和 ArkTS 资源目录对齐

项目中的真实场景

食界探味的图片资源分布在三个位置:

位置

用途

加载方式

目录

ArkTS 卡片

桌面卡片展示

$r('app.media.*')

ohos/entry/src/main/resources/base/media/

Flutter assets

应用内本地图片

Image.asset('assets/...')

app/assets/images/

网络图片

应用内在线图片

CachedNetworkImage(url)

CDN

RecommendData.ets 中的 imageResName 字段需要和 ArkTS 资源目录对齐:

// RecommendData.ets
const RECOMMEND_LIST: RecommendItem[] = [
  {
    id: 'beef-curry-mok1r6xe-6jhu',
    name: '牛肉咖喱',
    region: '印度 · 亚洲',
    imageResName: 'dish_beef_curry',  // 对应 ArkTS 资源目录中的文件名
    highlight: '浓郁香料',
    summary: '椰香与香料层层叠起,入口热烈又厚实。',
  },
  // ...
];

核心实现

ArkTS 资源目录结构

app/ohos/entry/src/main/resources/
├── base/
│   ├── media/
│   │   ├── dish_beef_curry.png
│   │   ├── dish_sukiyaki.png
│   │   ├── dish_bibimbap.png
│   │   ├── dish_fallback.png
│   │   └── ...
│   ├── element/
│   │   └── string.json
│   └── profile/
│       └── ...
└── rawfile/
    └── insight_intent.json

ArkTS 资源的命名规则:

  • 文件名就是资源标识(不含扩展名)

  • 通过 $r('app.media.dish_beef_curry') 引用

  • 资源名只能包含字母、数字、下划线

Flutter assets 目录结构

app/assets/
├── anim/
│   ├── anim_1/
│   └── anim_2/
├── avatar/
├── badges/
├── emoticon/
└── images/
    ├── dish_beef_curry.jpg
    ├── dish_sukiyaki.jpg
    └── ...

Flutter assets 的引用方式:

  • Image.asset('assets/images/dish_beef_curry.jpg')

  • 或通过 pubspec.yaml 声明

资源名对齐策略

RecommendData.ets 中的 resolveImageResName 函数负责校验资源名:

// RecommendData.ets
const VALID_IMAGE_RES_NAMES: Set<string> = new Set(
  RECOMMEND_LIST.map((item) => item.imageResName).concat(FALLBACK_ITEM.imageResName)
);

export function resolveImageResName(imageResName: string): string {
  if (!imageResName || !VALID_IMAGE_RES_NAMES.has(imageResName)) {
    return FALLBACK_ITEM.imageResName;
  }
  return imageResName;
}

这个函数的作用:

  1. 维护一个合法资源名的白名单

  2. 如果传入的资源名不在白名单中,返回 fallback 资源名

  3. 防止卡片加载不存在的资源导致崩溃

卡片图片加载

// DailyRecommendCard.ets
build() {
  Row() {
    Image($r(`app.media.${this.dishImage || FALLBACK_IMAGE_RES_NAME}`))
      .width(144)
      .height('100%')
      .objectFit(ImageFit.Cover)
      .borderRadius({ topLeft: 16, bottomLeft: 16 })
    // ...
  }
}

关键点:

  • this.dishImage 来自 RecommendData.etsimageResName

  • 如果 dishImage 为空,使用 FALLBACK_IMAGE_RES_NAME

  • $r('app.media.*') 是 ArkUI 的资源引用方式

Flutter 应用内图片加载

// Flutter 侧 - 网络图片加载
CachedNetworkImage(
  imageUrl: 'https://cdn.example.com/dishes/${dish.id}.jpg',
  width: 120,
  height: 120,
  fit: BoxFit.cover,
  placeholder: (context, url) => CircularProgressIndicator(),
  errorWidget: (context, url, error) => Icon(Icons.error),
)
// Flutter 侧 - 本地图片加载
Image.asset(
  'assets/images/${dish.imageName}',
  width: 120,
  height: 120,
  fit: BoxFit.cover,
)

两套资源的同步问题

当菜品图片更新时,需要同时更新两个位置:

  1. ArkTS 资源目录:更新 ohos/entry/src/main/resources/base/media/ 下的图片文件

  2. Flutter assets 目录:更新 app/assets/images/ 下的图片文件

  3. RecommendData.ets:确保 imageResName 和实际资源名一致

这带来了维护成本。解决方案:

// RecommendData.ets - 资源名约定
// 所有资源名统一使用 dish_{菜品标识} 格式
// 例如:dish_beef_curry, dish_sukiyaki, dish_bibimbap

// 同时在 ArkTS 资源目录和 Flutter assets 目录中保持相同命名
// 只是文件格式可能不同(ArkTS 用 png,Flutter 用 jpg)

图片格式差异

平台

推荐格式

原因

ArkTS 卡片

PNG

支持透明度,文件较小

Flutter assets

JPG

照片类图片压缩率更高

网络图片

WebP/JPG

CDN 优化,加载更快

资源目录的构建配置

ArkTS 资源需要在 build-profile.json5 中正确配置:

// app/ohos/build-profile.json5
{
  "app": {
    "products": [
      {
        "name": "default",
        "signingConfig": "default",
        "compatibleSdkVersion": "5.0.0(12)",
        "runtimeOS": "HarmonyOS"
      }
    ]
  }
}

Flutter assets 需要在 pubspec.yaml 中声明:

flutter:
  assets:
    - assets/images/
    - assets/anim/anim_1/
    - assets/anim/anim_2/

关键代码位置

  • app/ohos/entry/src/main/resources/base/media/ — ArkTS 卡片图片资源

  • app/ohos/entry/src/main/ets/formability/RecommendData.ets — 推荐数据与资源名

  • app/ohos/entry/src/main/ets/widget/pages/DailyRecommendCard.ets — 卡片图片加载

  • app/assets/images/ — Flutter 本地图片资源

  • app/pubspec.yaml — Flutter assets 声明

鸿蒙侧实现

鸿蒙侧的图片资源管理:

  1. 资源目录ohos/entry/src/main/resources/base/media/ 存放 ArkTS 可引用的图片

  2. 资源引用$r('app.media.*') 加载图片

  3. 资源校验resolveImageResName 确保资源名有效

  4. Fallback 机制:无效资源名返回 dish_fallback

Flutter 侧实现

Flutter 侧的图片资源管理:

  1. 本地资源assets/images/ 目录,通过 Image.asset 加载

  2. 网络资源:CDN 图片,通过 CachedNetworkImage 加载

  3. 缓存策略flutter_cache_manager 管理网络图片缓存

常见坑

  • 坑 1:ArkTS 资源名和 Flutter 资源名不一致。如果 RecommendData.ets 中的 imageResNamedish_beef_curry,但 ArkTS 资源目录中的文件名是 beef_curry.png,卡片加载会失败。

  • 坑 2:$r('app.media.*') 找不到资源。如果资源文件不存在或命名错误,ArkUI 不会报错,而是显示空白或默认图。需要在 resolveImageResName 中做白名单校验。

  • 坑 3:Flutter assets 路径错误Image.asset 的路径相对于 lib/ 目录,如果路径写错,编译时不会报错,运行时才显示空白。

  • 坑 4:图片格式不兼容。ArkTS 的 $r 不支持所有图片格式(如 WebP),需要确保格式兼容。

  • 坑 5:资源更新后缓存未清除。ArkTS 卡片资源更新后,系统可能缓存旧资源。需要重新添加卡片或重启应用。

可复用模板

// Flutter 侧 - 图片加载封装模板
class DishImage extends StatelessWidget {
  final String dishId;
  final String? imageUrl;
  final String? localAsset;
  final double width;
  final double height;

  const DishImage({
    required this.dishId,
    this.imageUrl,
    this.localAsset,
    this.width = 120,
    this.height = 120,
  });

  @override
  Widget build(BuildContext context) {
    if (imageUrl != null && imageUrl!.isNotEmpty) {
      return CachedNetworkImage(
        imageUrl: imageUrl!,
        width: width,
        height: height,
        fit: BoxFit.cover,
        placeholder: (context, url) => _buildPlaceholder(),
        errorWidget: (context, url, error) => _buildError(),
      );
    }

    if (localAsset != null) {
      return Image.asset(
        localAsset!,
        width: width,
        height: height,
        fit: BoxFit.cover,
        errorBuilder: (context, error, stackTrace) => _buildError(),
      );
    }

    return _buildPlaceholder();
  }

  Widget _buildPlaceholder() {
    return Container(
      width: width,
      height: height,
      color: Colors.grey[200],
      child: Icon(Icons.image, color: Colors.grey[400]),
    );
  }

  Widget _buildError() {
    return Container(
      width: width,
      height: height,
      color: Colors.grey[200],
      child: Icon(Icons.broken_image, color: Colors.grey[400]),
    );
  }
}
// 鸿蒙侧 - 资源名校验模板
const VALID_RES_NAMES: Set<string> = new Set([
  'item_default',
  'item_01',
  'item_02',
]);

const FALLBACK_RES_NAME = 'item_default';

export function resolveResName(name: string): string {
  if (!name || !VALID_RES_NAMES.has(name)) {
    return FALLBACK_RES_NAME;
  }
  return name;
}

本篇总结

鸿蒙 Flutter 项目的图片资源双轨管理,核心挑战在于两套资源体系的共存:ArkTS 卡片用 $r('app.media.*') 加载本地资源,Flutter 应用用 CachedNetworkImageImage.asset 加载网络/本地资源。避免冲突的关键是:统一资源命名约定、维护合法资源名白名单、确保 RecommendData.ets 中的 imageResName 和 ArkTS 资源目录中的文件名一致。

Logo

作为“人工智能6S店”的官方数字引擎,为AI开发者与企业提供一个覆盖软硬件全栈、一站式门户。

更多推荐