outlined、填充和 rounded 三种变体,
线宽、圆角和视觉重量互不相同,同一屏里就能看出来不是一套。
位置
| 路径 | 职责 |
|---|---|
lib/shared/icons/raku_glyph.dart | 绘制基元与 24×24 网格规则,含一个精简的 SVG 路径解析器 |
lib/shared/icons/glyphs/ | 字形本体,按导航/操作、内容/语言、身份/场景分三个文件 |
lib/shared/icons/raku_icons.dart | 图标目录:每个字形一个 static const,外加用于遍历的 all |
lib/shared/icons/raku_icon.dart | RakuIcon 组件、RakuIconSize 与 RakuIconStyle |
lib/shared/icons/icons.dart | 对外统一导出 |
pubspec.yaml,也不参与字体子集;未被引用的字形会被
tree shaking 直接移除。
使用
RakuIcon 的行为与 Flutter 的 Icon 一致:不传参数时从 IconTheme 取尺寸和颜色。
主题在 lib/main.dart 的 appTheme 中统一设定(RakuIconSize.lg = 24,
colorScheme.onSurface),这是全应用唯一决定图标默认尺寸与颜色的地方。
尺寸只允许取 RakuIconSize 上的档位:16 / 20 / 24 / 32 / 40 / 48 / 64。不要写 18、22、
28 这类中间值——图标尺寸不成体系,正是原先视觉不齐的另一半原因。
设计系统组件(GlassButton、GlassChip、GlassDialog、GlassIconButton、
GlassSegmented)的 icon 参数接收的是 Widget 而不是 IconData,并在内部套一层
IconTheme 决定尺寸与前景色。UILibrary 因此不绑定任何一套图标词汇:
绘制规则
所有字形画在 24×24 网格上,四周留 3 单位内边距,即活动区为 3–21。规则写在RakuGlyph 的文档注释里,要点:
- 线宽、线帽和连接方式属于画笔而不是字形,由
RakuIconStyle统一决定,字形函数 无权覆盖。这保证了不会出现”某个图标比旁边粗一点”。 - 圆形容器一律
r = 8.5、圆心(12, 12);圆内的加号/叉号伸到±4,独立的伸到±7。 - 端点落在四分之一网格值上。
- 箭头用共享的
arrowHead/arcArrow基元,不要手算坐标——箭头出现在六个字形上, 手摆坐标就会摆出六种不同的箭头。 - 填充只表示”开启”状态(例如
heartFilled之于heart),不用来做风格变体。
新增字形
- 在
glyphs/下合适的文件里写void drawXxx(RakuGlyph g),只使用RakuGlyph的基元。 - 在
raku_icons.dart里加一个static const,并加入all。 - 跑几何测试与联络表(见下)。
outlined / 填充 / rounded 变体在这套集合里合并成同一个
字形;把 121 个 Material 标识符合并到 100 个字形,本身就是这次改动的一半价值。
校验
Path.getBounds()——后者是保守边界,会把贝塞尔
控制点算进去,一段 90° 圆弧的控制点在 r × √2 处,半径 8.5 的整圆会被误判成占满整个网格。
联络表是必要的一步:靠读源码判断一套线性图标是否协调是做不到的,两个字形在视觉尺寸或
线条节奏上不一致,只有并排看才看得出来。
在测试中查找图标
find.byIcon 匹配的是 IconData,只有字体图标才有。自绘图标用 test/helpers.dart 中的:
const,因此按标识比较是精确的。
同步到 Figma
设计稿里的图标不是画出来的,是从这里编译过去的。tool/glyphs_to_svg.py 把
glyphs/ 里的字形函数直接转成 SVG 路径数据,再由 Figma 侧建成 99 个 24×24 组件
(Components 页 → Icons 分区),命名为 Icon / <名字>:
glyphs/*.dart 之后重跑一次并更新 Figma 组件,两边就不会漂移。设计稿里需要图标时
一律用组件实例,不要手绘矢量,也不要从截图上描——在建立这条管线之前,每个画板都各画各的,
同一个图标在不同页面上都不一样,而且没有一个和应用里的一致。
组件里的描边与填充矢量都带 constraints: SCALE,所以实例缩放到 20 或 28 时线宽会跟着缩放,
与 RakuIcon 按 size.shortestSide / RakuGlyph.grid 缩放的行为一致。
转译器主要在处理 Figma 侧的三个限制:vectorPaths 只接受绝对坐标的 M/L/C/Z
(H、V、S、Q、T 和相对坐标都会报 Invalid command,因此二次贝塞尔要升成三次);
Dart 里共享的路径常量与相邻字符串字面量要先解析拼接;以及 Figma 会丢弃零长度路径,所以圆点
必须是真正的填充圆。完整说明见 .cursor/skills/figma-sync/mapping.md。