# Sketch 测量五大陷阱 (sketch-pitfalls)

> 基于真实项目多轮修复经验总结的高频还原错误
> 版本: v1.0.0 | 更新: 2026-02-11

---

## 📋 概述

本规范记录了从 Sketch 测量数据还原 Flutter/前端代码时最常见的 5 类错误。每一类都曾在真实项目中导致 2-5 轮无效对话才最终修正。

**适用场景**：所有基于 Sketch 测量数据的 UI 还原工作。  
**配套工具**：测量插件 v4.6.0（sketch-tools.js），已内置自动检测能力。  
**推荐调用**：`mcp_mta_mta({ skill: "sketch_measure", params: { cmd: "measure" } })`

---

## 🔴 陷阱 1：Icon Group 尺寸 ≠ 视觉尺寸

### 问题描述

Sketch 中 Icon 通常有两层结构：外层 Group 定义布局占位，内层 ShapePath 才是实际可见图形。直接用 Group 的 frame 尺寸渲染 SVG，Icon 会偏大 2-4px。

### 典型结构

```
Group "Fee Icon" (16×16)        ← containerSize，含 padding
└─ ShapePath "path" (12×12)     ← contentSize，实际图形
```

### 错误做法

```dart
// 直接用 Group 尺寸
SvgPicture.asset('fee.svg', width: 16, height: 16)
// 结果：Icon 看起来比设计稿大
```

### 正确做法

```dart
// 分离占位与渲染
SizedBox(
  width: 16, height: 16,  // containerSize - 占位
  child: Center(
    child: SvgPicture.asset('fee.svg',
      width: 12, height: 12,  // contentSize - 渲染
      colorFilter: ColorFilter.mode($c.iconPrimary, BlendMode.srcIn),
    ),
  ),
)
```

### 检测方式

测量插件 v4.0 自动输出 `iconContentBounds`：
```json
{
  "containerSize": { "width": 16, "height": 16 },
  "contentSize": { "width": 12, "height": 12 }
}
```

### 验证要点

- `containerSize` ≠ `contentSize` 时必须分离处理
- 如果两者相等，说明 Icon 没有内部 padding，可直接用尺寸渲染

---

## 🔴 陷阱 2：同行 Icon 不等大导致文字错位

### 问题描述

同一区域内多个 Icon+文字组合（如手续费行、优惠券行），各 Icon 实际尺寸不同。如果各用各的宽度，后面的文字起始位置不一致，视觉上文字不对齐。

### 典型场景

```
手续费行: [Icon 14×14] [Gap 4] [手续费 ¥3.00]  → 文字从 x=18 开始
优惠券行: [Icon 12×12] [Gap 4] [优惠券 -¥1.00]  → 文字从 x=16 开始
                                                     ↑ 差 2px！
```

### 错误做法

```dart
// 各用各的尺寸
Row(children: [
  SvgPicture.asset('fee.svg', width: 14, height: 14),
  Gap(4),
  Text('手续费'),
])
Row(children: [
  SvgPicture.asset('coupon.svg', width: 12, height: 12),
  Gap(4),
  Text('优惠券'),  // 文字起始位置不同！
])
```

### 正确做法

```dart
// 统一 16×16 占位
Row(children: [
  SizedBox(width: 16, height: 16, child: Center(
    child: SvgPicture.asset('fee.svg', width: 14, height: 14),
  )),
  Gap(4),
  Text('手续费'),  // 文字从 x=20 开始
])
Row(children: [
  SizedBox(width: 16, height: 16, child: Center(
    child: SvgPicture.asset('coupon.svg', width: 12, height: 12),
  )),
  Gap(4),
  Text('优惠券'),  // 文字从 x=20 开始，对齐！
])
```

### 检测方式

测量插件 v4.0 自动输出 `siblingIconAlignment`：
```json
{
  "detected": true,
  "maxSlotSize": { "width": 16, "height": 16 },
  "icons": [
    { "name": "Fee Icon", "containerSize": [16,16], "contentSize": [14,14] },
    { "name": "Coupon Icon", "containerSize": [12,12], "contentSize": [12,12] }
  ]
}
```

### 验证要点

- 所有同行 Icon 的 SizedBox 宽度应相同（= maxSlotSize）
- 每个 Icon 内部用 Center 居中
- 检查多行文字起始位置是否对齐

---

## 🔴 陷阱 3：Tag/标签容器样式猜测

### 问题描述

小型 Tag（如"支付宝"、"银联"等标签）的 background、padding、cornerRadius 未从 Sketch 测量数据中提取，而是凭感觉猜测。通常需要 2-3 轮修正才能匹配设计稿。

### 典型错误链

```
第1次还原: padding(h:6,v:2), bg: 0xFF1677FF, radius: 4      ← 全猜错
用户反馈 → 第2次: padding(h:4,v:1), bg: 0x991676FE           ← 接近但仍偏差
再次反馈 → 第3次: padding(h:3,v:1), bg: 0x991676FE, radius: 3 ← 正确
```

### 错误做法

```dart
Container(
  padding: EdgeInsets.symmetric(horizontal: 6, vertical: 2),  // 猜的
  decoration: BoxDecoration(
    color: Color(0xFF1677FF),  // 丢失透明度
    borderRadius: BorderRadius.circular(4),  // 猜的
  ),
)
```

### 正确做法

```dart
// 直接使用测量数据中的 tagStyle
Container(
  padding: EdgeInsets.symmetric(horizontal: 3, vertical: 1),
  decoration: BoxDecoration(
    color: Color(0x991676FE),  // 包含透明度
    borderRadius: BorderRadius.circular(3),
  ),
)
```

### 检测方式

测量插件 v4.0 自动输出 `tagStyle`：
```json
{
  "background": "#1676fe99",
  "padding": { "horizontal": 3, "vertical": 1 },
  "cornerRadius": 3,
  "textContent": "支付宝"
}
```

### 注意事项

- Tag 的 background 常常带透明度（如 60%），转 Flutter 时保留 alpha 通道
- cornerRadius 通常较小（2-4px），不要使用大圆角
- padding 通常很紧凑（h:2-4, v:1-2），不要用常规间距值

---

## 🔴 陷阱 4：字体权重超出平台可用范围

### 问题描述

Sketch 使用 fontWeight 数值（如 8=Heavy），直接映射为 Flutter FontWeight.w800。但 PingFang SC 字体没有 Heavy 字重文件，Flutter 会使用**合成加粗**（synthetic bold），视觉效果显著偏粗。

### 平台字体可用范围

| 字体 | 支持的最大真实字重 | 超出后果 |
|------|-------------------|---------|
| **PingFang SC** | Semibold (w600) | w700+ 触发合成加粗，字体明显偏粗 |
| **Helvetica / Helvetica Neue** | Bold (w700) | w800+ 触发合成加粗 |
| **SF Pro** | Black (w900) | 全范围可用 |
| **Roboto** | Black (w900) | 全范围可用 |

### 错误做法

```dart
// Sketch fontWeight=8 → 直接用 w800
TextStyle(
  fontFamily: 'PingFang SC',
  fontWeight: FontWeight.w800,  // 不存在的字重！触发合成加粗
)
```

### 正确做法

```dart
// 用 PostScript 名映射: PingFangSC-Semibold → w600
TextStyle(
  fontFamily: 'PingFang SC',
  fontWeight: FontWeight.w600,  // 最大真实字重
)
```

### 检测方式

测量插件输出 `fontName`（PostScript 名），使用后缀映射：

| PostScript 后缀 | FontWeight | 说明 |
|----------------|-----------|------|
| `-Regular` | w400 | |
| `-Medium` | w500 | |
| `-Semibold` | w600 | PingFang SC 最大真实字重 |
| `-Bold` | w700 | Helvetica 最大真实字重 |
| `-Heavy` | w600（降级） | PingFang SC 无此字重 |
| `-Black` | w600（降级） | PingFang SC 无此字重 |

### 验证要点

- Sketch 的 `style.fontWeight` 数值**不可靠**，始终用 `fontName` 后缀
- 超出平台可用范围时，降级到对应字体的最大真实字重
- 如果设计稿文字看起来很粗但 PostScript 名为 Semibold，用 w600 即可

---

## 🔴 陷阱 5：添加设计稿中不存在的元素

### 问题描述

AI 根据语义推测（如"汇率"标签可能需要交换图标），自行添加了设计稿中不存在的 UI 元素。用户需要额外沟通才能发现并删除这些"幽灵元素"。

### 真实案例

| 场景 | AI 添加了什么 | 设计稿实际 |
|------|-------------|-----------|
| "汇率" 标签 | `Icons.swap_horiz` 图标 | 无任何图标 |
| "通知" 标签 | `Icons.notifications` 图标 | 仅一个 8×10 的自定义 SVG |
| 空状态页 | "暂无数据" 文字 | 设计稿有专门的空状态插画 |

### 强制规则

1. **只还原测量数据中明确存在的元素** — 如果 `measureRecursively` 输出中没有某元素，则不添加
2. **不根据语义推测添加 Icon** — 即使标签名暗示需要图标（如"设置"、"搜索"），也不自行添加
3. **不确定时询问用户** — 如果怀疑某处应该有元素但测量数据中没有，向用户确认
4. **Material Icons 不是默认选项** — 只有测量数据明确标记了系统图标时才使用

### 验证要点

- 还原完成后，逐元素与测量数据对比
- 代码中每个可视元素都应能在测量数据中找到对应的来源
- 如果代码中存在测量数据中没有的元素，必须删除或向用户确认

---

## 📋 还原前检查清单

在开始任何 Sketch → 代码的还原工作前，逐项检查：

- [ ] **Icon 尺寸**: 检查 `iconContentBounds`，使用 `contentSize` 渲染而非 `containerSize`
- [ ] **Icon 对齐**: 检查 `siblingIconAlignment`，同行 Icon 统一 `maxSlotSize` 占位
- [ ] **Tag 样式**: 检查 `tagStyle`，直接使用测量的 padding/bg/cornerRadius
- [ ] **字体权重**: 检查 `fontName` 后缀，确认不超出平台可用范围
- [ ] **幽灵元素**: 确认代码中每个元素都有测量数据来源

---

## 📚 相关规范

| 规范 | 调用方式 | 说明 |
|------|----------|------|
| 设计稿还原流程 | `get_standard_by_id({ id: 'design-restoration' })` | 通用还原规范 |
| Sketch MCP 用法 | `get_standard_by_id({ id: 'sketch-mcp' })` | 深度测量脚本 |
| Flutter 框架规范 | `get_standard_by_id({ id: 'flutter' })` | Token 系统 |

---

**维护者**: MTA工作室  
**创建日期**: 2026-02-11  
**数据来源**: my_flutter 项目"去汇款"页面 7 轮修复记录
