Expo Symbols(expo-symbols)跨平台符号图标方案:SF Symbols 与 Material Symbols 的集成指南
2026/9/11 6:13:49 网站建设 项目流程

Expo Symbols(expo-symbols)跨平台符号图标方案:SF Symbols 与 Material Symbols 的集成指南

【免费下载链接】expoAn open-source framework for making universal native apps with React. Expo runs on Android, iOS, and the web.项目地址: https://gitcode.com/GitHub_Trending/ex/expo

Expo Symbols 是 Expo SDK 中负责跨平台符号图标的官方模块,它在 iOS/tvOS 上渲染 Apple 的 SF Symbols,在 Android 与 Web 上渲染 Material Symbols,让开发者用同一套声明式SymbolView组件即可在多个平台展示高质量的系统图标。本文将围绕packages/expo-symbols的版本演进与源码实现,完整讲解其安装配置、组件 API、跨平台名称映射、动画效果与已知问题,帮助你直接上手并在项目中规避踩坑点。

一、模块定位与平台覆盖

从 README.md 的定位描述与 package.json 的说明来看,expo-symbols 的职责非常清晰:

Provides access to native symbol libraries across platforms for React Native and Expo apps. Uses SF Symbols on iOS/tvOS and Material Symbols on Android and web.

具体到平台支持,以当前仓库(版本 57.0.1)为准:

平台图标来源渲染实现
iOSSF Symbols)
tvOSSF Symbols同 iOS,使用固定pointSize: 18.0(见 SymbolView.swift)
macOSSF SymbolsmacOS 14+ 支持符号动画,默认 scale 为.medium(见 SymbolView.swift)
AndroidMaterial Symbols通过expo-font加载 Material Symbols 字体渲染(见 src/SymbolView.tsx)
WebMaterial Symbols与 Android 共用同一套字体渲染路径

需要说明的是,从版本演进看,平台能力是逐步补齐的:Android/Web 的 Material Symbols 支持在 55.0.0 中加入(CHANGELOG.md 中 #39516),macOS 支持在 56.0.6 中加入(#46471)。

二、安装与工程配置

托管(managed)Expo 项目

直接使用 Expo CLI 安装即可,命令会自动选取与当前 SDK 匹配的版本:

npx expo install expo-symbols

裸(bare)React Native 工程

根据 README.md 的指引,裸工程需要先完成expo包本身的安装与配置,再执行:

npx expo install expo-symbols npx pod-install

其中npx pod-install用于同步 iOS 的原生依赖。

依赖关系

从 package.json 可以看到模块自身的依赖设计:

  • 运行时依赖@expo-google-fonts/material-symbols(Android/Web 的 Material Symbols 字体包)、sf-symbols-typescript(提供 iOS SF Symbols 的类型名称,保证name属性有完整的类型提示);
  • peer 依赖expoexpo-fontreactreact-native——其中expo-font是 Android/Web 渲染路径的关键(源码中用loadAsync加载字体);
  • 子路径导出./androidWeights/*子路径专门导出各字重对应的字体资源,供 Android/Web 端按需加载。

三、SymbolView 组件核心 API

SymbolView是模块对外的主要组件,从 src/index.ts 可以看出模块整体导出结构:

export type { SFSymbol } from 'sf-symbols-typescript'; export type { AndroidSymbol } from './android'; export { unstable_getMaterialSymbolSourceAsync } from './materialImageSource'; export type * from './SymbolModule.types'; export { SymbolView } from './SymbolView';

组件的全部 Props 定义在 src/SymbolModule.types.ts,下面按用途分组说明。

基础用法

import { SymbolView } from 'expo-symbols'; export default function App() { return ( <SymbolView name="house.fill" tintColor="#007AFF" size={32} /> ); }

Props 详解

Prop类型默认值平台说明
nameSFSymbol \| { ios?, android?, web? }必填全部符号名称;传对象可做跨平台映射(见下文)
fallbackReactNode全部当某平台未定义符号时渲染的兜底内容
type'monochrome' \| 'hierarchical' \| 'palette' \| 'multicolor''monochrome'iOS符号变体(渲染模式)
scale'default' \| 'unspecified' \| 'small' \| 'medium' \| 'large''unspecified'iOS符号缩放级别
weightSymbolWeight \| { ios, android }'unspecified'全部字重;Android/Web 端需从androidWeights子路径导入对应字重
colorsColorValue \| ColorValue[]iOSpalette模式下的调色板颜色数组
sizenumber24全部符号尺寸
tintColorColorValue见下文全部着色颜色
resizeModeContentMode'scaleAspectFit'iOS图片在容器内的缩放模式
animationSpecAnimationSpeciOS动画配置(见下文)

SymbolWeight的取值集合为:'unspecified' | 'ultraLight' | 'thin' | 'light' | 'regular' | 'medium' | 'semibold' | 'bold' | 'heavy' | 'black'

ContentMode的取值集合为:'scaleToFill' | 'scaleAspectFit' | 'scaleAspectFill' | 'redraw' | 'center' | 'top' | 'bottom' | 'left' | 'right' | 'topLeft' | 'topRight' | 'bottomLeft' | 'bottomRight'

平台映射与默认兜底

当传入name为字符串时,iOS/Android/Web 会直接使用该名称查找对应平台符号库中的符号;若希望为不同平台显式指定不同符号,可传入对象:

<SymbolView name={{ ios: 'house.fill', android: 'home', web: 'home', }} />

Android 端的符号名类型AndroidSymbol来自 src/android/index.ts 中由symbols.json推导出的联合类型,androidSymbolToString会将符号名映射为字体中对应的 Unicode 码点(String.fromCharCode(symbols[symbol]))。

当平台未定义符号时(例如只提供了android未提供ios),会渲染fallback属性;iOS 实现里还会在原生 View 不存在时兜底渲染 fallback(见 src/SymbolView.ios.tsx)。

Android/Web 的颜色默认值

Android 端默认着色使用了系统平台色(源码 src/SymbolView.tsx):

const DEFAULT_SYMBOL_COLOR = Platform.OS === 'android' ? PlatformColor('@android:color/system_primary_dark') : '#7d9bd4';

即 Android 使用系统 primary dark 色,Web 端默认回退为#7d9bd4。同时该文件也确认:Android/Web 端SymbolView实际是「View + Text」组合,通过expo-fontloadAsync异步加载对应字重的 Material Symbols 字体,再用androidSymbolToString渲染字形,lineHeightfontSize都取size以保证符号占据正确的方形空间(对应 CHANGELOG 中 #41091 的修复)。

四、动画支持(iOS 17+ / macOS 14+)

animationSpec是 iOS 平台专属能力,底层映射到 Apple 的SymbolEffect(见 ios/SymbolView.swift 中addSymbolEffects的实现)。

AnimationSpec 结构

type AnimationSpec = { effect?: AnimationEffect; repeating?: boolean; repeatCount?: number; speed?: number; variableAnimationSpec?: VariableAnimationSpec; }; type AnimationEffect = { type: 'bounce' | 'pulse' | 'scale'; wholeSymbol?: boolean; // 默认 false,true 表示整个符号一起动画 direction?: 'up' | 'down'; };

原生侧的行为(对应 SymbolView.swift):

  • repeating默认为false,映射为SymbolEffectOptions.repeating / .nonRepeating
  • repeatCount通过options.repeat(abs(repeatCount))设置重复次数;
  • speed通过options.speed(speed)设置动画速度;
  • 若提供了variableAnimationSpec,优先调用addSymbolEffect(variableAnimationSpec.toVariableEffect())走可变色动画分支。

VariableAnimationSpec 可变色动画

type VariableAnimationSpec = { reversing?: boolean; // 每次重复时反向 nonReversing?: boolean; // 每次重复时不反向 cumulative?: boolean; // 各层依次点亮并保持到动画结束,会取消 iterative iterative?: boolean; // 各层短暂点亮后恢复 hideInactiveLayers?: boolean; // 完全隐藏非激活层 dimInactiveLayers?: boolean; // 以降低的不透明度绘制非激活层 };

这些效果是叠加的——每个置为true的字段都会额外叠加一个效果。使用示例:

<SymbolView name="wifi" animationSpec={{ effect: { type: 'bounce', direction: 'up' }, repeating: true, }} />

注意:原生实现中符号动画依赖if #available(iOS 17.0, tvOS 17.0, macOS 14.0, *)的运行时判断,即 iOS 17 / tvOS 17 / macOS 14 以下系统会自动跳过动画部分,因此动画能力与系统版本强相关。

五、Android/Web 字重加载

Android/Web 端由于采用字体渲染,不同字重对应不同字体文件。按 SymbolModule.types.ts 中weight的说明,Android/Web 端应通过子路径导入:

// 先导入字重字体资源 import 'expo-symbols/androidWeights/regular'; import 'expo-symbols/androidWeights/bold'; // 再渲染 <SymbolView name="star" weight={{ ios: 'bold', android: 'regular' }} tintColor="#FFD700" size={28} />

仓库中 src/android/weights/ 目录下提供了regularboldlightmediumsemiBoldthinextraLight等字重入口,对应 Material Symbols 字体的细分字重。weight也支持传对象分别指定 iOS 与 Android 的字重。

六、实用辅助 API:unstable_getMaterialSymbolSourceAsync

对于需要ImageSourcePropType而非组件的场景(例如 tab bar 图标),模块从 55.0.0 开始提供unstable_getMaterialSymbolSourceAsync(#41064)。其实现位于 src/materialImageSource.ts:

export async function unstable_getMaterialSymbolSourceAsync( symbol: AndroidSymbol | null, size: number, color: string ): Promise<ImageSourcePropType | null>

工作流程:

  1. 将 Material 符号名转换为字体字形码点(androidSymbolToString);
  2. expo-fontloadAsync确保regular字重字体已加载;
  3. 调用expo-fontrenderToImageAsync(fontChar, { fontFamily, size, color, lineHeight: size })将字形渲染成图片源。

expo-font未提供renderToImageAsync(版本过旧),会输出警告并返回null;符号为空时也返回null。调用示例:

import { unstable_getMaterialSymbolSourceAsync } from 'expo-symbols'; const icon = await unstable_getMaterialSymbolSourceAsync('home', 24, '#007AFF');

七、iOS 原生实现要点

iOS 端的原生视图位于 ios/SymbolView.swift,核心流程在reloadSymbolIOS()中:

  1. 通过UIImage(systemName: name)创建符号图片;
  2. getSymbolConfig()构建UIImage.SymbolConfiguration:按symbolType分别应用preferringMonochrome()(iOS 16+)、hierarchicalColorpaletteColors(调色板颜色数 > 1 时)或preferringMulticolor()
  3. 非 hierarchical 模式下,通过withTintColor(tint, renderingMode: .alwaysOriginal)应用着色;
  4. 最后(iOS 17+)先removeAllSymbolEffects()再按需添加动画效果。

JS 侧 src/SymbolView.ios.tsx 会做 Props 归一化:将colors统一为数组、用processColor处理颜色、计算animated布尔值、提取平台名称与字重,最后通过requireNativeView('SymbolModule')渲染原生视图。

macOS 端(#if os(macOS)分支)由于NSImage没有withTintColor,实现上改用contentTintColor进行着色,并把配置烘焙进新的NSImage(见 SymbolView.swift)。

八、版本演进与踩坑记录

以下是从 CHANGELOG.md 提炼的关键节点,能帮助你判断升级风险:

破坏性变更(Breaking changes)

  • 56.0.0(2026-05-05):最低 iOS/tvOS 版本提升到16.4,macOS 提升到13.4(#43296)。升级到 56.x 前需确认工程的最低系统版本满足要求;
  • 0.2.0(2024-10-22):iOS/tvOS 部署目标提升到15.1(#30840、#30865)。

新功能与稳定性

  • Unpublished(即将发布):Expo Symbols 从 beta 转为stable(#48537),同时修复了非原生回退(Android/Web 字体渲染)中styleprop 被忽略的问题(#48553);
  • 56.0.6:新增macOS 支持(#46471);
  • 55.0.0:Android/Web 支持 Material Symbols(#39516),并加入unstable_getMaterialSymbolSourceAsync(#41064);
  • 1.0.0(2025-08-13):修复 Android 上「native view manager isn't exported」警告(#38504);
  • 0.4.5:改用processColor,从而接受所有合法的颜色字符串(#36914);
  • 0.4.0:添加PlatformColor到类型定义(#34890);
  • 0.1.4:新增sizeprop 以对齐同类包 API(#28497)。

依赖相关

  • 56.0.5:修复@expo-google-fonts/material-symbolsuseFontsexpo-fontreact的未声明依赖问题(#45471),升级时建议同步更新该字体包;
  • 0.3.0:修复 Android 端expo-modules.config的平台配置错误(#35849),并统一了expo-module.config.json的平台语法(#34445)。

版本节奏说明

本模块多数小版本(如 57.0.1、57.0.0、56.0.4 等)明确标注「不包含面向用户的功能变更」,属于与 SDK 主版本对齐的例行发布;需要关注的核心节奏是:55.x 补齐 Android/Web、56.x 补齐 macOS 与系统版本下限、当前主线将模块转正为 stable

九、实践建议小结

  1. 跨平台应用优先使用对象形式的name映射,并为缺失平台提供fallback,避免某些平台空白;
  2. Android/Web 端记得导入字重子路径(如expo-symbols/androidWeights/bold),否则字重可能不符合预期;
  3. 需要图标作为图片源(如 tab bar)时,使用unstable_getMaterialSymbolSourceAsync,同时注意该 API 依赖较新的expo-font
  4. 动画仅限 iOS 17+ / tvOS 17+ / macOS 14+,低版本会自动降级为静态符号,设计动画前先确认目标系统版本;
  5. 升级 56.x 前核对最低系统版本(iOS/tvOS 16.4、macOS 13.4),避免 CI 或真机不满足要求;
  6. 需要查阅最新 API 细节时,可继续阅读模块内 README.md、类型定义 src/SymbolModule.types.ts 以及原生实现 ios/SymbolView.swift,并结合官方 SDK 文档确认稳定版行为。

【免费下载链接】expoAn open-source framework for making universal native apps with React. Expo runs on Android, iOS, and the web.项目地址: https://gitcode.com/GitHub_Trending/ex/expo

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询