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.
Files changed (24) hide show
  1. package/agents/flutter.agent.md +117 -1147
  2. package/agents/vue3.agent.md +177 -464
  3. package/dist/index.d.ts +63 -0
  4. package/dist/index.js +551 -132
  5. package/dist/index.js.map +1 -1
  6. package/package.json +2 -1
  7. package/troubleshooting/README.md +366 -0
  8. package/troubleshooting/USAGE_GUIDE.md +289 -0
  9. package/troubleshooting/flutter/clip-/351/230/264/345/275/261/350/243/201/345/211/252.md +244 -0
  10. package/troubleshooting/flutter/input-/345/255/227/346/256/265/347/274/272/345/244/261.md +240 -0
  11. package/troubleshooting/flutter/input-/350/276/271/346/241/206/351/227/256/351/242/230.md +236 -0
  12. package/troubleshooting/flutter/layout-/345/260/272/345/257/270/344/270/215/345/214/271/351/205/215.md +214 -0
  13. package/troubleshooting/flutter/shadow-/351/200/217/345/207/272/351/227/256/351/242/230.md +172 -0
  14. package/troubleshooting/flutter/sketch-/345/233/276/346/240/207/345/260/272/345/257/270.md +135 -0
  15. package/troubleshooting/flutter/sketch-/345/256/214/346/225/264/346/217/220/345/217/226.md +201 -0
  16. package/troubleshooting/flutter/sketch-/345/261/236/346/200/247/346/234/252/344/275/277/347/224/250.md +139 -0
  17. package/troubleshooting/flutter/svg-/346/234/252/345/261/205/344/270/255.md +120 -0
  18. package/troubleshooting/flutter/svg-/351/242/234/350/211/262/345/274/202/345/270/270.md +117 -0
  19. package/troubleshooting/flutter/tabbar-/345/212/250/347/224/273/345/220/214/346/255/245.md +107 -0
  20. package/troubleshooting/flutter/withopacity-/345/274/203/347/224/250.md +81 -0
  21. package/troubleshooting/vue3/cascader-/350/257/257/346/233/277/346/215/242.md +130 -0
  22. package/troubleshooting/vue3/drawer-input-/346/240/267/345/274/217.md +181 -0
  23. package/troubleshooting/vue3/table-/347/274/226/350/276/221/345/217/226/346/266/210.md +148 -0
  24. 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) - 滑块阴影被裁剪问题