在这里插入图片描述

每日一句正能量

“怕输会紧张,但想赢不会。”
当你盯着“输”的可能性,身体会紧绷、会退缩;当你专注于“赢”的画面,目光会坚定、步伐会从容。恐惧让人收缩,渴望让人舒展。


一、前言:为什么 SVG 矢量图是鸿蒙应用图标体系的首选

在鸿蒙生态的多设备战略下,应用需要同时适配手机、平板、车机、手表、智慧屏等多种形态的设备,分辨率从 320dp 到 4K 不等。传统的 PNG 位图图标在这种场景下面临严峻挑战:同一图标需要导出 1x/2x/3x 甚至更多倍图,资源包体积膨胀、维护成本倍增,且在超大屏或折叠屏上放大后会出现明显的锯齿和模糊。

SVG(Scalable Vector Graphics)矢量图凭借其分辨率无关性文件体积小支持动态着色等核心优势,已成为 HarmonyOS 官方推荐的标准图标格式。华为开发者联盟图标库原生支持 SVG 导出,ArkUI 框架的 Image 组件对 SVG 提供了完整的 fillColor 动态着色能力,使得一套 SVG 资源即可覆盖全场景、全主题。

本文将基于 HarmonyOS 6(API 23)的最新特性,从设计规范、资源管理、组件封装、主题适配、性能优化五个维度,系统讲解如何在全局样式统一管理的框架下,构建一套企业级的 SVG 矢量图使用体系。


二、HarmonyOS SVG 矢量图系统架构概览

在全局样式统一管理的语境下,SVG 矢量图不是孤立存在的视觉元素,而是整个设计系统(Design System)的核心组成部分。我们需要从架构层面建立"设计规范 → 资源管理 → 组件封装 → 主题适配 → 多设备渲染"的完整链路。

在这里插入图片描述

图1 SVG矢量图统一管理体系架构

如上图所示,整个体系分为五个层级:

  1. 设计规范层:定义统一的图标网格、描边粗细、圆角规范,确保所有图标在视觉语言上保持一致;
  2. 资源管理层:通过 resources 目录的限定词机制(base/zh_CN/dark)实现资源的分层管理;
  3. 样式统一管理:将图标颜色绑定到全局主题色变量,实现"一处修改,全局生效";
  4. 组件封装层:封装 SvgIcon 通用组件,统一处理尺寸、状态、点击反馈;
  5. 多设备自适应渲染层:利用 SVG 的矢量特性,在不同分辨率设备上自动无损缩放。

三、SVG 图标设计规范:从源头保证一致性

3.1 HarmonyOS 官方图标规范

根据 HarmonyOS 设计规范,标准图标尺寸为 24×24 vp,图标主体应保留在 22×22 vp 的安全区域内。所有图标必须遵循以下几何参数:

参数项 标准值 说明
画布尺寸 24 × 24 vp 标准图标画布
安全区域 22 × 22 vp 图标主体保留区域
描边粗细 1.5 vp 常规图标描边
复杂图标描边 1.3 vp 细节较多时微调
终点样式 圆头 (Round) 避免锐利端点
断口宽度 1.3 vp 开口间距
外圆角半径 3 vp 转角外圆角
内圆角半径 1.5 vp 转角内圆角
导出格式 SVG 必须包含外框线条

在这里插入图片描述

图2 SVG图标设计规范示意图

3.2 SVG 文件制作规范

为了确保 ArkUI 的 fillColor 能够正确着色,SVG 文件在制作时必须遵循以下技术规范:

(1)使用 fill 而非 stroke

fillColor 属性只作用于 SVG 的 fill 属性,对 stroke 描边无效。因此所有图标必须使用填充方式绘制:

<!-- 错误:使用 stroke 描边 -->
<circle cx="12" cy="12" r="10" stroke="#000000" stroke-width="2"/>

<!-- 正确:使用 fill 填充(用 path 实现相同形状) -->
<path d="M12 2C6.48 2 2 6.48 2 12s4.48 10 10 10 10-4.48 10-10S17.52 2 12 2z" 
      fill="#000000"/>

(2)设置默认黑色填充

SVG 源文件中应使用 fill="#000000" 作为默认颜色,这样在代码中通过 fillColor 覆盖时效果最准确:

<svg width="24" height="24" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg">
  <path d="M19 13h-6v6h-2v-6H5v-2h6V5h2v6h6v2z" fill="#000000"/>
</svg>

(3)移除多余样式

删除 opacityfiltercurrentColor 等可能影响颜色继承的属性,保持 SVG 结构简洁。

(4)视觉校正

圆形、菱形等几何图标的视觉重心天然偏低,需要略微放大(约 4%)或上移,才能与方形图标在视觉上保持体量一致。


四、资源目录管理与全局样式绑定

4.1 资源目录结构

在 HarmonyOS Stage 模型中,SVG 文件应放置在 entry/src/main/resources/base/media/ 目录下。利用限定词机制,可以实现多语言、深色模式的资源覆盖:

entry/src/main/resources/
├── base/
│   ├── media/
│   │   ├── ic_home.svg
│   │   ├── ic_search.svg
│   │   ├── ic_settings.svg
│   │   └── ic_user.svg
│   └── element/
│       └── color.json
├── dark/
│   └── element/
│       └── color.json
└── zh_CN/
    └── media/
        └── ic_home.svg    # 中文场景下的特殊图标

在这里插入图片描述

图4 SVG资源目录结构与组件封装架构

4.2 全局颜色资源定义

base/element/color.json 中定义图标相关的主题色:

{
  "color": [
    {
      "name": "icon_primary",
      "value": "#007DFF"
    },
    {
      "name": "icon_normal",
      "value": "#333333"
    },
    {
      "name": "icon_disabled",
      "value": "#BBBBBB"
    },
    {
      "name": "icon_inverse",
      "value": "#FFFFFF"
    }
  ]
}

dark/element/color.json 中定义深色模式下的对应颜色:

{
  "color": [
    {
      "name": "icon_primary",
      "value": "#4FC3F7"
    },
    {
      "name": "icon_normal",
      "value": "#E0E0E0"
    },
    {
      "name": "icon_disabled",
      "value": "#666666"
    },
    {
      "name": "icon_inverse",
      "value": "#121212"
    }
  ]
}

通过这种方式,图标颜色与业务代码完全解耦。当设计规范调整主题色时,只需修改 color.json,所有引用该颜色的 SVG 图标会自动更新,真正实现"一处修改,全局生效"。


五、SvgIcon 统一组件封装

5.1 状态枚举定义

在全局样式统一管理中,图标通常具有多种交互状态。我们定义统一的枚举来管理:

// common/enum/IconState.ets
export enum IconState {
  DEFAULT = 'default',
  ACTIVE = 'active',
  DISABLED = 'disabled',
  LOADING = 'loading'
}

// common/enum/IconSize.ets
export enum IconSize {
  SMALL = 16,
  NORMAL = 24,
  LARGE = 32,
  XLARGE = 48
}

5.2 SvgIcon 组件实现

封装一个高复用的 SvgIcon 组件,统一处理尺寸、颜色、状态和点击反馈:

// components/SvgIcon.ets
import { IconState, IconSize } from '../common/enum/IconEnum';

interface SvgIconProps {
  icon: Resource;
  size?: number | IconSize;
  state?: IconState;
  customColor?: ResourceColor;
  onClick?: () => void;
  rotate?: boolean;  // 是否支持旋转动画(用于 loading)
}

@Component
export struct SvgIcon {
  @Prop icon: Resource;
  @Prop size: number = IconSize.NORMAL;
  @Prop state: IconState = IconState.DEFAULT;
  @Prop customColor?: ResourceColor;
  @Prop rotate: boolean = false;
  @State angle: number = 0;
  onClick?: () => void;

  // 根据状态获取对应颜色
  private getIconColor(): ResourceColor {
    if (this.customColor !== undefined) {
      return this.customColor;
    }
    switch (this.state) {
      case IconState.ACTIVE:
        return $r('app.color.icon_primary');
      case IconState.DISABLED:
        return $r('app.color.icon_disabled');
      case IconState.LOADING:
        return $r('app.color.icon_primary');
      case IconState.DEFAULT:
      default:
        return $r('app.color.icon_normal');
    }
  }

  // 根据状态获取透明度
  private getOpacity(): number {
    return this.state === IconState.DISABLED ? 0.5 : 1.0;
  }

  aboutToAppear(): void {
    if (this.rotate) {
      this.startRotation();
    }
  }

  private startRotation(): void {
    // 使用动画实现 loading 旋转效果
    animateTo({ duration: 1000, iterations: -1, curve: Curve.Linear }, () => {
      this.angle = 360;
    });
  }

  build() {
    Stack() {
      Image(this.icon)
        .width(this.size)
        .height(this.size)
        .fillColor(this.getIconColor())
        .opacity(this.getOpacity())
        .objectFit(ImageFit.Contain)
        .rotate({ angle: this.angle })
        .interpolation(ImageInterpolation.High)  // 高质量插值
        .backgroundColor(Color.Transparent)       // 必须显式透明
        .transition(TransitionEffect.OPACITY)
    }
    .width(this.size + 12)   // 增加点击热区
    .height(this.size + 12)
    .backgroundColor(Color.Transparent)
    .onClick(() => {
      if (this.state !== IconState.DISABLED && this.state !== IconState.LOADING) {
        this.onClick?.();
      }
    })
  }
}

5.3 在业务中使用

// pages/HomePage.ets
import { SvgIcon } from '../components/SvgIcon';
import { IconState, IconSize } from '../common/enum/IconEnum';

@Entry
@Component
struct HomePage {
  @State currentTab: number = 0;

  build() {
    Column() {
      // 顶部导航栏图标
      Row({ space: 20 }) {
        SvgIcon({
          icon: $r('app.media.ic_search'),
          size: IconSize.NORMAL,
          state: IconState.DEFAULT,
          onClick: () => {
            // 跳转搜索页
          }
        })

        SvgIcon({
          icon: $r('app.media.ic_settings'),
          size: IconSize.NORMAL,
          state: IconState.DEFAULT
        })
      }
      .width('100%')
      .justifyContent(FlexAlign.End)
      .padding(16)

      // 底部 Tab 栏
      Row() {
        this.TabItem(0, $r('app.media.ic_home'), '首页')
        this.TabItem(1, $r('app.media.ic_category'), '分类')
        this.TabItem(2, $r('app.media.ic_message'), '消息')
        this.TabItem(3, $r('app.media.ic_user'), '我的')
      }
      .width('100%')
      .height(56)
      .backgroundColor($r('app.color.tab_bar_bg'))
      .justifyContent(FlexAlign.SpaceAround)
    }
    .width('100%')
    .height('100%')
    .backgroundColor($r('app.color.page_bg'))
  }

  @Builder
  TabItem(index: number, icon: Resource, label: string) {
    Column({ space: 4 }) {
      SvgIcon({
        icon: icon,
        size: IconSize.SMALL,
        state: this.currentTab === index ? IconState.ACTIVE : IconState.DEFAULT
      })
      Text(label)
        .fontSize(10)
        .fontColor(this.currentTab === index 
          ? $r('app.color.icon_primary') 
          : $r('app.color.icon_normal'))
    }
    .onClick(() => {
      this.currentTab = index;
    })
  }
}

六、深浅模式自适应与多主题切换

6.1 原理说明

HarmonyOS 的 fillColor 属性支持对 SVG 矢量图进行动态着色。当 SVG 源文件使用 fill="#000000" 时,fillColor 会将黑色完全替换为指定的颜色值。结合 resources/dark/element/color.json 的限定词机制,系统会在深色模式下自动加载深色主题的颜色定义。

在这里插入图片描述

图3 深浅模式SVG动态着色对比

6.2 主题切换实战

在全局样式统一管理中,主题切换通常由 AppStorage 或自定义状态管理器驱动:

// common/theme/ThemeManager.ets
import { preferences } from '@kit.ArkData';

export class ThemeManager {
  private static readonly THEME_KEY = 'app_theme';
  
  static async setTheme(isDark: boolean): Promise<void> {
    AppStorage.setOrCreate('isDarkMode', isDark);
    const pref = await preferences.getPreferences(getContext(), 'theme');
    await pref.put(ThemeManager.THEME_KEY, isDark ? 'dark' : 'light');
    await pref.flush();
  }

  static async getTheme(): Promise<boolean> {
    const pref = await preferences.getPreferences(getContext(), 'theme');
    const theme = await pref.get(ThemeManager.THEME_KEY, 'light');
    return theme === 'dark';
  }
}

Ability 启动时恢复主题设置:

// entry/src/main/ets/entryability/EntryAbility.ets
import { ThemeManager } from '../common/theme/ThemeManager';

export default class EntryAbility extends UIAbility {
  async onWindowStageCreate(windowStage: window.WindowStage): Promise<void> {
    const isDark = await ThemeManager.getTheme();
    AppStorage.setOrCreate('isDarkMode', isDark);
    
    windowStage.loadContent('pages/Index', (err) => {
      if (err.code) {
        return;
      }
    });
  }
}

6.3 监听系统主题变化

HarmonyOS 6 提供了系统主题变化的监听能力,应用可以实时响应系统级别的深色模式切换:

import { configuration } from '@kit.AbilityKit';

@Entry
@Component
struct ThemeAwarePage {
  @StorageLink('isDarkMode') isDarkMode: boolean = false;

  aboutToAppear(): void {
    // 监听系统配置变化
    getContext().on('configurationUpdate', (config: configuration.Configuration) => {
      this.isDarkMode = config.colorMode === configuration.ColorMode.COLOR_MODE_DARK;
    });
  }

  build() {
    Column() {
      SvgIcon({
        icon: $r('app.media.ic_theme'),
        size: 24,
        customColor: this.isDarkMode ? '#4FC3F7' : '#007DFF'
      })
      
      Text(this.isDarkMode ? '深色模式' : '浅色模式')
        .fontSize(16)
        .fontColor($r('app.color.text_primary'))
    }
    .width('100%')
    .height('100%')
    .backgroundColor($r('app.color.page_bg'))
  }
}

七、性能优化与最佳实践

7.1 SVG 加载性能优化

  1. 优先使用本地资源:本地 SVG 通过 $r('app.media.xxx') 加载,编译期即可进行资源校验和压缩;网络 SVG 需异步加载,增加首屏时间。

  2. 控制 SVG 复杂度:单个 SVG 的路径节点数建议控制在 200 个以内,过于复杂的路径会增加 GPU 渲染负担。

  3. 启用图片缓存:对于频繁使用的图标,ArkUI 会自动进行内存缓存,无需额外处理。

  4. 避免运行时字符串拼接:如需动态生成 SVG,应在 aboutToAppear 中预解析,避免在 build() 中重复计算。

7.2 常见问题排查

问题现象 原因分析 解决方案
fillColor 不生效 SVG 使用了 stroke 描边 将描边改为 fill 填充
图标显示灰色背景 父组件或 Image 默认背景色 显式设置 .backgroundColor(Color.Transparent)
图标边缘模糊 尺寸非整数 vp 或奇数像素 使用 16/20/24/32/48 等标准尺寸
深色模式颜色异常 使用了 currentColor 移除 SVG 中的 currentColor,改用固定黑色
多设备显示不一致 未使用 vp 单位 所有尺寸统一使用 vp 自适应单位

7.3 与全局样式的协同

SVG 矢量图体系应与全局样式系统深度集成:

// styles/GlobalStyles.ets
export class GlobalStyles {
  // 图标尺寸规范
  static readonly ICON_SIZE = {
    xs: 12,
    sm: 16,
    md: 24,
    lg: 32,
    xl: 48
  };

  // 图标颜色规范(与 color.json 保持一致)
  static readonly ICON_COLOR = {
    primary: $r('app.color.icon_primary'),
    normal: $r('app.color.icon_normal'),
    disabled: $r('app.color.icon_disabled'),
    inverse: $r('app.color.icon_inverse')
  };

  // 图标状态映射
  static getIconStateColor(state: IconState): ResourceColor {
    switch (state) {
      case IconState.ACTIVE: return GlobalStyles.ICON_COLOR.primary;
      case IconState.DISABLED: return GlobalStyles.ICON_COLOR.disabled;
      default: return GlobalStyles.ICON_COLOR.normal;
    }
  }
}

八、总结

本文从架构设计到代码实现,完整阐述了在 HarmonyOS 6(API 23)环境下,如何基于全局样式统一管理构建一套高可维护的 SVG 矢量图体系。核心要点总结如下:

  1. 设计先行:严格遵循 24×24 vp 标准网格和 1.5 vp 描边规范,使用 fill 而非 stroke 绘制;
  2. 资源分层:利用 base/dark/zh_CN 等限定词目录实现多主题、多语言的资源管理;
  3. 颜色解耦:通过 color.json 定义全局图标色,实现设计与开发的完全解耦;
  4. 组件封装:统一封装 SvgIcon 组件,内置状态管理和主题响应能力;
  5. 动态适配:利用 fillColor 绑定主题色,一套 SVG 资源自动适配深浅模式;
  6. 性能优先:控制 SVG 复杂度,优先使用本地资源,避免运行时字符串拼接;
  7. 全场景覆盖:SVG 的矢量特性天然适配手机、平板、车机、手表等多设备场景。

通过这套体系的落地,项目中的图标资源维护成本可降低 60% 以上,主题切换实现"零代码改动",多端适配从"多倍图导出"简化为"一套 SVG 走天下"。


转载自:https://blog.csdn.net/u014727709/article/details/163482018
欢迎 👍点赞✍评论⭐收藏,欢迎指正

Logo

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

更多推荐