HarmonyOS UI 设计套件:让你的应用图标更精致

什么是 UI 设计套件

你有没有注意到,华为手机上的应用图标看起来特别精致?有些图标还有立体感,有前景和背景的分层效果。这些效果不是开发者自己做的,而是系统提供的 UI 设计套件(UI Design Kit)帮你处理的。

UI 设计套件提供了一套图标处理 API,让你的应用图标能够和 HarmonyOS 的设计风格完美融合。简单说,就是让你的 App 图标看起来更"鸿蒙"、更精致。

你可能会问:我的图标不是已经在 APK 里了吗?为什么还需要处理?因为 HarmonyOS 有一套自己的图标风格规范,比如圆形遮罩、统一的边框、分层效果等等。如果你的图标不经过处理,看起来可能会和系统其他图标格格不入,就像穿了一身西装却配了一双拖鞋。

核心功能

UI 设计套件提供两种图标处理能力:

  1. 单层图标处理:处理普通的应用图标,加上 HarmonyOS 风格的边框和遮罩。你的图标本来是一张普通的图片,处理之后就变成了 HarmonyOS 风格的圆形图标
  2. 分层图标处理:处理有前景和背景分层的图标,实现立体效果。这种图标看起来更有层次感,前景是图标主体,背景是底色或图案

两种处理方式都有同步和异步接口,还支持批量处理。如果你有很多图标需要处理,用批量接口效率更高。

环境搭建

硬件要求

  • 设备类型:华为手机、平板、PC/2in1、TV
  • HarmonyOS 系统:HarmonyOS NEXT Developer Beta2 及以上

软件要求

  • DevEco Studio 版本:DevEco Studio NEXT Developer Beta2 及以上
  • HarmonyOS SDK 版本:HarmonyOS NEXT Developer Beta2 SDK 及以上

搭建步骤

  1. 安装 DevEco Studio:去华为开发者官网下载安装
  2. 配置开发环境:确保网络环境正常
  3. 设备调试:使用真机进行调试

项目结构

├─ entry/src/main/ets
│  ├─ entryability
│  │  └─ EntryAbility.ets
│  └─ pages
│     ├─ GetHdsIcon.ets           // 单层图标处理页面
│     ├─ GetHdsLayeredIcon.ets    // 分层图标处理页面
│     └─ Index.ets                // 主页界面
├─ entry/src/main/resources       // 应用资源目录

项目按功能分成了两个页面:单层图标处理和分层图标处理。你可以根据自己的需求选择用哪种。

图标处理流程

下面是 UI 设计套件的图标处理流程:

单层图标

分层图标

导入 UI 设计套件模块

配置图标资源

图标类型

准备原始图标和遮罩

准备前景和背景图

初始化资源管理器

获取图标描述符

调用图标处理接口

返回 PixelMap

在 Image 组件中显示

批量图标处理流程

下面是批量图标处理的流程:

准备图标列表

配置处理选项

设置图标大小

设置是否加边框

设置并行处理数量

调用批量处理接口

并行处理多个图标

返回处理结果数组

遍历显示所有图标

分层图标处理

分层图标就是有前景和背景两层的图标,看起来更有立体感。比如华为手机上的一些系统图标,前景是图标主体,背景是一层渐变色,这就是分层图标。

第一步:导入模块

import { LayeredDrawableDescriptor } from '@kit.ArkUI';
import { hdsDrawable } from '@kit.UIDesignKit';
import { image } from '@kit.ImageKit';
import { BusinessError } from '@kit.BasicServicesKit';
import { resourceManager } from '@kit.LocalizationKit';

导入的模块有:

  • LayeredDrawableDescriptor:分层图标描述符,用来描述前景和背景的关系。你可以理解为"分层图标的配置文件"
  • hdsDrawable:UI 设计套件的核心接口,图标处理都靠它。所有的处理方法都在这个模块里
  • image:图片处理,返回的图标是 PixelMap 格式。PixelMap 是鸿蒙系统的图片格式,可以直接显示在 Image 组件里
  • BusinessError:错误处理用的
  • resourceManager:资源管理,用来获取图标资源。你的图标文件放在 resources 目录下,需要用这个来读取

第二步:配置分层图标资源

entry/src/main/resources/base/media 目录下创建一个 JSON 文件(比如 drawable.json):

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

这个文件告诉系统:分层图标由背景图和前景图组成。你需要把背景图(background)和前景图(foreground)放到 resources/base/media 目录下。

$media:background$media:foreground 是资源引用语法,意思是引用 media 目录下的 backgroundforeground 文件。这两个文件一般是 PNG 图片。

为什么要用 JSON 配置文件?因为这样系统就知道你的前景和背景分别是什么,处理的时候就能把它们合成分层图标。如果你不配置这个文件,系统不知道哪张是前景、哪张是背景。

第三步:初始化资源

aboutToAppear(): void {
  this.resManager = getContext().resourceManager;
  if (!this.resManager) {
    return;
  }
  this.layeredDrawableDescriptor = (this.resManager.getDrawableDescriptor(
    $r('app.media.drawable').id
  )) as LayeredDrawableDescriptor;
}

在页面出现时,初始化资源管理器,获取分层图标的描述符。

getContext().resourceManager 获取当前应用的资源管理器。资源管理器是读取应用资源文件的工具,你的图片、JSON 配置文件等都靠它来读取。

getDrawableDescriptor 会读取我们之前创建的 JSON 配置文件,然后返回一个 LayeredDrawableDescriptor 对象。这个对象包含了前景和背景的信息,后面调用图标处理接口时需要用到。

第四步:调用分层图标接口

同步方式获取单个分层图标:

private getHdsLayeredIconWithBorder(): image.PixelMap | null {
  try {
    return hdsDrawable.getHdsLayeredIcon(
      this.bundleName,           // 应用包名
      this.layeredDrawableDescriptor, // 分层图标描述符
      48,                         // 图标大小(像素)
      true                        // 是否有边框
    );
  } catch (err) {
    let message = (err as BusinessError).message;
    let code = (err as BusinessError).code;
    console.error(`getHdsLayeredIcon failed, code: ${code}, message: ${message}`);
    return null;
  }
}

调用 hdsDrawable.getHdsLayeredIcon 获取处理后的分层图标。参数说明:

  • bundleName:应用的包名,比如 com.example.myapp。系统用这个来标识是哪个应用的图标
  • layeredDrawableDescriptor:分层图标描述符,就是上一步获取的那个对象
  • 48:图标大小,单位是像素。48 像素是比较常用的尺寸,适合在列表里显示
  • true:是否添加 HarmonyOS 风格的边框。加了边框的图标看起来更统一,和系统其他图标风格一致

这个接口是同步的,会直接返回处理后的 PixelMap。如果处理失败,会抛出异常,所以要用 try...catch 包起来。

异步方式获取单个分层图标:

private getHdsLayeredIconAsync(): void {
  try {
    hdsDrawable.getHdsLayeredIconAsync(
      this.bundleName,
      this.layeredDrawableDescriptor,
      48,
      true
    ).then((data: image.PixelMap) => {
      this.layeredIconAsyncResult = data;
    }).catch((err: BusinessError) => {
      console.error(`getHdsLayeredIconAsync error, code: ${err.code}, msg: ${err.message}`);
    });
  } catch (err) {
    let message = (err as BusinessError).message;
    let code = (err as BusinessError).code;
    console.error(`getHdsLayeredIconAsync failed, code: ${code}, message: ${message}`);
  }
}

异步版本的接口返回一个 Promise,适合在需要避免阻塞主线程的场景下使用。

为什么要区分同步和异步?因为图标处理可能需要一些时间,特别是处理大图标或者批量处理的时候。如果用同步接口,界面可能会卡顿。用异步接口的话,处理过程在后台进行,界面不会卡。

批量处理分层图标:

private getHdsLayeredIcons(): void {
  if (!this.layeredDrawableDescriptor) {
    return;
  }

  // 配置选项
  let options: hdsDrawable.Options = {
    size: 48,           // 图标大小
    hasBorder: true,    // 是否有边框
    parallelNumber: 4   // 并行处理数量
  };

  // 准备要处理的图标列表
  let layeredIcons: Array<hdsDrawable.LayeredIcon> = [];
  for (let i = 0; i < 10; i++) {
    layeredIcons.push({
      bundleName: `${this.bundleName}-${i}`,
      layeredDrawableDescriptor: this.layeredDrawableDescriptor
    });
  }

  // 批量处理
  try {
    hdsDrawable.getHdsLayeredIcons(layeredIcons, options)
      .then((data: Array<hdsDrawable.ProcessedIcon>) => {
        this.layeredIconsResult = data;
      })
      .catch((err: BusinessError) => {
        console.error(`getHdsLayeredIcons error, code: ${err.code}, msg: ${err.message}`);
      });
  } catch (err) {
    console.error(`getHdsLayeredIcons failed`);
  }
}

批量处理接口 getHdsLayeredIcons 可以一次处理多个图标。这在应用商店、文件管理器等需要显示大量图标的场景下特别有用。

options 里的配置:

  • size:图标大小,和单个处理一样
  • hasBorder:是否加边框
  • parallelNumber:并行处理数量。设为 4 表示同时处理 4 个图标,能提高效率。但不要设太大,否则会占用太多系统资源

第五步:显示图标

Image(this.getHdsLayeredIconWithBorder())
  .width(48)
  .height(48)

把处理后的图标显示在 Image 组件里就行了。注意宽高要和你处理时设置的大小一致,否则图标可能会被拉伸或压缩。

单层图标处理

单层图标处理和分层图标处理类似,但不需要配置分层资源。你的图标就是一张普通的图片,处理之后加上 HarmonyOS 风格的遮罩和边框。

第一步:导入模块

import { LayeredDrawableDescriptor, DrawableDescriptor } from '@kit.ArkUI';
import { hdsDrawable } from '@kit.UIDesignKit';
import { image } from '@kit.ImageKit';
import { BusinessError } from '@kit.BasicServicesKit';
import { resourceManager } from '@kit.LocalizationKit';

多导入了一个 DrawableDescriptor,这是单层图标用的描述符。和分层图标不同,单层图标只有一个图层,所以用 DrawableDescriptor 而不是 LayeredDrawableDescriptor

第二步:初始化资源

aboutToAppear(): void {
  this.resManager = getContext().resourceManager;
  if (!this.resManager) {
    return;
  }

  // 获取分层图标描述符(用于遮罩)
  this.layeredDrawableDescriptor = (this.resManager.getDrawableDescriptor(
    $r('app.media.drawable').id
  )) as LayeredDrawableDescriptor;

  // 获取单层图标描述符
  this.drawableDescriptor = (this.resManager?.getDrawableDescriptor(
    $r('app.media.normal_icon')
  )) as DrawableDescriptor;
}

单层图标处理需要两个描述符:

  • layeredDrawableDescriptor:提供遮罩(mask),用于 HarmonyOS 风格的圆形遮罩。你可能会问:单层图标为什么需要分层图标描述符?因为遮罩是从分层图标配置里提取的,系统用这个遮罩来把你的方形图标裁剪成圆形
  • drawableDescriptor:原始的应用图标,就是你放在 resources 目录下的那张图片

第三步:调用单层图标接口

同步方式:

private getHdsIconWithBorder(): image.PixelMap | null {
  try {
    return hdsDrawable.getHdsIcon(
      this.bundleName,                              // 应用包名
      this.drawableDescriptor?.getPixelMap(),       // 原始图标
      48,                                            // 图标大小
      this.layeredDrawableDescriptor?.getMask().getPixelMap(), // 遮罩
      true                                           // 是否有边框
    );
  } catch (err) {
    let message = (err as BusinessError).message;
    let code = (err as BusinessError).code;
    console.error(`getHdsIcon failed, code: ${code}, message: ${message}`);
    return null;
  }
}

调用 hdsDrawable.getHdsIcon 处理单层图标。参数比处理分层图标多了一个遮罩参数:

  • this.drawableDescriptor?.getPixelMap():原始图标的 PixelMap。getPixelMap() 是把描述符转成 PixelMap 格式
  • this.layeredDrawableDescriptor?.getMask().getPixelMap():遮罩的 PixelMap。getMask() 获取遮罩,getPixelMap() 转成 PixelMap 格式

遮罩的作用是把你的方形图标裁剪成 HarmonyOS 的标准形状(一般是圆形)。就像用一个圆形的模具去切一块方形的饼干,切完就变成圆形的了。

异步方式:

private getHdsIconAsync(): void {
  try {
    hdsDrawable.getHdsIconAsync(
      this.bundleName,
      this.drawableDescriptor?.getPixelMap(),
      48,
      this.layeredDrawableDescriptor?.getMask().getPixelMap(),
      true
    ).then((data: image.PixelMap) => {
      this.iconAsyncResult = data;
    }).catch((err: BusinessError) => {
      console.error(`getHdsIconAsync error, code: ${err.code}, msg: ${err.message}`);
    });
  } catch (err) {
    console.error(`getHdsIconAsync failed`);
  }
}

异步版本和同步版本参数一样,只是返回 Promise 而不是直接返回结果。

批量处理:

getHdsIcons(): void {
  if (!this.drawableDescriptor || !this.layeredDrawableDescriptor) {
    return;
  }

  let options: hdsDrawable.Options = {
    size: 48,
    hasBorder: true,
    parallelNumber: 4
  };

  let icons: Array<hdsDrawable.Icon> = [];
  for (let i = 0; i < 10; i++) {
    icons.push({
      bundleName: `${this.bundleName}-${i}`,
      pixelMap: this.drawableDescriptor.getPixelMap()
    });
  }

  try {
    hdsDrawable.getHdsIcons(
      icons,
      this.layeredDrawableDescriptor.getMask().getPixelMap(),
      options
    ).then((data: Array<hdsDrawable.ProcessedIcon>) => {
      this.iconsResult = data;
    }).catch((err: BusinessError) => {
      console.error(`getHdsIcons error, code: ${err.code}, msg: ${err.message}`);
    });
  } catch (err) {
    console.error(`getHdsIcons failed`);
  }
}

批量处理接口 getHdsIcons 和分层图标的批量接口类似,但传入的是单层图标的数组。遮罩参数单独传入,因为所有图标共用同一个遮罩。

实际应用场景

UI 设计套件在实际开发中有很多用途:

应用商店

// 应用商店里显示应用图标
async function displayAppIcon(bundleName: string, iconPixelMap: image.PixelMap) {
  let mask = layeredDrawableDescriptor.getMask().getPixelMap();
  let processedIcon = hdsDrawable.getHdsIcon(bundleName, iconPixelMap, 48, mask, true);
  // 显示处理后的图标
}

在应用商店里,每个应用的图标都需要和 HarmonyOS 的设计风格一致。用 UI 设计套件处理一下,图标就会自动加上 HarmonyOS 风格的边框和遮罩,看起来和系统应用的图标一样精致。

文件管理器

// 文件管理器里显示应用图标
async function showAppIcons(appList: Array<{name: string, icon: image.PixelMap}>) {
  let icons: Array<hdsDrawable.Icon> = appList.map(app => ({
    bundleName: app.name,
    pixelMap: app.icon
  }));

  let options: hdsDrawable.Options = { size: 48, hasBorder: true, parallelNumber: 4 };
  let mask = layeredDrawableDescriptor.getMask().getPixelMap();
  let processedIcons = await hdsDrawable.getHdsIcons(icons, mask, options);
  // 显示处理后的图标列表
}

在文件管理器里,如果要显示已安装应用的图标列表,可以用批量处理接口一次性处理所有图标,效率更高。想象一下,如果你有 100 个应用图标要处理,一个一个处理太慢了,批量处理能快很多。

桌面小部件

// 桌面小部件里显示应用图标
function getWidgetIcon(bundleName: string): image.PixelMap | null {
  return hdsDrawable.getHdsLayeredIcon(bundleName, layeredDrawableDescriptor, 96, false);
}

桌面小部件里的图标可能需要更大的尺寸(比如 96 像素),而且可能不需要边框。可以根据需求调整参数,让图标在不同场景下都有最好的显示效果。

适用场景

UI 设计套件适合以下场景:

  • 应用商店:展示应用图标
  • 文件管理器:显示应用图标
  • 桌面小部件:显示应用图标
  • 设置页面:显示应用图标
  • 任何需要显示应用图标的地方

注意事项

  1. 图标资源:分层图标需要配置前景和背景资源,确保资源文件存在且路径正确
  2. 图标大小:根据实际需求设置合适的图标大小,太大会影响性能和内存占用
  3. 批量处理:如果需要处理大量图标,建议使用批量接口并设置合适的并行数。并行数太大会占用太多 CPU,太小又处理得慢
  4. 错误处理:图标处理可能会失败(比如资源文件不存在),要做好错误处理
  5. 内存管理:处理后的 PixelMap 会占用内存,不需要时要及时释放。特别是在批量处理的场景下,要注意内存使用

总结

UI 设计套件让你的应用图标更精致,核心流程:

  1. 导入 UI 设计套件模块
  2. 配置图标资源(分层图标需要前景和背景)
  3. 初始化资源管理器,获取图标描述符
  4. 调用图标处理接口,获取处理后的图标
  5. 在 Image 组件里显示图标

掌握了这些,你就能让你的应用图标和 HarmonyOS 的设计风格完美融合,提升应用的整体品质。

Logo

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

更多推荐