mta-mcp 2.13.0 → 2.14.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/agents/flutter.agent.md +117 -1147
- package/agents/vue3.agent.md +177 -464
- package/dist/index.d.ts +63 -0
- package/dist/index.js +551 -132
- package/dist/index.js.map +1 -1
- package/package.json +2 -1
- package/troubleshooting/README.md +366 -0
- package/troubleshooting/USAGE_GUIDE.md +289 -0
- package/troubleshooting/flutter/clip-/351/230/264/345/275/261/350/243/201/345/211/252.md +244 -0
- package/troubleshooting/flutter/input-/345/255/227/346/256/265/347/274/272/345/244/261.md +240 -0
- package/troubleshooting/flutter/input-/350/276/271/346/241/206/351/227/256/351/242/230.md +236 -0
- package/troubleshooting/flutter/layout-/345/260/272/345/257/270/344/270/215/345/214/271/351/205/215.md +214 -0
- package/troubleshooting/flutter/shadow-/351/200/217/345/207/272/351/227/256/351/242/230.md +172 -0
- package/troubleshooting/flutter/sketch-/345/233/276/346/240/207/345/260/272/345/257/270.md +135 -0
- package/troubleshooting/flutter/sketch-/345/256/214/346/225/264/346/217/220/345/217/226.md +201 -0
- package/troubleshooting/flutter/sketch-/345/261/236/346/200/247/346/234/252/344/275/277/347/224/250.md +139 -0
- package/troubleshooting/flutter/svg-/346/234/252/345/261/205/344/270/255.md +120 -0
- package/troubleshooting/flutter/svg-/351/242/234/350/211/262/345/274/202/345/270/270.md +117 -0
- package/troubleshooting/flutter/tabbar-/345/212/250/347/224/273/345/220/214/346/255/245.md +107 -0
- package/troubleshooting/flutter/withopacity-/345/274/203/347/224/250.md +81 -0
- package/troubleshooting/vue3/cascader-/350/257/257/346/233/277/346/215/242.md +130 -0
- package/troubleshooting/vue3/drawer-input-/346/240/267/345/274/217.md +181 -0
- package/troubleshooting/vue3/table-/347/274/226/350/276/221/345/217/226/346/266/210.md +148 -0
- package/troubleshooting/vue3/table-/350/276/271/346/241/206/351/227/256/351/242/230.md +178 -0
|
@@ -0,0 +1,201 @@
|
|
|
1
|
+
# Sketch 设计稿样式不完整提取问题
|
|
2
|
+
|
|
3
|
+
> **问题标签**: `sketch`, `design`, `extraction`, `incomplete`, `一次性提取`
|
|
4
|
+
> **框架**: Flutter
|
|
5
|
+
> **严重程度**: 高(导致多轮对话和返工)
|
|
6
|
+
|
|
7
|
+
---
|
|
8
|
+
|
|
9
|
+
## 🔍 问题识别
|
|
10
|
+
|
|
11
|
+
### 用户描述关键词
|
|
12
|
+
- "漏了xxx属性"
|
|
13
|
+
- "渐变怎么还原"
|
|
14
|
+
- "阴影参数是多少"
|
|
15
|
+
- "这个图标是圆形的吗"
|
|
16
|
+
- "设计稿上的圆角多大"
|
|
17
|
+
|
|
18
|
+
### 问题特征
|
|
19
|
+
- [ ] AI 每次只查询单个属性(颜色、尺寸、阴影等)
|
|
20
|
+
- [ ] 需要多轮对话才能获取完整样式信息
|
|
21
|
+
- [ ] 使用近似值代替精确值(如 Material Icons 代替 SVG)
|
|
22
|
+
- [ ] 假设属性而非验证(假设是圆形、假设颜色相同等)
|
|
23
|
+
|
|
24
|
+
---
|
|
25
|
+
|
|
26
|
+
## 🎯 核心原理
|
|
27
|
+
|
|
28
|
+
**根本原因**:分散查询导致信息不完整,浪费时间。
|
|
29
|
+
|
|
30
|
+
| 问题 | 表现 | 根因 |
|
|
31
|
+
|------|------|------|
|
|
32
|
+
| 属性读取不完整 | 漏读渐变、圆角、阴影参数 | 只读取部分属性 |
|
|
33
|
+
| 假设而非验证 | 假设圆形/颜色/图标 | 未从设计稿验证 |
|
|
34
|
+
| 使用近似值 | 用 Material Icons 代替 | 未导出原始 SVG |
|
|
35
|
+
| 分散查询 | 多轮对话才获取完整信息 | 每次只查一个属性 |
|
|
36
|
+
|
|
37
|
+
---
|
|
38
|
+
|
|
39
|
+
## ✅ 正确解决方案
|
|
40
|
+
|
|
41
|
+
### 强制执行:一次性完整提取脚本
|
|
42
|
+
|
|
43
|
+
**在还原任何 UI 元素前,必须使用以下脚本一次性提取所有属性:**
|
|
44
|
+
|
|
45
|
+
```javascript
|
|
46
|
+
// Sketch 完整样式提取脚本 - 必须使用
|
|
47
|
+
const sketch = require('sketch');
|
|
48
|
+
const document = sketch.getSelectedDocument();
|
|
49
|
+
const page = document.selectedPage;
|
|
50
|
+
|
|
51
|
+
function extractFullStyle(layerName) {
|
|
52
|
+
const layer = sketch.find(`[name="${layerName}"]`, page)[0];
|
|
53
|
+
if (!layer) return console.log(`Layer "${layerName}" not found`);
|
|
54
|
+
|
|
55
|
+
console.log('=== 基本信息 ===');
|
|
56
|
+
console.log(`Name: ${layer.name} (${layer.type})`);
|
|
57
|
+
console.log(`Frame: ${layer.frame.width}x${layer.frame.height} @ (${layer.frame.x}, ${layer.frame.y})`);
|
|
58
|
+
|
|
59
|
+
const style = layer.style;
|
|
60
|
+
|
|
61
|
+
// 1. 填充(颜色/渐变)
|
|
62
|
+
console.log('=== 填充 ===');
|
|
63
|
+
if (style.fills?.length) {
|
|
64
|
+
style.fills.forEach((fill, i) => {
|
|
65
|
+
console.log(`Fill ${i}: Type=${fill.fillType}, Enabled=${fill.enabled}`);
|
|
66
|
+
if (fill.fillType === 'Color') {
|
|
67
|
+
console.log(` Color: ${fill.color}`);
|
|
68
|
+
} else if (fill.fillType === 'Gradient') {
|
|
69
|
+
console.log(` Gradient: ${fill.gradient.gradientType}`);
|
|
70
|
+
console.log(` From: (${fill.gradient.from.x.toFixed(2)}, ${fill.gradient.from.y.toFixed(2)})`);
|
|
71
|
+
console.log(` To: (${fill.gradient.to.x.toFixed(2)}, ${fill.gradient.to.y.toFixed(2)})`);
|
|
72
|
+
fill.gradient.stops.forEach((stop, j) => {
|
|
73
|
+
console.log(` Stop ${j}: ${stop.color} @ ${stop.position}`);
|
|
74
|
+
});
|
|
75
|
+
}
|
|
76
|
+
});
|
|
77
|
+
} else {
|
|
78
|
+
console.log('No fills');
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
// 2. 边框
|
|
82
|
+
console.log('=== 边框 ===');
|
|
83
|
+
if (style.borders?.length) {
|
|
84
|
+
style.borders.forEach((border, i) => {
|
|
85
|
+
console.log(`Border ${i}: Color=${border.color}, Width=${border.thickness}, Position=${border.position}, Enabled=${border.enabled}`);
|
|
86
|
+
});
|
|
87
|
+
} else {
|
|
88
|
+
console.log('No borders');
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
// 3. 阴影
|
|
92
|
+
console.log('=== 阴影 ===');
|
|
93
|
+
if (style.shadows?.length) {
|
|
94
|
+
style.shadows.forEach((s, i) => {
|
|
95
|
+
console.log(`Shadow ${i}: Color=${s.color}, Offset=(${s.x}, ${s.y}), Blur=${s.blur}, Spread=${s.spread}`);
|
|
96
|
+
});
|
|
97
|
+
} else {
|
|
98
|
+
console.log('No shadows');
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
// 4. 内阴影
|
|
102
|
+
console.log('=== 内阴影 ===');
|
|
103
|
+
if (style.innerShadows?.length) {
|
|
104
|
+
style.innerShadows.forEach((s, i) => {
|
|
105
|
+
console.log(`InnerShadow ${i}: Color=${s.color}, Offset=(${s.x}, ${s.y}), Blur=${s.blur}, Spread=${s.spread}`);
|
|
106
|
+
});
|
|
107
|
+
} else {
|
|
108
|
+
console.log('No inner shadows');
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
// 5. 圆角
|
|
112
|
+
console.log('=== 圆角 ===');
|
|
113
|
+
if (layer.layers?.[0]?.points) {
|
|
114
|
+
const points = layer.layers[0].points;
|
|
115
|
+
const radii = points.map(p => p.cornerRadius);
|
|
116
|
+
const allSame = radii.every(r => r === radii[0]);
|
|
117
|
+
if (allSame) {
|
|
118
|
+
console.log(`CornerRadius: ${radii[0]} (all corners)`);
|
|
119
|
+
} else {
|
|
120
|
+
console.log(`CornerRadius: [${radii.join(', ')}] (TL, TR, BR, BL)`);
|
|
121
|
+
}
|
|
122
|
+
} else if (layer.points) {
|
|
123
|
+
const radii = layer.points.map(p => p.cornerRadius);
|
|
124
|
+
console.log(`CornerRadius: ${radii[0]}`);
|
|
125
|
+
} else {
|
|
126
|
+
console.log('No corner radius (circle or custom shape)');
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
// 6. 不透明度
|
|
130
|
+
console.log('=== 不透明度 ===');
|
|
131
|
+
console.log(`Opacity: ${style.opacity}`);
|
|
132
|
+
|
|
133
|
+
// 7. 子元素(如果是 Group)
|
|
134
|
+
if (layer.layers?.length) {
|
|
135
|
+
console.log('=== 子元素 ===');
|
|
136
|
+
layer.layers.forEach(child => {
|
|
137
|
+
console.log(`- ${child.name} (${child.type}): ${child.frame.width}x${child.frame.height}`);
|
|
138
|
+
});
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
// 使用示例
|
|
143
|
+
extractFullStyle('Button Background');
|
|
144
|
+
extractFullStyle('Icon');
|
|
145
|
+
```
|
|
146
|
+
|
|
147
|
+
### 图标导出:必须使用 SVG
|
|
148
|
+
|
|
149
|
+
**禁止使用 Material Icons 或其他近似图标:**
|
|
150
|
+
|
|
151
|
+
```javascript
|
|
152
|
+
// 导出图标 SVG
|
|
153
|
+
const sketch = require('sketch');
|
|
154
|
+
const layer = sketch.find('[name="Icon Name"]', sketch.getSelectedDocument().selectedPage)[0];
|
|
155
|
+
if (layer) {
|
|
156
|
+
const svg = sketch.export(layer, { formats: 'svg', output: false });
|
|
157
|
+
console.log(svg.toString());
|
|
158
|
+
}
|
|
159
|
+
```
|
|
160
|
+
|
|
161
|
+
### 还原检查清单(强制)
|
|
162
|
+
|
|
163
|
+
在还原任何 UI 元素前,必须确认以下所有属性:
|
|
164
|
+
|
|
165
|
+
| 属性 | 检查项 | Flutter 对应 |
|
|
166
|
+
|------|--------|--------------|
|
|
167
|
+
| 尺寸 | width, height | `SizedBox(width:, height:)` |
|
|
168
|
+
| 位置 | x, y | `Positioned(left:, top:)` |
|
|
169
|
+
| 颜色/渐变 | fills[0] | `BoxDecoration(color:, gradient:)` |
|
|
170
|
+
| 圆角 | cornerRadius | `BorderRadius.circular()` |
|
|
171
|
+
| 边框 | borders | `Border.all()` |
|
|
172
|
+
| 阴影 | shadows | `BoxShadow()` |
|
|
173
|
+
| 透明度 | opacity | `Opacity(opacity:)` |
|
|
174
|
+
|
|
175
|
+
---
|
|
176
|
+
|
|
177
|
+
## ❌ 常见错误排查路线(避免重复)
|
|
178
|
+
|
|
179
|
+
| 尝试方向 | 为什么无效 | 浪费时间 |
|
|
180
|
+
|----------|-----------|---------|
|
|
181
|
+
| 每次只查一个属性 | 需要多轮对话才能获取完整信息 | 5-10 轮 |
|
|
182
|
+
| 假设是圆形 | 未验证 cornerRadius,可能是圆角矩形 | 2-3 轮 |
|
|
183
|
+
| 使用 Material Icons | 与设计稿不完全一致 | 3-5 轮 |
|
|
184
|
+
| 忽略渐变参数 | 只看到 from/to,未读取 stops | 2-4 轮 |
|
|
185
|
+
|
|
186
|
+
---
|
|
187
|
+
|
|
188
|
+
## 📁 适用场景
|
|
189
|
+
|
|
190
|
+
- ✅ 从 Sketch 设计稿还原任何 UI 元素
|
|
191
|
+
- ✅ 需要获取完整样式信息
|
|
192
|
+
- ✅ 包含渐变、阴影、复杂圆角的设计
|
|
193
|
+
- ✅ 图标还原(必须导出 SVG)
|
|
194
|
+
|
|
195
|
+
---
|
|
196
|
+
|
|
197
|
+
## 🔗 相关案例
|
|
198
|
+
|
|
199
|
+
- [sketch-图标尺寸.md](./sketch-图标尺寸.md) - 图标尺寸提取错误
|
|
200
|
+
- [svg-颜色异常.md](./svg-颜色异常.md) - SVG 颜色处理问题
|
|
201
|
+
- [svg-未居中.md](./svg-未居中.md) - SVG 居中问题
|
|
@@ -0,0 +1,139 @@
|
|
|
1
|
+
# 组件属性定义但未使用问题
|
|
2
|
+
|
|
3
|
+
> **问题标签**: `property`, `unused`, `debug`, `widget`
|
|
4
|
+
> **框架**: Flutter
|
|
5
|
+
> **严重程度**: 高(功能无效)
|
|
6
|
+
|
|
7
|
+
---
|
|
8
|
+
|
|
9
|
+
## 🔍 问题识别
|
|
10
|
+
|
|
11
|
+
### 用户描述关键词
|
|
12
|
+
- "设置了属性但没效果"
|
|
13
|
+
- "参数不生效"
|
|
14
|
+
- "改了值但 UI 没变化"
|
|
15
|
+
- "属性没有被使用"
|
|
16
|
+
|
|
17
|
+
### 问题特征
|
|
18
|
+
- [ ] 在组件类中定义了新属性
|
|
19
|
+
- [ ] 修改属性值后 UI 没有变化
|
|
20
|
+
- [ ] 没有编译错误
|
|
21
|
+
- [ ] 运行正常但功能不符合预期
|
|
22
|
+
|
|
23
|
+
---
|
|
24
|
+
|
|
25
|
+
## 🎯 核心原理
|
|
26
|
+
|
|
27
|
+
**根本原因**:在类中定义了属性,但在 `build` 方法中没有实际使用该属性。
|
|
28
|
+
|
|
29
|
+
```dart
|
|
30
|
+
// ❌ 错误示例:定义了 iconSize 但没使用
|
|
31
|
+
class MyIcon extends StatelessWidget {
|
|
32
|
+
const MyIcon({
|
|
33
|
+
super.key,
|
|
34
|
+
this.iconSize = 24.0, // 定义了属性
|
|
35
|
+
});
|
|
36
|
+
|
|
37
|
+
final double iconSize; // 有这个字段
|
|
38
|
+
|
|
39
|
+
@override
|
|
40
|
+
Widget build(BuildContext context) {
|
|
41
|
+
return Icon(
|
|
42
|
+
Icons.star,
|
|
43
|
+
size: 24.0, // ← 直接写死了!没用 iconSize
|
|
44
|
+
);
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
---
|
|
50
|
+
|
|
51
|
+
## ✅ 正确解决方案
|
|
52
|
+
|
|
53
|
+
### 1. 验证属性被使用
|
|
54
|
+
|
|
55
|
+
```bash
|
|
56
|
+
# 搜索属性在文件中的使用情况
|
|
57
|
+
grep -n "iconSize" lib/widgets/my_icon.dart
|
|
58
|
+
|
|
59
|
+
# 期望输出:
|
|
60
|
+
# 5: this.iconSize = 24.0, ← 定义
|
|
61
|
+
# 8: final double iconSize; ← 字段
|
|
62
|
+
# 14: size: iconSize, ← 使用 ✅
|
|
63
|
+
```
|
|
64
|
+
|
|
65
|
+
### 2. 正确使用属性
|
|
66
|
+
|
|
67
|
+
```dart
|
|
68
|
+
// ✅ 正确示例
|
|
69
|
+
class MyIcon extends StatelessWidget {
|
|
70
|
+
const MyIcon({
|
|
71
|
+
super.key,
|
|
72
|
+
this.iconSize = 24.0,
|
|
73
|
+
});
|
|
74
|
+
|
|
75
|
+
final double iconSize;
|
|
76
|
+
|
|
77
|
+
@override
|
|
78
|
+
Widget build(BuildContext context) {
|
|
79
|
+
return Icon(
|
|
80
|
+
Icons.star,
|
|
81
|
+
size: iconSize, // ← 使用属性
|
|
82
|
+
);
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
```
|
|
86
|
+
|
|
87
|
+
### 3. 添加属性后的验证清单
|
|
88
|
+
|
|
89
|
+
每次添加新属性后必须检查:
|
|
90
|
+
|
|
91
|
+
- [ ] 属性在 constructor 中定义
|
|
92
|
+
- [ ] 属性有对应的 final 字段
|
|
93
|
+
- [ ] 属性在 build 方法中被引用
|
|
94
|
+
- [ ] 修改属性值后 UI 确实变化
|
|
95
|
+
|
|
96
|
+
---
|
|
97
|
+
|
|
98
|
+
## 📝 快速调试方法
|
|
99
|
+
|
|
100
|
+
```dart
|
|
101
|
+
// 临时添加打印语句验证
|
|
102
|
+
@override
|
|
103
|
+
Widget build(BuildContext context) {
|
|
104
|
+
print('🔍 iconSize: $iconSize'); // 确认值正确
|
|
105
|
+
return Icon(
|
|
106
|
+
Icons.star,
|
|
107
|
+
size: iconSize,
|
|
108
|
+
);
|
|
109
|
+
}
|
|
110
|
+
```
|
|
111
|
+
|
|
112
|
+
```dart
|
|
113
|
+
// 或使用断言
|
|
114
|
+
@override
|
|
115
|
+
Widget build(BuildContext context) {
|
|
116
|
+
assert(iconSize > 0, 'iconSize must be positive');
|
|
117
|
+
// ...
|
|
118
|
+
}
|
|
119
|
+
```
|
|
120
|
+
|
|
121
|
+
---
|
|
122
|
+
|
|
123
|
+
## ❌ 常见错误排查路线(避免重复)
|
|
124
|
+
|
|
125
|
+
| 尝试方向 | 为什么无效 | 浪费时间 |
|
|
126
|
+
|----------|-----------|---------|
|
|
127
|
+
| 调整属性默认值 | 如果没使用,改默认值也没用 | 1-2 轮对话 |
|
|
128
|
+
| 添加更多属性 | 同样的问题会重复 | 2-3 轮对话 |
|
|
129
|
+
| 检查父组件传值 | 如果子组件没用,传什么都一样 | 1-2 轮对话 |
|
|
130
|
+
|
|
131
|
+
**最佳做法**:先 grep 确认属性被使用,再调试其他问题
|
|
132
|
+
|
|
133
|
+
---
|
|
134
|
+
|
|
135
|
+
## 📁 适用场景
|
|
136
|
+
|
|
137
|
+
- ✅ 自定义 Widget 开发
|
|
138
|
+
- ✅ 组件可配置化
|
|
139
|
+
- ✅ 任何涉及属性传递的场景
|
|
@@ -0,0 +1,120 @@
|
|
|
1
|
+
# Flutter SVG 未居中问题
|
|
2
|
+
|
|
3
|
+
> **问题标签**: `svg`, `viewbox`, `center`, `alignment`, `icon`
|
|
4
|
+
> **框架**: Flutter
|
|
5
|
+
> **严重程度**: 低(布局问题)
|
|
6
|
+
|
|
7
|
+
---
|
|
8
|
+
|
|
9
|
+
## 🔍 问题识别
|
|
10
|
+
|
|
11
|
+
### 用户描述关键词
|
|
12
|
+
- "SVG 图标没有居中"
|
|
13
|
+
- "图标位置偏了"
|
|
14
|
+
- "需要用 Center 包裹"
|
|
15
|
+
- "viewBox 不对"
|
|
16
|
+
|
|
17
|
+
### 问题特征
|
|
18
|
+
- [ ] SVG 显示位置与预期不符
|
|
19
|
+
- [ ] 需要额外的 Center/Align 组件包裹
|
|
20
|
+
- [ ] SVG viewBox 尺寸与使用尺寸不匹配
|
|
21
|
+
|
|
22
|
+
---
|
|
23
|
+
|
|
24
|
+
## 🎯 核心原理
|
|
25
|
+
|
|
26
|
+
**根本原因**:Sketch 导出 SVG 时,viewBox 可能是图形的最小边界框,而非设计稿中的容器尺寸。
|
|
27
|
+
|
|
28
|
+
```xml
|
|
29
|
+
<!-- ❌ 问题:viewBox 是最小边界框 -->
|
|
30
|
+
<!-- 箭头图标实际尺寸是 6x3,但需要放在 12x12 的容器中 -->
|
|
31
|
+
<svg viewBox="0 0 6 3">
|
|
32
|
+
<path d="..."/>
|
|
33
|
+
</svg>
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
```dart
|
|
37
|
+
// 放在 12x12 容器中,图标会被拉伸或需要额外居中处理
|
|
38
|
+
SvgPicture.asset('arrow.svg', width: 12, height: 12);
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
---
|
|
42
|
+
|
|
43
|
+
## ✅ 正确解决方案
|
|
44
|
+
|
|
45
|
+
### 方案1:导出时保留完整 viewBox(推荐)
|
|
46
|
+
|
|
47
|
+
在 Sketch 中导出 SVG 时,选择图标所在的外层容器(Group),而非内部的形状:
|
|
48
|
+
|
|
49
|
+
```javascript
|
|
50
|
+
// Sketch 脚本:导出完整容器的 SVG
|
|
51
|
+
const sketch = require('sketch');
|
|
52
|
+
const layer = sketch.find('[name="Icon Container"]')[0]; // 选择外层 Group
|
|
53
|
+
const svg = sketch.export(layer, { formats: 'svg', output: false });
|
|
54
|
+
```
|
|
55
|
+
|
|
56
|
+
```xml
|
|
57
|
+
<!-- ✅ 正确:viewBox 包含完整容器空间 -->
|
|
58
|
+
<svg viewBox="0 0 12 12">
|
|
59
|
+
<!-- 箭头在容器中的正确位置 -->
|
|
60
|
+
<path transform="translate(3, 4.5)" d="..."/>
|
|
61
|
+
</svg>
|
|
62
|
+
```
|
|
63
|
+
|
|
64
|
+
### 方案2:Flutter 中手动设置对齐
|
|
65
|
+
|
|
66
|
+
```dart
|
|
67
|
+
// 如果 SVG 无法重新导出,在 Flutter 中处理
|
|
68
|
+
SizedBox(
|
|
69
|
+
width: 12,
|
|
70
|
+
height: 12,
|
|
71
|
+
child: Center(
|
|
72
|
+
child: SvgPicture.asset(
|
|
73
|
+
'arrow.svg',
|
|
74
|
+
width: 6, // 使用 SVG 实际尺寸
|
|
75
|
+
height: 3,
|
|
76
|
+
),
|
|
77
|
+
),
|
|
78
|
+
)
|
|
79
|
+
```
|
|
80
|
+
|
|
81
|
+
---
|
|
82
|
+
|
|
83
|
+
## 📝 Sketch 图标导出检查清单
|
|
84
|
+
|
|
85
|
+
1. **确认选择正确的图层**
|
|
86
|
+
- 选择外层 Group/Frame,而非内部 Shape
|
|
87
|
+
- 外层应该是设计稿中图标的"点击区域"尺寸
|
|
88
|
+
|
|
89
|
+
2. **验证导出的 viewBox**
|
|
90
|
+
```bash
|
|
91
|
+
# 检查 SVG 文件的 viewBox
|
|
92
|
+
head -3 assets/icons/arrow.svg
|
|
93
|
+
# 应该看到 viewBox="0 0 12 12" 而非 viewBox="0 0 6 3"
|
|
94
|
+
```
|
|
95
|
+
|
|
96
|
+
3. **检查 Flutter 中的使用**
|
|
97
|
+
```dart
|
|
98
|
+
// 宽高应该与 viewBox 匹配
|
|
99
|
+
SvgPicture.asset('arrow.svg', width: 12, height: 12);
|
|
100
|
+
```
|
|
101
|
+
|
|
102
|
+
---
|
|
103
|
+
|
|
104
|
+
## ❌ 常见错误排查路线(避免重复)
|
|
105
|
+
|
|
106
|
+
| 尝试方向 | 为什么无效 | 浪费时间 |
|
|
107
|
+
|----------|-----------|---------|
|
|
108
|
+
| 用 Center 包裹 | 治标不治本,每个图标都要处理 | 1 轮对话/图标 |
|
|
109
|
+
| 调整 alignment 属性 | SvgPicture 的 alignment 可能不生效 | 1-2 轮对话 |
|
|
110
|
+
| 修改 SVG 的 transform | 需要计算精确偏移量 | 2-3 轮对话 |
|
|
111
|
+
|
|
112
|
+
**最佳做法**:从源头修复,重新导出正确 viewBox 的 SVG
|
|
113
|
+
|
|
114
|
+
---
|
|
115
|
+
|
|
116
|
+
## 📁 适用场景
|
|
117
|
+
|
|
118
|
+
- ✅ 小尺寸图标(箭头、点击指示器等)
|
|
119
|
+
- ✅ 需要放在固定容器中的图标
|
|
120
|
+
- ✅ TabBar、按钮中的图标
|
|
@@ -0,0 +1,117 @@
|
|
|
1
|
+
# Flutter SVG 颜色异常问题
|
|
2
|
+
|
|
3
|
+
> **问题标签**: `svg`, `color`, `colorfilter`, `transparency`, `icon`
|
|
4
|
+
> **框架**: Flutter
|
|
5
|
+
> **严重程度**: 中等(视觉问题)
|
|
6
|
+
|
|
7
|
+
---
|
|
8
|
+
|
|
9
|
+
## 🔍 问题识别
|
|
10
|
+
|
|
11
|
+
### 用户描述关键词
|
|
12
|
+
- "SVG 颜色比设计稿浅"
|
|
13
|
+
- "图标颜色不对"
|
|
14
|
+
- "透明度丢失"
|
|
15
|
+
- "图标颜色变淡了"
|
|
16
|
+
|
|
17
|
+
### 问题特征
|
|
18
|
+
- [ ] 使用 flutter_svg 加载 SVG 图标
|
|
19
|
+
- [ ] 使用了 ColorFilter.mode
|
|
20
|
+
- [ ] SVG 原本有透明度(如 #1C2B45B3)
|
|
21
|
+
- [ ] 实际显示颜色比设计稿浅
|
|
22
|
+
|
|
23
|
+
---
|
|
24
|
+
|
|
25
|
+
## 🎯 核心原理
|
|
26
|
+
|
|
27
|
+
**根本原因**:`ColorFilter.mode(color, BlendMode.srcIn)` 会完全覆盖 SVG 的原有颜色和透明度。
|
|
28
|
+
|
|
29
|
+
```dart
|
|
30
|
+
// Sketch 设计稿:#1C2B45B3(70% 透明度的深蓝色)
|
|
31
|
+
// SVG 文件中:fill="#1C2B45" fill-opacity="0.7"
|
|
32
|
+
|
|
33
|
+
// ❌ 使用 ColorFilter 后:
|
|
34
|
+
SvgPicture.asset(
|
|
35
|
+
'icon.svg',
|
|
36
|
+
colorFilter: ColorFilter.mode(
|
|
37
|
+
Color(0xFF1C2B45), // 只有颜色,没有透明度
|
|
38
|
+
BlendMode.srcIn, // 完全覆盖原有样式
|
|
39
|
+
),
|
|
40
|
+
)
|
|
41
|
+
// 结果:显示为 100% 的深蓝色,比设计稿深/浅
|
|
42
|
+
```
|
|
43
|
+
|
|
44
|
+
---
|
|
45
|
+
|
|
46
|
+
## ✅ 正确解决方案
|
|
47
|
+
|
|
48
|
+
### 方案1:保留 SVG 原有样式(推荐)
|
|
49
|
+
|
|
50
|
+
```dart
|
|
51
|
+
// ✅ 不使用 colorFilter,保留 SVG 原有颜色和透明度
|
|
52
|
+
SvgPicture.asset(
|
|
53
|
+
'assets/icons/dropdown_arrow.svg',
|
|
54
|
+
width: 12,
|
|
55
|
+
height: 12,
|
|
56
|
+
// 不传 colorFilter
|
|
57
|
+
)
|
|
58
|
+
```
|
|
59
|
+
|
|
60
|
+
### 方案2:需要动态颜色时,包含透明度
|
|
61
|
+
|
|
62
|
+
```dart
|
|
63
|
+
// ✅ 如果必须使用 colorFilter,颜色要包含 alpha
|
|
64
|
+
SvgPicture.asset(
|
|
65
|
+
'icon.svg',
|
|
66
|
+
colorFilter: customColor != null
|
|
67
|
+
? ColorFilter.mode(
|
|
68
|
+
customColor.withValues(alpha: 0.7), // 保留透明度
|
|
69
|
+
BlendMode.srcIn,
|
|
70
|
+
)
|
|
71
|
+
: null, // 无自定义颜色时保留原样式
|
|
72
|
+
)
|
|
73
|
+
```
|
|
74
|
+
|
|
75
|
+
### 方案3:SVG 导出时保留透明度信息
|
|
76
|
+
|
|
77
|
+
```xml
|
|
78
|
+
<!-- Sketch 导出 SVG 时确保保留 fill-opacity -->
|
|
79
|
+
<path fill="#1C2B45" fill-opacity="0.7" d="..."/>
|
|
80
|
+
```
|
|
81
|
+
|
|
82
|
+
---
|
|
83
|
+
|
|
84
|
+
## 📝 Sketch 颜色透明度转换
|
|
85
|
+
|
|
86
|
+
Sketch 颜色格式:`#RRGGBBAA`(最后两位是透明度)
|
|
87
|
+
|
|
88
|
+
| Sketch | Flutter | 说明 |
|
|
89
|
+
|--------|---------|------|
|
|
90
|
+
| #1C2B45B3 | Color(0xB31C2B45) | 70% 透明度 |
|
|
91
|
+
| #00000080 | Color(0x80000000) | 50% 透明度 |
|
|
92
|
+
| #FFFFFF26 | Color(0x26FFFFFF) | 15% 透明度 |
|
|
93
|
+
|
|
94
|
+
**常用透明度对照表**:
|
|
95
|
+
- 100% = FF
|
|
96
|
+
- 70% = B3
|
|
97
|
+
- 50% = 80
|
|
98
|
+
- 30% = 4D
|
|
99
|
+
- 15% = 26
|
|
100
|
+
|
|
101
|
+
---
|
|
102
|
+
|
|
103
|
+
## ❌ 常见错误排查路线(避免重复)
|
|
104
|
+
|
|
105
|
+
| 尝试方向 | 为什么无效 | 浪费时间 |
|
|
106
|
+
|----------|-----------|---------|
|
|
107
|
+
| 调整 SVG 本身的颜色 | ColorFilter 会覆盖 | 1-2 轮对话 |
|
|
108
|
+
| 修改 BlendMode | srcIn 是正确的混合模式 | 1-2 轮对话 |
|
|
109
|
+
| 用 Opacity widget 包裹 | 影响整个图标,不是颜色透明度 | 1 轮对话 |
|
|
110
|
+
|
|
111
|
+
---
|
|
112
|
+
|
|
113
|
+
## 📁 适用场景
|
|
114
|
+
|
|
115
|
+
- ✅ 设计稿中有透明度的图标
|
|
116
|
+
- ✅ TabBar 图标
|
|
117
|
+
- ✅ 任何使用 flutter_svg 的场景
|
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
# Flutter TabBar 动画同步问题
|
|
2
|
+
|
|
3
|
+
> **问题标签**: `tabbar`, `animation`, `neumorphism`, `color`, `sync`
|
|
4
|
+
> **框架**: Flutter
|
|
5
|
+
> **严重程度**: 中等(视觉体验)
|
|
6
|
+
|
|
7
|
+
---
|
|
8
|
+
|
|
9
|
+
## 🔍 问题识别
|
|
10
|
+
|
|
11
|
+
### 用户描述关键词
|
|
12
|
+
- "颜色变化太快"
|
|
13
|
+
- "动画不同步"
|
|
14
|
+
- "背景块还没移动图标就变色了"
|
|
15
|
+
- "选中/未选中切换不流畅"
|
|
16
|
+
|
|
17
|
+
### 问题特征
|
|
18
|
+
- [ ] TabBar 有背景滑块动画
|
|
19
|
+
- [ ] 图标/文字颜色需要随选中状态变化
|
|
20
|
+
- [ ] 背景块使用 AnimatedPositioned/AnimatedContainer
|
|
21
|
+
- [ ] 颜色使用 setState 直接切换
|
|
22
|
+
|
|
23
|
+
---
|
|
24
|
+
|
|
25
|
+
## 🎯 核心原理
|
|
26
|
+
|
|
27
|
+
**根本原因**:背景块使用了动画(如 AnimatedPositioned),但颜色切换使用 `setState` 立即更新,导致颜色变化比背景块移动快。
|
|
28
|
+
|
|
29
|
+
```dart
|
|
30
|
+
// ❌ 错误:颜色立即变化
|
|
31
|
+
setState(() {
|
|
32
|
+
selectedIndex = newIndex; // 颜色立即切换
|
|
33
|
+
});
|
|
34
|
+
// 同时 AnimatedPositioned 的背景块需要 300ms 移动
|
|
35
|
+
// 结果:图标已经变白,背景还没移到位
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
---
|
|
39
|
+
|
|
40
|
+
## ✅ 正确解决方案
|
|
41
|
+
|
|
42
|
+
**使用 TweenAnimationBuilder 同步所有动画**
|
|
43
|
+
|
|
44
|
+
```dart
|
|
45
|
+
const animDuration = Duration(milliseconds: 300);
|
|
46
|
+
const animCurve = Curves.easeInOut;
|
|
47
|
+
|
|
48
|
+
// 背景块动画
|
|
49
|
+
AnimatedPositioned(
|
|
50
|
+
duration: animDuration,
|
|
51
|
+
curve: animCurve,
|
|
52
|
+
left: targetLeft,
|
|
53
|
+
child: greenBackground,
|
|
54
|
+
)
|
|
55
|
+
|
|
56
|
+
// 颜色动画(同步)
|
|
57
|
+
TweenAnimationBuilder<Color?>(
|
|
58
|
+
duration: animDuration,
|
|
59
|
+
curve: animCurve,
|
|
60
|
+
tween: ColorTween(
|
|
61
|
+
begin: isSelected ? unselectedColor : selectedColor,
|
|
62
|
+
end: isSelected ? selectedColor : unselectedColor,
|
|
63
|
+
),
|
|
64
|
+
builder: (_, color, child) => Icon(
|
|
65
|
+
icon,
|
|
66
|
+
color: color,
|
|
67
|
+
size: iconSize,
|
|
68
|
+
),
|
|
69
|
+
)
|
|
70
|
+
|
|
71
|
+
// 布局动画(同步)
|
|
72
|
+
AnimatedContainer(
|
|
73
|
+
duration: animDuration,
|
|
74
|
+
curve: animCurve,
|
|
75
|
+
height: isSelected ? 44.0 : 22.0,
|
|
76
|
+
child: icon,
|
|
77
|
+
)
|
|
78
|
+
```
|
|
79
|
+
|
|
80
|
+
### 关键要点
|
|
81
|
+
1. **统一动画参数**:所有动画使用相同的 `duration` 和 `curve`
|
|
82
|
+
2. **颜色也要动画**:使用 `TweenAnimationBuilder<Color?>` 而非直接 setState
|
|
83
|
+
3. **布局也要动画**:如果选中/未选中的布局不同,也要使用 AnimatedContainer
|
|
84
|
+
|
|
85
|
+
---
|
|
86
|
+
|
|
87
|
+
## ❌ 常见错误排查路线(避免重复)
|
|
88
|
+
|
|
89
|
+
| 尝试方向 | 为什么无效 | 浪费时间 |
|
|
90
|
+
|----------|-----------|---------|
|
|
91
|
+
| 调整动画时长 | 不解决同步问题 | 1-2 轮对话 |
|
|
92
|
+
| 使用 AnimatedSwitcher | 会导致整个组件重建 | 2-3 轮对话 |
|
|
93
|
+
| 添加延迟切换颜色 | Hack 方案,难以精确匹配 | 2-3 轮对话 |
|
|
94
|
+
|
|
95
|
+
---
|
|
96
|
+
|
|
97
|
+
## 📁 适用场景
|
|
98
|
+
|
|
99
|
+
- ✅ 底部导航栏 TabBar
|
|
100
|
+
- ✅ 分段控制器 SegmentedControl
|
|
101
|
+
- ✅ 任何需要"选中滑块 + 颜色变化"的组件
|
|
102
|
+
|
|
103
|
+
---
|
|
104
|
+
|
|
105
|
+
## 🔗 相关案例
|
|
106
|
+
|
|
107
|
+
- [clip-阴影裁剪](./clip-阴影裁剪.md) - 滑块阴影被裁剪问题
|