前言

之前做项目图标都是切 PNG,1x 2x 3x 一堆文件塞得资源目录乱七八糟。后来发现 HarmonyOS7 的 SymbolGlyph 直接用系统图标,爽到飞起——直到产品说"这个图标系统里没有,要自定义的"。

好吧,自定义 SVG 图标这条路迟早得走。今天把从系统图标到自定义 SVG 的完整方案梳理一遍。
图标是 UI 里看着不起眼但特别重要的东西。一个 App 动辄几十上百个图标,管理不好就是灾难。HarmonyOS7 给了三条路:

A hand-drawn doodle illustration on pure white pap

  1. 系统 Symbol 图标 —— 零配置,拿来就用
  2. Svg 组件绘制 —— 纯代码画,适合简单图形
  3. SVG 资源文件 —— 设计师出图,开发引用,最常用

三条路各有利弊,往下看。

图标方案对比

方案 适用场景 优点 缺点
SymbolGlyph 系统标准图标 零配置、支持动效、自动适配字体粗细 只有系统预置图标,约433个
Svg 组件绘制 简单几何图形 纯代码、动态可控 复杂图形代码量爆炸
SVG 资源文件 自定义设计图标 任意图形、矢量不失真 需要管理资源文件
PNG 图片 位图/特殊效果 兼容性好 多分辨率、文件大、不能动态着色

我的建议:系统图标能用的就用,不够的用 SVG 资源,别再用 PNG 了。

SymbolGlyph 系统图标

SymbolGlyph 是 HarmonyOS7 最省心的图标方案,直接引用系统图标库:

A hand-drawn doodle illustration on pure white pap

@Entry
@Component
struct SymbolGlyphDemo {
  build() {
    Flex({ direction: FlexDirection.Row, wrap: FlexWrap.Wrap, alignItems: ItemAlign.Center }) {
      SymbolGlyph($r('sys.symbol.ohos_wifi'))
        .fontSize(28)
        .fontColor([Color.Black])
        .renderingStrategy(SymbolRenderingStrategy.SINGLE)

      SymbolGlyph($r('sys.symbol.ohos_folder_badge_plus'))
        .fontSize(28)
        .fontColor([Color.Blue])
        .renderingStrategy(SymbolRenderingStrategy.SINGLE)

      SymbolGlyph($r('sys.symbol.airplane_fill'))
        .fontSize(28)
        .fontColor([Color.Red])
        .renderingStrategy(SymbolRenderingStrategy.SINGLE)

      SymbolGlyph($r('sys.symbol.checkmark_circle'))
        .fontSize(28)
        .fontColor([Color.Black, Color.Green, Color.White])
        .renderingStrategy(SymbolRenderingStrategy.MULTIPLE_COLOR)
    }
    .width('100%')
    .padding(16)
    .justifyContent(FlexAlign.SpaceAround)
  }
}

A hand-drawn doodle illustration on pure white pap

关键代码讲解:

  • $r('sys.symbol.ohos_wifi') —— 引用系统图标,sys.symbol. 是固定前缀,后面接图标标识名
  • .fontSize(28) —— 图标大小跟字号一致,设 28 就是 28x28vp
  • .fontColor([Color.Black]) —— 颜色数组,单色渲染时只生效第一个
  • renderingStrategy —— 渲染策略,决定了多颜色怎么分配到图层

渲染策略详解:

策略 说明
SINGLE 单色,只用第一个颜色
MULTIPLE_OPACITY 分层,一个颜色按不同透明度渲染各层
MULTIPLE_COLOR 多色,每个图层独立设色

图标名称去哪找?华为开发者官网有完整的 Symbol 图标库,每个图标下面都标着标识名。

自定义 SVG 资源

系统图标不够用的时候,把设计师给的 SVG 文件放进资源目录:

resources/
  base/
    media/
      icon_home.svg
      icon_search.svg
      icon_profile.svg

代码中引用:

Image($r('app.media.icon_home'))
  .width(24)
  .height(24)
  .fillColor('#333333')
  .objectFit(ImageFit.Contain)

关键代码讲解:

  • $r('app.media.icon_home') —— 引用 media 目录下的 SVG 文件
  • .fillColor('#333333') —— SVG 专用属性,动态修改填充颜色,PNG 不支持这个
  • .objectFit(ImageFit.Contain) —— 保持比例缩放

这里的 .fillColor() 就是 SVG 比 PNG 强的地方——同一张 SVG 可以在不同场景复用不同颜色。

Svg 组件绘制

如果图标很简单(比如一个圆形、一个箭头),可以直接用 Svg 组件代码画:

@Entry
@Component
struct SvgDrawDemo {
  build() {
    Column({ space: 24 }) {
      Shape() {
        Circle()
          .width(48)
          .height(48)
          .fill('#007DFF')
      }
      .width(48)
      .height(48)

      Shape() {
        Path()
          .commands('M12 4 L24 24 L0 24 Z')
          .fill('#ff4444')
      }
      .width(24)
      .height(24)
      .viewBox({ x: 0, y: 0, width: 24, height: 24 })

      Shape() {
        Line()
          .startPoint([0, 0])
          .endPoint([24, 24])
          .stroke(Color.Black)
          .strokeWidth(2)

        Line()
          .startPoint([24, 0])
          .endPoint([0, 24])
          .stroke(Color.Black)
          .strokeWidth(2)
      }
      .width(24)
      .height(24)
    }
    .padding(24)
  }
}

关键代码讲解:

  • Shape —— 所有绘制组件的容器,类似 SVG 的 <svg> 标签
  • Circle() —— 画圆,width/height 控制直径,fill 填充颜色
  • Path().commands() —— 自定义路径,M 移动,L 画线,Z 闭合。M12 4 L24 24 L0 24 Z 画了一个三角形
  • viewBox() —— 定义坐标系,类似 SVG 的 viewBox 属性,确保路径在不同尺寸下比例正确
  • Line() —— 画直线,startPoint/endPoint 是起止坐标

复杂图形别硬画,代码维护成本太高。交给设计师出 SVG 文件才是正道。

图标库管理方案

图标一多就得有管理方案,不然东一个西一个,改起来要命。

推荐结构:

resources/
  base/
    media/
      icons/
        tab/
          icon_home.svg
          icon_search.svg
          icon_message.svg
          icon_profile.svg
        action/
          icon_add.svg
          icon_delete.svg
          icon_edit.svg
        status/
          icon_success.svg
          icon_warning.svg
          icon_error.svg

命名规范:

  • 前缀按用途分类:tab_action_status_
  • 全小写+下划线,别用驼峰
  • 一个图标一个文件,别把所有图标塞一个 SVG 里

动态着色

图标着色是高频需求——Tab 栏选中变色、暗黑模式变色、主题切换变色。SVG 的 .fillColor() 就是干这个的:

@Component
struct TabIconItem {
  @Prop icon: Resource
  @Prop isActive: boolean = false

  build() {
    Image(this.icon)
      .width(24)
      .height(24)
      .fillColor(this.isActive ? '#007DFF' : '#999999')
      .objectFit(ImageFit.Contain)
  }
}

@Entry
@Component
struct DynamicColorDemo {
  @State currentTab: number = 0
  private tabs = [
    { icon: $r('app.media.icon_home'), label: '首页' },
    { icon: $r('app.media.icon_search'), label: '搜索' },
    { icon: $r('app.media.icon_profile'), label: '我的' }
  ]

  build() {
    Row() {
      ForEach(this.tabs, (tab: Record<string, Resource | string>, index: number) => {
        Column() {
          TabIconItem({
            icon: tab.icon as Resource,
            isActive: this.currentTab === index
          })
          Text(tab.label as string)
            .fontSize(12)
            .fontColor(this.currentTab === index ? '#007DFF' : '#999999')
            .margin({ top: 4 })
        }
        .layoutWeight(1)
        .onClick(() => {
          this.currentTab = index
        })
      })
    }
    .width('100%')
    .height(56)
    .padding({ top: 8, bottom: 8 })
  }
}

关键代码讲解:

  • .fillColor(this.isActive ? '#007DFF' : '#999999') —— 核心! 选中蓝色,未选中灰色,一个属性搞定
  • @Prop —— 子组件接收父组件传值,单向绑定够用
  • 同一张 SVG 文件,不同颜色渲染——这就是矢量的魅力

写在最后

图标方案就三句话:

  1. 能用 SymbolGlyph 就用,省心省力还带动效
  2. 自定义图标用 SVG 资源文件,fillColor 动态着色是杀手锏
  3. 别用 PNG,都 2026 了还切多分辨率图片,太古老了

图标管理也是工程问题,命名规范、目录结构提前定好,后面维护才不痛苦。

Logo

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

更多推荐