HarmonyOS ArkTS IconFont 实践指南

一次完整的 iconfont 字体图标调试之旅

前言

在 HarmonyOS 应用开发中,使用 iconfont 字体图标是一种常见的 UI 实现方式。然而在实际开发中,经常会遇到"字体文件已加载但图标不显示"的问题。本文记录了一次完整的 iconfont 调试过程,总结了常见问题及解决方案。

问题现象

应用中使用了自定义的 iconfont.ttf 字体文件,但在页面渲染时,所有图标位置都显示为空白方框 ⬜,即使日志显示字体已成功注册。

typescript 复制代码
// 日志显示注册成功
[Font] iconfont registered successfully

// 但页面显示空白方框
Text('\ue81a')
  .fontFamily('iconfont')  // 显示 ⬜ 而不是图标

问题排查过程

第一步:检查字体文件路径

问题描述:最初使用了错误的路径格式

typescript 复制代码
// ❌ 错误写法
font.registerFont({
  familyName: 'iconfont',
  familySrc: 'iconfont.ttf'  // 字符串路径
});

// ✅ 正确写法
font.registerFont({
  familyName: 'iconfont',
  familySrc: $rawfile('iconfont.ttf')  // 使用 $rawfile() 包装
});

关键点

  • HarmonyOS 中 rawfile 资源必须使用 $rawfile() 函数访问
  • 字体文件放在 entry/src/main/resources/rawfile/ 目录下
  • 不要使用相对路径字符串

参考文档HarmonyOS Font API


第二步:验证 Unicode 编码映射

问题描述:代码中的 Unicode 值与字体文件中的实际编码不匹配

如何验证编码是否正确?
  1. 找到源 CSS 文件 (通常在 iconfont 下载包中):
css 复制代码
/* iconfont.css */
.icon-shouye:before {
  content: "\e81a";  /* 正确的 Unicode 编码 */
}

.icon-chazhao:before {
  content: "\e829";
}
  1. 对比代码中的映射
typescript 复制代码
// ❌ 错误的映射
const iconMap: Record<string, string> = {
  'shouye': '\ue601',   // 与 CSS 不匹配!
  'chazhao': '\ue6ae'   // 与 CSS 不匹配!
}

// ✅ 正确的映射
const iconMap: Record<string, string> = {
  'shouye': '\ue81a',   // 与 CSS 一致
  'chazhao': '\ue829'   // 与 CSS 一致
}
批量提取正确编码的方法

可以使用正则表达式从 CSS 文件中提取:

bash 复制代码
# 从 iconfont.css 提取所有图标编码
grep -oP '\.icon-\K[^:]+(?=:before)' iconfont.css
grep -oP 'content: "\K\\[^"]+' iconfont.css

或者查看 iconfont.json 文件:

json 复制代码
{
  "glyphs": [
    {
      "name": "首页",
      "font_class": "shouye",
      "unicode": "e81a",
      "unicode_decimal": 59418
    }
  ]
}

第三步:检查字体文件版本

问题描述:即使编码正确,图标仍不显示

通过 MD5 校验发现字体文件不匹配:

bash 复制代码
# 检查字体文件 MD5
md5sum entry/src/main/resources/rawfile/iconfont.ttf
# 输出: 4e1f4122795ebf3c942c4bac45c9f5f8  40KB (错误的文件)

md5sum vue/static/icon/iconfont.ttf
# 输出: 495b2770ac78a7c25a1039931dbe20ac  35KB (正确的文件)

解决方案:从源项目复制正确的字体文件

bash 复制代码
cp vue/static/icon/iconfont.ttf entry/src/main/resources/rawfile/iconfont.ttf

注意

  • 确保字体文件与 CSS/JSON 配置文件来自同一个 iconfont 下载包
  • 不同版本的字体文件可能包含不同的字形
  • 更新字体文件后需要 Clean Project 清理构建缓存

第四步:字体注册时机问题(核心问题)

这是最关键的问题! 即使前面都正确,图标可能仍然不显示。

问题分析
typescript 复制代码
// EntryAbility.ets 中全局注册
export default class EntryAbility extends UIAbility {
  onWindowStageCreate(windowStage: window.WindowStage): void {
    // 全局注册字体
    font.registerFont({
      familyName: 'iconfont',
      familySrc: $rawfile('iconfont.ttf')
    });

    // 加载页面
    windowStage.loadContent('pages/Index', (err) => {
      // 页面可能在字体注册完成前就开始渲染
    });
  }
}

问题原因

  • EntryAbility 中的全局注册与页面渲染存在时序问题
  • 页面可能在字体完全加载前就开始渲染
  • 不同页面的生命周期可能导致字体不可用
解决方案:页面级字体注册

创建工具函数 (common/IconFont.ets):

typescript 复制代码
import { Font } from '@kit.ArkUI';

/**
 * 注册 iconfont 字体
 * 在每个需要使用图标的页面的 aboutToAppear 中调用
 */
export function registerIconFont(uiContext: UIContext): void {
  try {
    const font: Font = uiContext.getFont();
    font.registerFont({
      familyName: 'iconfont',
      familySrc: $rawfile('iconfont.ttf')
    });
    console.info('[IconFont] Font registered successfully');
  } catch (err) {
    console.error('[IconFont] Failed to register font:', JSON.stringify(err));
  }
}

/**
 * 获取图标字符
 */
export function getIconChar(icon: string): string {
  const iconMap: Record<string, string> = {
    'shouye': '\ue81a',
    'chazhao': '\ue829',
    'wendu': '\ue80f',
    // ... 其他图标映射
  };
  return iconMap[icon] || '';
}

在每个页面中使用

typescript 复制代码
import { registerIconFont, getIconChar } from '../common/IconFont';

@Entry
@Component
struct MyPage {
  aboutToAppear() {
    // ✅ 关键:在页面生命周期中注册字体
    registerIconFont(this.getUIContext());

    // 其他初始化代码...
  }

  build() {
    Column() {
      Text(getIconChar('shouye'))
        .fontSize(24)
        .fontFamily('iconfont')  // 现在可以正常显示了!
    }
  }
}

为什么这样可以解决问题?

  1. 时序保证:字体在页面组件初始化时注册,确保渲染前字体已可用
  2. 上下文正确:使用页面自己的 UIContext,避免全局上下文问题
  3. 重复注册安全:HarmonyOS 允许重复注册同一字体,后续注册会被忽略
  4. 独立性强:每个页面独立管理字体,不依赖全局状态

完整解决方案

1. 项目结构

复制代码
entry/src/main/
├── ets/
│   ├── common/
│   │   └── IconFont.ets          # 字体工具函数
│   ├── pages/
│   │   ├── Index.ets             # 使用字体的页面
│   │   └── LoginPage.ets
│   └── entryability/
│       └── EntryAbility.ets      # 可选的全局注册
└── resources/
    └── rawfile/
        ├── iconfont.ttf          # 字体文件
        ├── iconfont.css          # 编码参考
        └── iconfont.json         # 元数据

2. IconFont.ets 工具文件

typescript 复制代码
import { Font } from '@kit.ArkUI';

/**
 * 注册 iconfont 字体
 * 必须在每个页面的 aboutToAppear 中调用
 */
export function registerIconFont(uiContext: UIContext): void {
  try {
    const font: Font = uiContext.getFont();
    font.registerFont({
      familyName: 'iconfont',
      familySrc: $rawfile('iconfont.ttf')
    });
    console.info('[IconFont] Font registered successfully');
  } catch (err) {
    console.error('[IconFont] Failed to register font:', JSON.stringify(err));
  }
}

/**
 * 图标映射表(从 iconfont.css 提取)
 */
const iconMap: Record<string, string> = {
  // 导航图标
  'shouye': '\ue81a',         // 首页
  'chazhao': '\ue829',        // 搜索
  'touxiang': '\ue602',       // 用户
  'downshow': '\ue608',       // 下拉
  'upshow': '\ue609',         // 上拉

  // 功能图标
  'wendu': '\ue80f',          // 体温
  'yaoping': '\ue820',        // 药瓶
  'zhushe': '\ue816',         // 注射
  'shuye': '\ue91f',          // 输液

  // 系统图标
  'right': '\ue627',          // 右箭头
  'left': '\ue626',           // 左箭头
  'guanbi': '\ue61e',         // 关闭
  'gengxin': '\ue628',        // 更新
  'tuichu': '\ue632',         // 退出

  // ... 添加所有图标映射
};

/**
 * 获取图标字符
 */
export function getIconChar(icon: string): string {
  return iconMap[icon] || '';
}

/**
 * IconFont 组件(可选)
 */
@Component
export struct IconFont {
  @Prop icon: string = '';
  @Prop fontSize: number = 20;
  @Prop color: string = '#000000';

  build() {
    Text(getIconChar(this.icon))
      .fontSize(this.fontSize)
      .fontColor(this.color)
      .fontFamily('iconfont')
  }
}

3. 页面中使用

方式一:使用工具函数(推荐)
typescript 复制代码
import { registerIconFont, getIconChar } from '../common/IconFont';

@Entry
@Component
struct HomePage {
  aboutToAppear() {
    // 必须:注册字体
    registerIconFont(this.getUIContext());
  }

  build() {
    Column() {
      // 方式1:直接使用 Text
      Text(getIconChar('shouye'))
        .fontSize(24)
        .fontColor('#0092A1')
        .fontFamily('iconfont')

      // 方式2:在 Row 中使用
      Row() {
        Text(getIconChar('chazhao'))
          .fontSize(20)
          .fontFamily('iconfont')
          .margin({ right: 10 })

        Text('搜索')
          .fontSize(16)
      }
    }
  }
}
方式二:使用 IconFont 组件
typescript 复制代码
import { registerIconFont, IconFont } from '../common/IconFont';

@Entry
@Component
struct HomePage {
  aboutToAppear() {
    registerIconFont(this.getUIContext());
  }

  build() {
    Column() {
      IconFont({
        icon: 'shouye',
        fontSize: 24,
        color: '#0092A1'
      })

      IconFont({
        icon: 'chazhao',
        fontSize: 20
      })
    }
  }
}

4. 常见使用场景

TabBar 图标
typescript 复制代码
@Builder
buildTabBar(title: string, index: number, iconName: string) {
  Column() {
    Text(getIconChar(iconName))
      .fontSize(24)
      .fontColor(this.currentIndex === index ? '#0092A1' : '#666666')
      .fontFamily('iconfont')

    Text(title)
      .fontSize(12)
      .fontColor(this.currentIndex === index ? '#0092A1' : '#666666')
      .margin({ top: 4 })
  }
}
按钮图标
typescript 复制代码
Button() {
  Row() {
    Text(getIconChar('gengxin'))
      .fontSize(16)
      .fontColor(Color.White)
      .fontFamily('iconfont')
      .margin({ right: 5 })

    Text('更新')
      .fontSize(14)
      .fontColor(Color.White)
  }
}
.backgroundColor('#0092A1')
列表项图标
typescript 复制代码
Row() {
  Text(getIconChar('right'))
    .fontSize(14)
    .fontFamily('iconfont')
    .fontColor('#999999')
}

调试技巧

1. 创建字体测试页面

typescript 复制代码
import { registerIconFont, getIconChar } from '../common/IconFont';
import { font } from '@kit.ArkUI';

@Entry
@Component
struct FontTestPage {
  @State fontStatus: string = '准备注册字体...';
  @State testIcons: string[] = ['shouye', 'chazhao', 'wendu', 'yaoping'];

  aboutToAppear() {
    try {
      // 注册字体
      font.registerFont({
        familyName: 'iconfont',
        familySrc: $rawfile('iconfont.ttf')
      });
      this.fontStatus = '✅ 字体注册成功';
    } catch (err) {
      this.fontStatus = '❌ 字体注册失败: ' + JSON.stringify(err);
    }
  }

  build() {
    Scroll() {
      Column() {
        Text('iconfont 显示测试')
          .fontSize(20)
          .fontWeight(FontWeight.Bold)
          .margin({ top: 20, bottom: 20 })

        Text(this.fontStatus)
          .fontSize(14)
          .fontColor(this.fontStatus.includes('成功') ? '#4CAF50' : '#FF6B6B')
          .margin({ bottom: 30 })

        // 测试1: 直接 Unicode
        Text('测试1: 直接 Unicode')
          .fontSize(16)
          .margin({ top: 20, bottom: 10 })

        Row({ space: 20 }) {
          Text('\ue81a')
            .fontSize(32)
            .fontFamily('iconfont')
            .border({ width: 1, color: '#FF6B6B' })
            .padding(10)

          Text('应显示"首页"图标')
            .fontSize(12)
        }

        // 测试2: getIconChar 函数
        Text('测试2: getIconChar 函数')
          .fontSize(16)
          .margin({ top: 20, bottom: 10 })

        Row({ space: 20 }) {
          Text(getIconChar('shouye'))
            .fontSize(32)
            .fontFamily('iconfont')
            .border({ width: 1, color: '#4CAF50' })
            .padding(10)

          Text('应显示"首页"图标')
            .fontSize(12)
        }

        // 测试3: 多个图标
        Text('测试3: 多个图标')
          .fontSize(16)
          .margin({ top: 20, bottom: 10 })

        GridRow({ columns: 4, gutter: { x: 10, y: 10 } }) {
          ForEach(this.testIcons, (iconName: string) => {
            GridCol() {
              Column() {
                Text(getIconChar(iconName))
                  .fontSize(32)
                  .fontFamily('iconfont')
                  .border({ width: 1, color: '#0092A1' })
                  .width(60)
                  .height(60)
                  .textAlign(TextAlign.Center)

                Text(iconName)
                  .fontSize(10)
                  .margin({ top: 5 })
              }
            }
          })
        }

        // 测试4: 不指定 fontFamily
        Text('测试4: 不指定 fontFamily')
          .fontSize(16)
          .margin({ top: 20, bottom: 10 })

        Text('\ue81a')
          .fontSize(32)
          .border({ width: 1, color: '#FFA500' })
          .padding(10)

        Text('← 不指定 fontFamily,应显示空白方框')
          .fontSize(12)
          .fontColor('#FFA500')

        // 测试5: 普通文字
        Text('测试5: 普通文字')
          .fontSize(16)
          .margin({ top: 20, bottom: 10 })

        Text('测试文字 ABC 123')
          .fontSize(16)
          .fontFamily('iconfont')
          .border({ width: 1, color: '#999999' })
          .padding(10)

        Text('← 用 iconfont 显示文字')
          .fontSize(12)
      }
      .width('100%')
      .padding(20)
    }
  }
}

2. 日志调试

typescript 复制代码
// 在 aboutToAppear 中添加详细日志
aboutToAppear() {
  console.info('===== 页面开始初始化 =====');

  try {
    const uiContext = this.getUIContext();
    console.info('UIContext 获取成功');

    const font = uiContext.getFont();
    console.info('Font 对象获取成功');

    registerIconFont(uiContext);
    console.info('字体注册完成');
  } catch (error) {
    console.error('字体注册失败:', JSON.stringify(error));
  }

  console.info('===== 页面初始化完成 =====');
}

3. 检查构建产物

bash 复制代码
# 清理构建缓存
rm -rf entry/build

# 或使用 DevEco Studio
Build → Clean Project

# 重新构建
Build → Rebuild Project

4. 验证字体文件

bash 复制代码
# 检查文件是否存在
ls -lh entry/src/main/resources/rawfile/iconfont.ttf

# 验证 MD5(与源文件对比)
md5sum entry/src/main/resources/rawfile/iconfont.ttf
md5sum path/to/original/iconfont.ttf

# 查看字体文件信息(macOS/Linux)
otfinfo -i iconfont.ttf

常见问题 FAQ

Q1: 为什么有的图标显示正常,有的显示空白?

A: Unicode 编码映射不完整或错误。

解决方法

  1. 检查 iconMap 中是否包含该图标的映射
  2. 验证 Unicode 值是否与 CSS 文件一致
  3. 确认字体文件中包含该字形
typescript 复制代码
// 添加缺失的图标映射
const iconMap: Record<string, string> = {
  'missing-icon': '\uexxx',  // 从 CSS 中找到正确编码
  // ...
};

Q2: 在模拟器上正常,真机上不显示?

A: 可能是字体文件打包问题。

解决方法

  1. 确认 rawfile 目录结构正确
  2. 检查 module.json5 中没有排除 rawfile 资源
  3. Clean Project 后重新构建
  4. 确认字体文件大小正常(不是 0 字节)

Q3: 字体文件很大,如何优化?

A: 只保留需要的字形。

解决方法

  1. iconfont.cn 重新选择需要的图标
  2. 下载精简版字体文件
  3. 或使用工具裁剪字体文件:
bash 复制代码
# 使用 fonttools 裁剪字体(Python)
pip install fonttools
pyftsubset iconfont.ttf \
  --unicodes="U+e81a,U+e829,U+e80f" \
  --output-file="iconfont-subset.ttf"

Q4: 可以在组件中使用吗?

A: 可以,但需要正确传递 UIContext。

typescript 复制代码
@Component
export struct MyComponent {
  aboutToAppear() {
    // 组件也需要注册字体
    registerIconFont(this.getUIContext());
  }

  build() {
    Text(getIconChar('shouye'))
      .fontFamily('iconfont')
  }
}

Q5: 动态加载字体文件?

A: 当前不支持运行时动态加载,字体必须在编译时打包。

替代方案

  • 使用 Image 组件加载 SVG
  • 使用系统图标库
  • 预先打包多个字体文件切换使用

Q6: 字体注册失败,提示权限错误?

A : 检查 module.json5 权限配置。

json5 复制代码
{
  "module": {
    "requestPermissions": [
      // 不需要特殊权限访问 rawfile
    ]
  }
}

如果仍有问题,检查文件路径:

  • $rawfile('iconfont.ttf')
  • $rawfile('/iconfont.ttf')
  • $rawfile('fonts/iconfont.ttf')(除非你创建了子目录)

最佳实践总结

✅ 推荐做法

  1. 每个页面独立注册字体

    typescript 复制代码
    aboutToAppear() {
      registerIconFont(this.getUIContext());
    }
  2. 使用工具函数统一管理

    • 创建 common/IconFont.ets 统一管理
    • 所有图标映射集中维护
    • 提供类型安全的访问方式
  3. 从源文件提取编码

    • 始终以 iconfont.css 为准
    • 不要手动猜测 Unicode 值
    • 使用脚本自动提取
  4. 版本控制字体文件

    • 将字体文件纳入版本控制
    • 记录 iconfont 项目 ID 和下载时间
    • 保留 CSS/JSON 配置文件用于查询
  5. 创建测试页面

    • 开发阶段保留字体测试页面
    • 方便验证新增图标
    • 快速定位显示问题

❌ 避免的做法

  1. 不要只在 EntryAbility 中注册

    typescript 复制代码
    // ❌ 不可靠
    export default class EntryAbility extends UIAbility {
      onWindowStageCreate() {
        font.registerFont(...);  // 时序问题
      }
    }
  2. 不要使用硬编码 Unicode

    typescript 复制代码
    // ❌ 维护困难
    Text('\ue81a')
    
    // ✅ 使用语义化名称
    Text(getIconChar('shouye'))
  3. 不要忘记 fontFamily

    typescript 复制代码
    // ❌ 不会显示
    Text(getIconChar('shouye'))
    
    // ✅ 必须指定 fontFamily
    Text(getIconChar('shouye'))
      .fontFamily('iconfont')
  4. 不要混用多个字体文件

    • 容易造成编码冲突
    • 增加应用体积
    • 如需多套图标,考虑使用不同 familyName
  5. 不要跳过 Clean Build

    • 更新字体文件后必须清理缓存
    • 否则可能使用旧的字体文件

工具脚本

从 CSS 提取 Unicode 映射

python 复制代码
#!/usr/bin/env python3
# extract_iconmap.py

import re
import sys

def extract_icon_map(css_file):
    """从 iconfont.css 提取图标映射"""
    with open(css_file, 'r', encoding='utf-8') as f:
        content = f.read()

    # 匹配 .icon-xxx:before { content: "\exxx"; }
    pattern = r'\.icon-([^:]+):before\s*\{\s*content:\s*"\\([^"]+)"'
    matches = re.findall(pattern, content)

    print("// 从", css_file, "提取的图标映射")
    print("const iconMap: Record<string, string> = {")

    for name, unicode in matches:
        # 转换为 TypeScript 格式
        print(f"  '{name}': '\\u{unicode}',")

    print("};")
    print(f"\n// 总共 {len(matches)} 个图标")

if __name__ == '__main__':
    if len(sys.argv) < 2:
        print("用法: python extract_iconmap.py iconfont.css")
        sys.exit(1)

    extract_icon_map(sys.argv[1])

使用方法:

bash 复制代码
python extract_iconmap.py entry/src/main/resources/rawfile/iconfont.css > icon_map.txt

验证字体文件完整性

bash 复制代码
#!/bin/bash
# check_font.sh

FONT_FILE="entry/src/main/resources/rawfile/iconfont.ttf"
CSS_FILE="entry/src/main/resources/rawfile/iconfont.css"

echo "===== 字体文件检查 ====="

# 检查文件是否存在
if [ ! -f "$FONT_FILE" ]; then
    echo "❌ 字体文件不存在: $FONT_FILE"
    exit 1
fi

if [ ! -f "$CSS_FILE" ]; then
    echo "⚠️  CSS 文件不存在: $CSS_FILE"
fi

# 文件大小
SIZE=$(ls -lh "$FONT_FILE" | awk '{print $5}')
echo "✅ 字体文件大小: $SIZE"

# MD5
MD5=$(md5sum "$FONT_FILE" | awk '{print $1}')
echo "✅ 字体文件 MD5: $MD5"

# 统计 CSS 中图标数量
if [ -f "$CSS_FILE" ]; then
    ICON_COUNT=$(grep -c "\.icon-" "$CSS_FILE")
    echo "✅ CSS 定义图标数: $ICON_COUNT"
fi

# 检查 rawfile 目录权限
PERMS=$(stat -c "%a" "$FONT_FILE" 2>/dev/null || stat -f "%p" "$FONT_FILE" | tail -c 4)
echo "✅ 文件权限: $PERMS"

echo "===== 检查完成 ====="

性能优化建议

1. 字体文件优化

typescript 复制代码
// 按需加载(如果有多个页面模块)
class FontManager {
  private static loaded: Set<string> = new Set();

  static registerOnce(uiContext: UIContext, fontName: string = 'iconfont') {
    if (this.loaded.has(fontName)) {
      console.info(`[FontManager] ${fontName} already loaded`);
      return;
    }

    try {
      const font = uiContext.getFont();
      font.registerFont({
        familyName: fontName,
        familySrc: $rawfile(`${fontName}.ttf`)
      });
      this.loaded.add(fontName);
      console.info(`[FontManager] ${fontName} registered`);
    } catch (err) {
      console.error(`[FontManager] Failed to register ${fontName}:`, err);
    }
  }
}

// 使用
aboutToAppear() {
  FontManager.registerOnce(this.getUIContext());
}

2. 图标组件缓存

typescript 复制代码
@Component
export struct CachedIconFont {
  @Prop icon: string;
  @Prop fontSize: number = 20;
  @Prop color: string = '#000000';

  // 缓存字符,避免重复查询
  @State private cachedChar: string = '';

  aboutToAppear() {
    this.cachedChar = getIconChar(this.icon);
  }

  build() {
    Text(this.cachedChar)
      .fontSize(this.fontSize)
      .fontColor(this.color)
      .fontFamily('iconfont')
  }
}

3. 减少重复注册

typescript 复制代码
// 使用单例模式
class IconFontLoader {
  private static instance: IconFontLoader;
  private loaded: boolean = false;

  private constructor() {}

  static getInstance(): IconFontLoader {
    if (!IconFontLoader.instance) {
      IconFontLoader.instance = new IconFontLoader();
    }
    return IconFontLoader.instance;
  }

  register(uiContext: UIContext): void {
    if (this.loaded) return;

    try {
      const font = uiContext.getFont();
      font.registerFont({
        familyName: 'iconfont',
        familySrc: $rawfile('iconfont.ttf')
      });
      this.loaded = true;
    } catch (err) {
      console.error('Font registration failed:', err);
    }
  }
}

// 使用
aboutToAppear() {
  IconFontLoader.getInstance().register(this.getUIContext());
}

总结

iconfont 在 HarmonyOS 中的使用核心要点:

  1. 路径格式 :必须使用 $rawfile('iconfont.ttf')
  2. Unicode 映射:必须与 CSS 文件中的编码完全一致
  3. 字体文件:确保使用正确版本的字体文件
  4. 注册时机 :✨ 在每个页面的 aboutToAppear() 中注册字体(最重要!)
  5. fontFamily :使用图标时必须指定 .fontFamily('iconfont')

通过本文的实践,你应该能够:

  • ✅ 正确集成 iconfont 到 HarmonyOS 应用
  • ✅ 快速定位图标不显示的问题
  • ✅ 建立可维护的图标管理方案
  • ✅ 避免常见的字体加载陷阱

希望这篇文章能帮助你在 HarmonyOS 开发中顺利使用 iconfont!


参考资源


作者 :[你的名字]
日期 :2025-01-05
HarmonyOS 版本 :5.0.0 (API 12)
DevEco Studio 版本:5.0.3.500


附录:完整代码示例

IconFont.ets 完整代码

typescript 复制代码
import { Font } from '@kit.ArkUI';

/**
 * 注册 iconfont 字体
 * 在每个需要使用图标的页面的 aboutToAppear 中调用
 */
export function registerIconFont(uiContext: UIContext): void {
  try {
    const font: Font = uiContext.getFont();
    font.registerFont({
      familyName: 'iconfont',
      familySrc: $rawfile('iconfont.ttf')
    });
    console.info('[IconFont] Font registered successfully');
  } catch (err) {
    console.error('[IconFont] Failed to register font:', JSON.stringify(err));
  }
}

/**
 * IconFont 字体图标组件
 * 使用方法: IconFont({ icon: 'chazhao', size: 20, color: '#333' })
 */
@Component
export struct IconFont {
  @Prop icon: string = '';
  @Prop fontSize: number = 20;
  @Prop color: string = '#000000';

  private uiContext: UIContext | null = null;
  private font: Font | null = null;

  // 组件初始化(字体已在页面 aboutToAppear 中注册)
  aboutToAppear() {
    this.uiContext = this.getUIContext();
    this.font = this.uiContext.getFont();
  }

  // iconfont 字符映射表(从 iconfont.css 提取)
  private iconMap: Record<string, string> = {
    // 常用图标
    'chazhao': '\ue829',        // 查找
    'shouye': '\ue81a',         // 首页
    'touxiang': '\ue602',       // 头像
    'downshow': '\ue608',       // 下拉
    'upshow': '\ue609',         // 上拉
    'qiehuanyonghu': '\ue6ed',  // 切换用户
    'guanbi': '\ue61e',         // 关闭

    // 功能模块图标
    'wendu': '\ue80f',          // 体温
    'yaopian': '\ue822',        // 药片(出入量)
    'youzhi': '\ue81c',         // 血糖
    'yaoping': '\ue820',        // 药瓶(病区收药/口服)
    'xietong': '\ue61a',        // 血酮
    'binglizhuanyunjilu1': '\ue615', // 转运接收
    'a-wenjianjiawenjian1': '\ue726', // 护理文件
    'dingdan': '\ue82a',        // 订单/记录

    // 执行中心图标
    'shuye': '\ue91f',          // 输液
    'zhushe': '\ue816',         // 注射
    'pifu': '\ue987',           // 皮试
    'tianjiaji': '\ue821',      // 雾化
    'yaoxiang': '\ue815',       // 护理
    'renzheng': '\ue826',       // 治疗
    'quanke': '\ue81f',         // 其他
    'shoushudao': '\ue920',     // 手术
    'huayanke': '\ue828',       // 标本
    'shiguan': '\ue81d',        // 检查
    'shuxie': '\ue9aa',         // 输血

    // 系统功能图标
    'daijiaolao': '\ue67b',     // 带教老师
    'keshi': '\ue69d',          // 科室
    'gengxin': '\ue628',        // 更新
    'qinglihuancun': '\ue631',  // 清理缓存
    'tuichu': '\ue632',         // 退出
    'a-bianzu62x': '\ue607',    // 筛选

    // 导航图标
    'right': '\ue627',          // 右箭头
    'left': '\ue626',           // 左箭头
    'top': '\ue646',            // 上箭头
    'bottom': '\ue647',         // 下箭头
  }

  build() {
    Text(this.getIconChar())
      .fontSize(this.fontSize)
      .fontColor(this.color)
      .fontFamily('iconfont')
  }

  private getIconChar(): string {
    return this.iconMap[this.icon] || '';
  }
}

/**
 * 获取图标字符(用于非组件场景)
 */
export function getIconChar(icon: string): string {
  const iconMap: Record<string, string> = {
    'chazhao': '\ue829',
    'shouye': '\ue81a',
    'touxiang': '\ue602',
    'downshow': '\ue608',
    'upshow': '\ue609',
    'qiehuanyonghu': '\ue6ed',
    'guanbi': '\ue61e',
    'wendu': '\ue80f',
    'yaopian': '\ue822',
    'youzhi': '\ue81c',
    'yaoping': '\ue820',
    'xietong': '\ue61a',
    'binglizhuanyunjilu1': '\ue615',
    'a-wenjianjiawenjian1': '\ue726',
    'dingdan': '\ue82a',
    'shuye': '\ue91f',
    'zhushe': '\ue816',
    'pifu': '\ue987',
    'tianjiaji': '\ue821',
    'yaoxiang': '\ue815',
    'renzheng': '\ue826',
    'quanke': '\ue81f',
    'shoushudao': '\ue920',
    'huayanke': '\ue828',
    'shiguan': '\ue81d',
    'shuxie': '\ue9aa',
    'daijiaolao': '\ue67b',
    'keshi': '\ue69d',
    'gengxin': '\ue628',
    'qinglihuancun': '\ue631',
    'tuichu': '\ue632',
    'a-bianzu62x': '\ue607',
    'right': '\ue627',
    'left': '\ue626',
    'top': '\ue646',
    'bottom': '\ue647',
  };
  return iconMap[icon] || '';
}

相关推荐
kirk_wang2 小时前
Flutter视频播放器在鸿蒙系统(HarmonyOS)上的适配实践
flutter·移动开发·跨平台·arkts·鸿蒙
盐焗西兰花12 小时前
鸿蒙学习实战之路:Tabs 组件开发场景最佳实践
学习·华为·harmonyos
盐焗西兰花12 小时前
鸿蒙学习实战之路 - 瀑布流操作实现
学习·华为·harmonyos
lqj_本人14 小时前
Flutter 适配鸿蒙桌面快捷入口完整指南
flutter·华为·harmonyos
春卷同学14 小时前
足球游戏 - Electron for 鸿蒙PC项目实战案例
游戏·electron·harmonyos
kirk_wang15 小时前
Flutter 三方库鸿蒙适配实践:以 Firebase Messaging 为例实现跨平台推送集成
flutter·移动开发·跨平台·arkts·鸿蒙
春卷同学17 小时前
篮球游戏 - Electron for 鸿蒙PC项目实战案例
游戏·electron·harmonyos
赵财猫._.17 小时前
【Flutter x 鸿蒙】第一篇:环境搭建与第一个鸿蒙Flutter应用运行
flutter·华为·harmonyos
春卷同学19 小时前
滑雪游戏 - Electron for 鸿蒙PC项目实战案例
游戏·electron·harmonyos