mta-mcp 2.15.0 → 2.16.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 +69 -1
- package/package.json +1 -1
- package/standards/frameworks/flutter.md +78 -0
- package/standards/troubleshooting-cases/flutter/textfield-vertical-centering.md +107 -0
- package/standards/workflows/design-restoration-guide.md +164 -0
- package/standards/workflows/problem-diagnosis.md +68 -0
- package/standards/workflows/textfield-centering-guide.md +157 -0
package/agents/flutter.agent.md
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
# Flutter 开发代理
|
|
2
2
|
|
|
3
3
|
> 此 Agent 引导 AI 通过 MCP 工具获取 npm 包中的详细规范
|
|
4
|
-
> 版本: v3.
|
|
4
|
+
> 版本: v3.1.0 | 最后更新: 2026-01-19
|
|
5
5
|
|
|
6
6
|
---
|
|
7
7
|
|
|
@@ -22,6 +22,41 @@ troubleshoot({ problem: "用户描述的问题" })
|
|
|
22
22
|
| 阴影被裁剪 | clip, shadow, clipBehavior |
|
|
23
23
|
| SVG 颜色/居中问题 | svg, color, viewbox |
|
|
24
24
|
| 输入框边框异常 | input, border, focus |
|
|
25
|
+
| **TextField 垂直居中** | textfield, placeholder, 居中, 光标, 输入框 |
|
|
26
|
+
|
|
27
|
+
---
|
|
28
|
+
|
|
29
|
+
## ⚠️ TextField 垂直居中(高频问题)
|
|
30
|
+
|
|
31
|
+
> 此问题曾导致 15+ 轮对话才修复,必须一步到位
|
|
32
|
+
|
|
33
|
+
### 问题特征
|
|
34
|
+
- placeholder 和输入内容位置不一致
|
|
35
|
+
- 光标位置偏上/偏下
|
|
36
|
+
- 修改一个问题引发另一个问题
|
|
37
|
+
|
|
38
|
+
### 正确方案(直接使用)
|
|
39
|
+
|
|
40
|
+
```dart
|
|
41
|
+
Container(
|
|
42
|
+
height: 36, // 设计稿容器高度
|
|
43
|
+
alignment: Alignment.center, // 关键1
|
|
44
|
+
child: TextField(
|
|
45
|
+
style: TextStyle(fontSize: 14, height: 1.43), // 关键2: 行高 = 文本高度÷字号
|
|
46
|
+
decoration: InputDecoration(
|
|
47
|
+
hintStyle: TextStyle(fontSize: 14, height: 1.43), // 关键3: 必须与style一致
|
|
48
|
+
contentPadding: EdgeInsets.zero, // 关键4
|
|
49
|
+
isDense: true, // 关键5
|
|
50
|
+
),
|
|
51
|
+
),
|
|
52
|
+
)
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
### 禁止事项
|
|
56
|
+
- ❌ `style.height` ≠ `hintStyle.height`
|
|
57
|
+
- ❌ 同时用 `textAlignVertical` 和 `height`
|
|
58
|
+
- ❌ 用 `strutStyle + forceStrutHeight`
|
|
59
|
+
- ❌ 反复调整 `contentPadding` 试错
|
|
25
60
|
|
|
26
61
|
---
|
|
27
62
|
|
|
@@ -101,5 +136,38 @@ get_standard_by_id({ ids: ['flutter', 'flutter-ui-system'] })
|
|
|
101
136
|
|
|
102
137
|
---
|
|
103
138
|
|
|
139
|
+
## 🎨 设计稿还原强制规范
|
|
140
|
+
|
|
141
|
+
> ⚠️ 使用 Sketch/Figma MCP 时必须遵守
|
|
142
|
+
|
|
143
|
+
### 强制要求:完整读取选中元素及其所有子集
|
|
144
|
+
|
|
145
|
+
```javascript
|
|
146
|
+
// 正确:一次性获取容器+所有子元素的完整信息
|
|
147
|
+
function extractComplete(element) {
|
|
148
|
+
// 1. 容器信息
|
|
149
|
+
console.log(`容器: ${element.frame.width}x${element.frame.height}`);
|
|
150
|
+
|
|
151
|
+
// 2. 所有子元素信息(关键!)
|
|
152
|
+
element.layers.forEach(child => {
|
|
153
|
+
console.log(`子元素: ${child.name}`);
|
|
154
|
+
console.log(` 位置: Y=${child.frame.y}px`);
|
|
155
|
+
console.log(` 尺寸: ${child.frame.width}x${child.frame.height}`);
|
|
156
|
+
if (child.type === 'Text') {
|
|
157
|
+
console.log(` 字号: ${child.style.fontSize}px`);
|
|
158
|
+
console.log(` 行高: ${child.frame.height / child.style.fontSize}`);
|
|
159
|
+
}
|
|
160
|
+
});
|
|
161
|
+
}
|
|
162
|
+
```
|
|
163
|
+
|
|
164
|
+
### 禁止事项
|
|
165
|
+
- ❌ 只读容器属性,忽略内部元素位置
|
|
166
|
+
- ❌ 每次只查一个属性,分散多轮获取
|
|
167
|
+
- ❌ 假设"差不多"而不验证精确像素值
|
|
168
|
+
- ❌ 用"试错法"调整参数
|
|
169
|
+
|
|
170
|
+
---
|
|
171
|
+
|
|
104
172
|
**维护团队**: MTA工作室
|
|
105
173
|
**设计理念**: Agent 只提供获取指引,详细规范由 MCP 工具从 npm 包动态获取
|
package/package.json
CHANGED
|
@@ -996,6 +996,84 @@ class UserProfile extends StatelessWidget {
|
|
|
996
996
|
|
|
997
997
|
---
|
|
998
998
|
|
|
999
|
+
## 📝 TextField 垂直居中规范(重要)
|
|
1000
|
+
|
|
1001
|
+
> ⚠️ **此问题曾导致 15+ 轮对话才修复,必须一步到位**
|
|
1002
|
+
|
|
1003
|
+
### 问题场景
|
|
1004
|
+
TextField 中 placeholder、光标、输入内容三者需要在固定高度容器中垂直居中对齐。
|
|
1005
|
+
|
|
1006
|
+
### 正确方案(一步到位)
|
|
1007
|
+
|
|
1008
|
+
```dart
|
|
1009
|
+
// ✅ 正确 - Container.alignment + 统一 height + contentPadding.zero
|
|
1010
|
+
Widget _buildCenteredTextField(String placeholder, TextEditingController controller) {
|
|
1011
|
+
return Container(
|
|
1012
|
+
height: 36, // 固定容器高度
|
|
1013
|
+
alignment: Alignment.center, // 关键1:让 TextField 整体居中
|
|
1014
|
+
child: TextField(
|
|
1015
|
+
controller: controller,
|
|
1016
|
+
textAlign: TextAlign.center,
|
|
1017
|
+
style: const TextStyle(
|
|
1018
|
+
fontSize: 14,
|
|
1019
|
+
height: 1.43, // 关键2:行高 = 期望文本高度 ÷ 字号
|
|
1020
|
+
),
|
|
1021
|
+
decoration: InputDecoration(
|
|
1022
|
+
hintText: placeholder,
|
|
1023
|
+
hintStyle: const TextStyle(
|
|
1024
|
+
fontSize: 14,
|
|
1025
|
+
height: 1.43, // 关键3:必须与 style.height 完全一致
|
|
1026
|
+
),
|
|
1027
|
+
contentPadding: EdgeInsets.zero, // 关键4:清除默认 padding
|
|
1028
|
+
isDense: true, // 关键5:移除额外空间
|
|
1029
|
+
border: InputBorder.none,
|
|
1030
|
+
),
|
|
1031
|
+
),
|
|
1032
|
+
);
|
|
1033
|
+
}
|
|
1034
|
+
|
|
1035
|
+
// ❌ 错误 - 会导致 placeholder 和输入内容位置不一致
|
|
1036
|
+
TextField(
|
|
1037
|
+
textAlignVertical: TextAlignVertical.center, // 只影响输入内容
|
|
1038
|
+
style: TextStyle(fontSize: 14, height: 1.43),
|
|
1039
|
+
decoration: InputDecoration(
|
|
1040
|
+
hintStyle: TextStyle(fontSize: 14), // height 不一致!
|
|
1041
|
+
contentPadding: EdgeInsets.symmetric(vertical: 8), // 干扰居中
|
|
1042
|
+
),
|
|
1043
|
+
)
|
|
1044
|
+
```
|
|
1045
|
+
|
|
1046
|
+
### 行高计算公式
|
|
1047
|
+
|
|
1048
|
+
从设计稿获取:
|
|
1049
|
+
- 容器高度:36px
|
|
1050
|
+
- 文本 Y 坐标:8px(距顶部)
|
|
1051
|
+
- 文本高度:20px
|
|
1052
|
+
- 字号:14px
|
|
1053
|
+
|
|
1054
|
+
```
|
|
1055
|
+
height = 文本高度 ÷ 字号 = 20 ÷ 14 = 1.43
|
|
1056
|
+
```
|
|
1057
|
+
|
|
1058
|
+
### 禁止事项
|
|
1059
|
+
|
|
1060
|
+
| 禁止 | 原因 |
|
|
1061
|
+
|------|------|
|
|
1062
|
+
| `style.height` ≠ `hintStyle.height` | placeholder 和输入内容位置不一致 |
|
|
1063
|
+
| 同时用 `textAlignVertical` 和 `height` | 产生冲突,效果不可预测 |
|
|
1064
|
+
| 用 `strutStyle + forceStrutHeight` | 可能压缩文字 |
|
|
1065
|
+
| 反复调整 `contentPadding` 试错 | 应先确定行高配置 |
|
|
1066
|
+
|
|
1067
|
+
### 调试顺序(遇到问题时)
|
|
1068
|
+
|
|
1069
|
+
1. 先从设计稿获取:容器高度、文本 Y 坐标、文本高度
|
|
1070
|
+
2. 计算 `height = 文本高度 ÷ 字号`
|
|
1071
|
+
3. 确保 `style.height` = `hintStyle.height`
|
|
1072
|
+
4. 设置 `Container.alignment: Alignment.center`
|
|
1073
|
+
5. 设置 `contentPadding: EdgeInsets.zero` + `isDense: true`
|
|
1074
|
+
|
|
1075
|
+
---
|
|
1076
|
+
|
|
999
1077
|
## 🎨 Sketch/Figma 设计稿还原规范
|
|
1000
1078
|
|
|
1001
1079
|
> ⚠️ **此章节为强制执行规范** - 所有 UI 还原任务必须严格遵循
|
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
# TextField 垂直居中问题
|
|
2
|
+
|
|
3
|
+
**问题标签**: `textfield`, `居中`, `placeholder`, `光标`, `输入框`, `vertical-align`
|
|
4
|
+
**问题类型**: `UI 对齐`
|
|
5
|
+
**严重程度**: 高
|
|
6
|
+
**节省时间**: 15+ 轮对话 → 1 轮
|
|
7
|
+
|
|
8
|
+
---
|
|
9
|
+
|
|
10
|
+
## 问题描述
|
|
11
|
+
|
|
12
|
+
TextField 中 placeholder、光标、输入内容三者需要在固定高度容器中垂直居中对齐,但出现以下问题:
|
|
13
|
+
- placeholder 和输入内容位置不一致
|
|
14
|
+
- 光标位置偏上或偏下
|
|
15
|
+
- 修改一个问题引发另一个问题
|
|
16
|
+
|
|
17
|
+
## 问题根因
|
|
18
|
+
|
|
19
|
+
Flutter TextField 的垂直对齐涉及多个相互作用的属性:
|
|
20
|
+
- `style.height` - 输入文本的行高
|
|
21
|
+
- `hintStyle.height` - placeholder 的行高
|
|
22
|
+
- `contentPadding` - 内边距
|
|
23
|
+
- `isDense` - 紧凑模式
|
|
24
|
+
- `textAlignVertical` - 垂直对齐
|
|
25
|
+
- `strutStyle` - 行高基准
|
|
26
|
+
|
|
27
|
+
**核心问题**:这些属性相互影响,单独调整一个可能破坏其他元素的对齐。
|
|
28
|
+
|
|
29
|
+
## 错误尝试(导致反复)
|
|
30
|
+
|
|
31
|
+
| 尝试 | 结果 |
|
|
32
|
+
|------|------|
|
|
33
|
+
| 只调整 `contentPadding` | 整体偏移,不居中 |
|
|
34
|
+
| 用 `textAlignVertical: center` | 只影响输入内容,placeholder 不动 |
|
|
35
|
+
| 用 `strutStyle + forceStrutHeight` | 文字被压缩变形 |
|
|
36
|
+
| `style.height` ≠ `hintStyle.height` | placeholder 和输入内容位置不一致 |
|
|
37
|
+
|
|
38
|
+
## 正确方案
|
|
39
|
+
|
|
40
|
+
### 从设计稿获取数值
|
|
41
|
+
|
|
42
|
+
```javascript
|
|
43
|
+
// Sketch 提取脚本
|
|
44
|
+
const container = sketch.find('[name="Input"]', artboard)[0];
|
|
45
|
+
const text = container.layers.find(l => l.type === 'Text');
|
|
46
|
+
|
|
47
|
+
console.log(`容器高度: ${container.frame.height}px`); // 36
|
|
48
|
+
console.log(`文本 Y 坐标: ${text.frame.y}px`); // 8
|
|
49
|
+
console.log(`文本高度: ${text.frame.height}px`); // 20
|
|
50
|
+
console.log(`字号: ${text.style.fontSize}px`); // 14
|
|
51
|
+
console.log(`行高倍数: ${text.frame.height / text.style.fontSize}`); // 1.43
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
### Flutter 代码
|
|
55
|
+
|
|
56
|
+
```dart
|
|
57
|
+
Container(
|
|
58
|
+
height: 36, // 设计稿容器高度
|
|
59
|
+
alignment: Alignment.center, // 关键1:让 TextField 整体居中
|
|
60
|
+
child: TextField(
|
|
61
|
+
textAlign: TextAlign.center,
|
|
62
|
+
style: const TextStyle(
|
|
63
|
+
fontSize: 14,
|
|
64
|
+
height: 1.43, // 关键2:行高 = 文本高度÷字号 = 20÷14
|
|
65
|
+
),
|
|
66
|
+
decoration: InputDecoration(
|
|
67
|
+
hintText: placeholder,
|
|
68
|
+
hintStyle: const TextStyle(
|
|
69
|
+
fontSize: 14,
|
|
70
|
+
height: 1.43, // 关键3:必须与 style.height 完全一致
|
|
71
|
+
),
|
|
72
|
+
contentPadding: EdgeInsets.zero, // 关键4:清除默认 padding
|
|
73
|
+
isDense: true, // 关键5:移除额外空间
|
|
74
|
+
border: InputBorder.none,
|
|
75
|
+
),
|
|
76
|
+
),
|
|
77
|
+
)
|
|
78
|
+
```
|
|
79
|
+
|
|
80
|
+
## 关键原则
|
|
81
|
+
|
|
82
|
+
| 原则 | 说明 |
|
|
83
|
+
|------|------|
|
|
84
|
+
| `style.height` = `hintStyle.height` | 两者必须相同,否则位置不一致 |
|
|
85
|
+
| `Container.alignment` 控制居中 | 不要用 `textAlignVertical` |
|
|
86
|
+
| `contentPadding: EdgeInsets.zero` | 让 Container.alignment 生效 |
|
|
87
|
+
| 先计算再编码 | 行高 = 文本高度 ÷ 字号 |
|
|
88
|
+
|
|
89
|
+
## 禁止事项
|
|
90
|
+
|
|
91
|
+
- ❌ `style.height` ≠ `hintStyle.height`
|
|
92
|
+
- ❌ 同时使用 `textAlignVertical` 和 `height`
|
|
93
|
+
- ❌ 使用 `strutStyle + forceStrutHeight`
|
|
94
|
+
- ❌ 反复调整 `contentPadding` 试错
|
|
95
|
+
|
|
96
|
+
## 验证方法
|
|
97
|
+
|
|
98
|
+
1. 点击输入框,检查光标位置
|
|
99
|
+
2. 输入文字,检查文字位置
|
|
100
|
+
3. 清空输入,检查 placeholder 位置
|
|
101
|
+
4. 三者应该在同一垂直位置
|
|
102
|
+
|
|
103
|
+
---
|
|
104
|
+
|
|
105
|
+
**案例来源**: 汇款记录筛选弹窗金额输入框
|
|
106
|
+
**创建日期**: 2026-01-19
|
|
107
|
+
**版本**: v1.0
|
|
@@ -0,0 +1,164 @@
|
|
|
1
|
+
# 设计稿还原通用规范
|
|
2
|
+
|
|
3
|
+
> 适用于使用 Sketch、Figma 等设计工具 MCP 进行 UI 还原的所有场景
|
|
4
|
+
|
|
5
|
+
## 🔴 核心问题
|
|
6
|
+
|
|
7
|
+
本规范基于真实案例总结:**一个简单的输入框垂直居中问题,经历了 15+ 轮对话才修复**。
|
|
8
|
+
|
|
9
|
+
### 问题根源分析
|
|
10
|
+
|
|
11
|
+
| 问题 | 表现 | 后果 |
|
|
12
|
+
|------|------|------|
|
|
13
|
+
| **属性读取不完整** | 只读容器尺寸,忽略内部文本位置 | 无法精确计算边距和行高 |
|
|
14
|
+
| **分散查询** | 每次只问一个属性 | 信息不完整,反复补充 |
|
|
15
|
+
| **假设而非验证** | "应该差不多居中" | 实际偏差明显 |
|
|
16
|
+
| **试错式调整** | 逐个调整参数看效果 | 改了 A 问题引发 B 问题 |
|
|
17
|
+
|
|
18
|
+
---
|
|
19
|
+
|
|
20
|
+
## ✅ 强制执行规范
|
|
21
|
+
|
|
22
|
+
### 1. 完整读取选中元素及其所有子集
|
|
23
|
+
|
|
24
|
+
**⚠️ 这是最重要的规则**
|
|
25
|
+
|
|
26
|
+
```javascript
|
|
27
|
+
// 正确示例:一次性获取完整信息
|
|
28
|
+
function extractCompleteInfo(element) {
|
|
29
|
+
console.log('=== 容器信息 ===');
|
|
30
|
+
console.log(`尺寸: ${element.frame.width}x${element.frame.height}px`);
|
|
31
|
+
|
|
32
|
+
console.log('=== 所有子元素 ===');
|
|
33
|
+
element.layers.forEach((child, index) => {
|
|
34
|
+
console.log(`\n子元素 ${index}: ${child.name} (${child.type})`);
|
|
35
|
+
console.log(` X: ${child.frame.x}px, Y: ${child.frame.y}px`);
|
|
36
|
+
console.log(` 宽: ${child.frame.width}px, 高: ${child.frame.height}px`);
|
|
37
|
+
|
|
38
|
+
if (child.type === 'Text') {
|
|
39
|
+
console.log(` 字号: ${child.style.fontSize}px`);
|
|
40
|
+
console.log(` 字重: ${child.style.fontWeight}`);
|
|
41
|
+
console.log(` 颜色: ${child.style.textColor}`);
|
|
42
|
+
console.log(` 行高倍数: ${(child.frame.height / child.style.fontSize).toFixed(2)}`);
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
// 递归处理嵌套子元素
|
|
46
|
+
if (child.layers && child.layers.length > 0) {
|
|
47
|
+
child.layers.forEach(nested => {
|
|
48
|
+
console.log(` 嵌套: ${nested.name} (${nested.type})`);
|
|
49
|
+
console.log(` 位置: Y=${nested.frame.y}px`);
|
|
50
|
+
});
|
|
51
|
+
}
|
|
52
|
+
});
|
|
53
|
+
|
|
54
|
+
console.log('=== 计算结果 ===');
|
|
55
|
+
const textChild = element.layers.find(l => l.type === 'Text');
|
|
56
|
+
if (textChild) {
|
|
57
|
+
const topSpace = textChild.frame.y;
|
|
58
|
+
const bottomSpace = element.frame.height - textChild.frame.y - textChild.frame.height;
|
|
59
|
+
console.log(`文本上边距: ${topSpace}px`);
|
|
60
|
+
console.log(`文本下边距: ${bottomSpace}px`);
|
|
61
|
+
console.log(`是否居中: ${Math.abs(topSpace - bottomSpace) < 2 ? '是' : '否'}`);
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
```
|
|
65
|
+
|
|
66
|
+
### 2. 必须获取的完整信息清单
|
|
67
|
+
|
|
68
|
+
| 元素类型 | 必须获取的属性 | 用途 |
|
|
69
|
+
|----------|----------------|------|
|
|
70
|
+
| **容器** | width, height, padding, 背景色/渐变 | 外层布局 |
|
|
71
|
+
| **文本** | Y 坐标, height, fontSize, fontWeight, color | 计算行高和边距 |
|
|
72
|
+
| **图标** | viewBox, path, fill, fill-opacity | 完整导出 SVG |
|
|
73
|
+
| **渐变** | 所有 stops(颜色+位置), from, to | 精确还原 |
|
|
74
|
+
| **阴影** | x, y, blur, spread, color (全部 5 个) | 完整阴影 |
|
|
75
|
+
| **边框** | color, width, position | 边框样式 |
|
|
76
|
+
|
|
77
|
+
### 3. 计算而非试错
|
|
78
|
+
|
|
79
|
+
**正确流程**:
|
|
80
|
+
```
|
|
81
|
+
1. 从设计稿获取精确数值
|
|
82
|
+
2. 计算目标框架需要的参数
|
|
83
|
+
3. 一次性配置完整方案
|
|
84
|
+
4. 验证结果
|
|
85
|
+
```
|
|
86
|
+
|
|
87
|
+
**示例:TextField 行高计算**
|
|
88
|
+
```
|
|
89
|
+
设计稿数据:
|
|
90
|
+
- 容器高度: 36px
|
|
91
|
+
- 文本 Y 坐标: 8px
|
|
92
|
+
- 文本高度: 20px
|
|
93
|
+
- 字号: 14px
|
|
94
|
+
|
|
95
|
+
计算:
|
|
96
|
+
- 行高倍数 = 文本高度 ÷ 字号 = 20 ÷ 14 = 1.43
|
|
97
|
+
- 上边距 = 文本 Y 坐标 = 8px
|
|
98
|
+
- 下边距 = 容器高度 - Y - 文本高度 = 36 - 8 - 20 = 8px
|
|
99
|
+
|
|
100
|
+
Flutter 配置:
|
|
101
|
+
style: TextStyle(fontSize: 14, height: 1.43)
|
|
102
|
+
Container: height=36, alignment=Alignment.center
|
|
103
|
+
```
|
|
104
|
+
|
|
105
|
+
---
|
|
106
|
+
|
|
107
|
+
## 📋 框架特定注意事项
|
|
108
|
+
|
|
109
|
+
### Flutter
|
|
110
|
+
|
|
111
|
+
| 问题场景 | 关键属性 | 注意事项 |
|
|
112
|
+
|----------|----------|----------|
|
|
113
|
+
| TextField 居中 | `style.height`, `hintStyle.height` | 两者必须相同 |
|
|
114
|
+
| 容器阴影 | `clipBehavior` | 设为 `Clip.none` 避免裁剪 |
|
|
115
|
+
| Gap 间距 | `Gap()` vs `Gap.h()` | 默认垂直,`.h()` 水平 |
|
|
116
|
+
| SVG 颜色 | `colorFilter` | 不要覆盖,保留原色 |
|
|
117
|
+
|
|
118
|
+
### Vue/CSS
|
|
119
|
+
|
|
120
|
+
| 问题场景 | 关键属性 | 注意事项 |
|
|
121
|
+
|----------|----------|----------|
|
|
122
|
+
| Flex 居中 | `align-items`, `justify-content` | 注意主轴方向 |
|
|
123
|
+
| 文本居中 | `line-height` | 设为容器高度实现垂直居中 |
|
|
124
|
+
| 边框问题 | `box-sizing` | 使用 `border-box` |
|
|
125
|
+
| 阴影裁剪 | `overflow` | 避免 `hidden` 裁剪阴影 |
|
|
126
|
+
|
|
127
|
+
### React Native
|
|
128
|
+
|
|
129
|
+
| 问题场景 | 关键属性 | 注意事项 |
|
|
130
|
+
|----------|----------|----------|
|
|
131
|
+
| 文本居中 | `textAlignVertical` | 仅 Android 生效 |
|
|
132
|
+
| 输入框 | `includeFontPadding` | Android 设为 false |
|
|
133
|
+
| 阴影 | `elevation` vs `shadow*` | 平台差异 |
|
|
134
|
+
|
|
135
|
+
---
|
|
136
|
+
|
|
137
|
+
## 🚫 禁止事项
|
|
138
|
+
|
|
139
|
+
1. ❌ **禁止只读容器属性** - 必须读取所有子元素
|
|
140
|
+
2. ❌ **禁止分散查询** - 一次性获取完整信息
|
|
141
|
+
3. ❌ **禁止假设数值** - 必须从设计稿读取精确像素
|
|
142
|
+
4. ❌ **禁止试错调整** - 先计算,再编码
|
|
143
|
+
5. ❌ **禁止忽略透明度** - 颜色 `#RRGGBBAA` 最后两位是透明度
|
|
144
|
+
6. ❌ **禁止属性不一致** - 相关属性(如 style 和 hintStyle)必须统一配置
|
|
145
|
+
|
|
146
|
+
---
|
|
147
|
+
|
|
148
|
+
## ✅ 检查清单
|
|
149
|
+
|
|
150
|
+
还原任何设计稿元素前:
|
|
151
|
+
|
|
152
|
+
- [ ] 已获取目标元素的**容器尺寸**
|
|
153
|
+
- [ ] 已获取**所有子元素**的位置和尺寸
|
|
154
|
+
- [ ] 已获取文本的**Y 坐标、高度、字号**
|
|
155
|
+
- [ ] 已**计算**行高倍数和边距
|
|
156
|
+
- [ ] 已确认相关属性(style/hintStyle)**配置一致**
|
|
157
|
+
- [ ] 已一次性配置**完整方案**
|
|
158
|
+
- [ ] 已验证**所有相关元素**符合预期
|
|
159
|
+
|
|
160
|
+
---
|
|
161
|
+
|
|
162
|
+
**维护团队**: MTA工作室
|
|
163
|
+
**创建日期**: 2026-01-19
|
|
164
|
+
**版本**: v1.0
|
|
@@ -194,6 +194,74 @@
|
|
|
194
194
|
|
|
195
195
|
---
|
|
196
196
|
|
|
197
|
+
## 🎨 设计稿还原专项规范
|
|
198
|
+
|
|
199
|
+
> ⚠️ **此章节基于真实案例:一个输入框居中问题反复 15+ 轮对话才修复**
|
|
200
|
+
|
|
201
|
+
### 设计稿工具使用规范(Sketch/Figma)
|
|
202
|
+
|
|
203
|
+
#### 强制要求:完整读取选中元素及其所有子集
|
|
204
|
+
|
|
205
|
+
**问题根源**:
|
|
206
|
+
- 只读取了容器属性,忽略了内部文本的精确位置
|
|
207
|
+
- 每次只查询一个属性,导致信息不完整
|
|
208
|
+
- 假设而非验证具体数值
|
|
209
|
+
|
|
210
|
+
**正确做法**:
|
|
211
|
+
|
|
212
|
+
```javascript
|
|
213
|
+
// 示例:读取输入框完整信息(包含所有子集)
|
|
214
|
+
function extractInputFieldComplete(inputGroup) {
|
|
215
|
+
console.log('=== 容器信息 ===');
|
|
216
|
+
console.log(`宽度: ${inputGroup.frame.width}px`);
|
|
217
|
+
console.log(`高度: ${inputGroup.frame.height}px`);
|
|
218
|
+
|
|
219
|
+
console.log('=== 内部文本(子集)===');
|
|
220
|
+
const text = inputGroup.layers.find(l => l.type === 'Text');
|
|
221
|
+
console.log(`文本 Y 坐标: ${text.frame.y}px`); // 关键:距容器顶部距离
|
|
222
|
+
console.log(`文本高度: ${text.frame.height}px`); // 关键:计算行高
|
|
223
|
+
console.log(`字号: ${text.style.fontSize}px`);
|
|
224
|
+
|
|
225
|
+
console.log('=== 计算结果 ===');
|
|
226
|
+
const topSpace = text.frame.y;
|
|
227
|
+
const bottomSpace = inputGroup.frame.height - text.frame.y - text.frame.height;
|
|
228
|
+
console.log(`上边距: ${topSpace}px`);
|
|
229
|
+
console.log(`下边距: ${bottomSpace}px`);
|
|
230
|
+
console.log(`行高倍数: ${(text.frame.height / text.style.fontSize).toFixed(2)}`);
|
|
231
|
+
}
|
|
232
|
+
```
|
|
233
|
+
|
|
234
|
+
#### 必须获取的信息清单
|
|
235
|
+
|
|
236
|
+
| 元素类型 | 必须获取 | 用途 |
|
|
237
|
+
|----------|----------|------|
|
|
238
|
+
| 容器 | width, height, padding | 外层尺寸 |
|
|
239
|
+
| 文本 | fontSize, Y坐标, height, fontWeight | 计算行高和边距 |
|
|
240
|
+
| 图标/SVG | viewBox, path, fill-opacity | 完整导出 |
|
|
241
|
+
| 渐变 | stops(所有节点), from, to | 精确还原 |
|
|
242
|
+
| 阴影 | x, y, blur, spread, color | 全部5个参数 |
|
|
243
|
+
|
|
244
|
+
### 反复修复问题的根因分析
|
|
245
|
+
|
|
246
|
+
| 导致反复的原因 | 正确做法 |
|
|
247
|
+
|----------------|----------|
|
|
248
|
+
| 只读取容器高度,不读内部文本 Y 坐标 | 一次性获取容器+所有子元素的完整信息 |
|
|
249
|
+
| 用"试错法"调整参数 | 先计算精确值(如行高=文本高度÷字号) |
|
|
250
|
+
| 修改 A 属性引发 B 问题 | 理解属性之间的相互作用关系 |
|
|
251
|
+
| 假设"差不多居中" | 从设计稿获取精确像素值验证 |
|
|
252
|
+
|
|
253
|
+
### 设计稿还原检查清单
|
|
254
|
+
|
|
255
|
+
修复任何设计还原问题前:
|
|
256
|
+
|
|
257
|
+
- [ ] 已获取目标元素的**完整信息**(容器+所有子集)
|
|
258
|
+
- [ ] 已计算精确数值(行高、边距、颜色透明度)
|
|
259
|
+
- [ ] 已理解框架渲染机制(如 Flutter TextField 的多属性相互作用)
|
|
260
|
+
- [ ] 一次性配置完整方案,而非逐个属性调整
|
|
261
|
+
- [ ] 验证所有相关元素(placeholder、光标、输入内容)都符合预期
|
|
262
|
+
|
|
263
|
+
---
|
|
264
|
+
|
|
197
265
|
## ✅ 检查清单
|
|
198
266
|
|
|
199
267
|
修复任何问题前,确认以下检查项:
|
|
@@ -0,0 +1,157 @@
|
|
|
1
|
+
# Flutter TextField 垂直居中纠错指南
|
|
2
|
+
|
|
3
|
+
> 本文档基于一次真实案例总结:修复一个简单的输入框垂直居中问题,经历了 **15+ 轮对话**,涉及多种方案反复尝试
|
|
4
|
+
|
|
5
|
+
## 📋 问题总结
|
|
6
|
+
|
|
7
|
+
### 问题描述
|
|
8
|
+
输入框需要实现 placeholder、光标、输入内容三者在容器中垂直居中对齐。
|
|
9
|
+
|
|
10
|
+
### 问题根因
|
|
11
|
+
Flutter TextField 的垂直对齐涉及**多个相互作用的属性**,修改一个属性可能影响其他元素的位置,导致"改了 A 问题引发 B 问题"的循环。
|
|
12
|
+
|
|
13
|
+
### 反复失败的原因
|
|
14
|
+
|
|
15
|
+
| 尝试次数 | 方案 | 结果 |
|
|
16
|
+
|----------|------|------|
|
|
17
|
+
| 1 | contentPadding: vertical: 10 | 整体偏下 |
|
|
18
|
+
| 2 | textAlignVertical: center | 只影响输入内容,placeholder 不动 |
|
|
19
|
+
| 3 | 移除 contentPadding | 整体偏上 |
|
|
20
|
+
| 4 | strutStyle + height: 1.0 | 文字被压缩,更难看 |
|
|
21
|
+
| 5 | Container.alignment + contentPadding: 8 | 输入内容正常,placeholder 偏下 |
|
|
22
|
+
| 6 | hintStyle height: 1.2 | placeholder 移动,但不够 |
|
|
23
|
+
| 7 | hintStyle height: 1.0 | placeholder 过于靠上 |
|
|
24
|
+
| 8-15 | 各种组合... | 来回折腾 |
|
|
25
|
+
|
|
26
|
+
### 根本问题
|
|
27
|
+
**没有一开始就理解 TextField 的渲染模型**,而是"试错式"调整参数。
|
|
28
|
+
|
|
29
|
+
---
|
|
30
|
+
|
|
31
|
+
## ✅ 正确方案(一步到位)
|
|
32
|
+
|
|
33
|
+
### 设计规格(从 Sketch 获取)
|
|
34
|
+
- 容器高度:36px
|
|
35
|
+
- 文本 Y 坐标:8px(距顶部)
|
|
36
|
+
- 文本高度:20px
|
|
37
|
+
- 字号:14px
|
|
38
|
+
- 行高倍数:1.43(20 ÷ 14 = 1.43)
|
|
39
|
+
|
|
40
|
+
### Flutter 正确配置
|
|
41
|
+
|
|
42
|
+
```dart
|
|
43
|
+
Widget _buildCenteredTextField({
|
|
44
|
+
required String placeholder,
|
|
45
|
+
required TextEditingController controller,
|
|
46
|
+
}) {
|
|
47
|
+
return Container(
|
|
48
|
+
height: 36,
|
|
49
|
+
decoration: BoxDecoration(
|
|
50
|
+
// ... 背景、圆角、阴影
|
|
51
|
+
),
|
|
52
|
+
alignment: Alignment.center, // 关键:让 TextField 在容器中居中
|
|
53
|
+
child: TextField(
|
|
54
|
+
controller: controller,
|
|
55
|
+
textAlign: TextAlign.center, // 水平居中
|
|
56
|
+
// 不要使用 textAlignVertical,会导致 placeholder 和输入内容位置不一致
|
|
57
|
+
style: const TextStyle(
|
|
58
|
+
fontSize: 14,
|
|
59
|
+
height: 1.43, // 关键:与 hintStyle 保持一致
|
|
60
|
+
fontWeight: FontWeight.w600,
|
|
61
|
+
),
|
|
62
|
+
decoration: InputDecoration(
|
|
63
|
+
hintText: placeholder,
|
|
64
|
+
hintStyle: const TextStyle(
|
|
65
|
+
fontSize: 14,
|
|
66
|
+
height: 1.43, // 关键:与 style 完全一致
|
|
67
|
+
fontWeight: FontWeight.w600,
|
|
68
|
+
),
|
|
69
|
+
contentPadding: EdgeInsets.zero, // 关键:清除默认 padding
|
|
70
|
+
isDense: true, // 关键:移除额外空间
|
|
71
|
+
border: InputBorder.none,
|
|
72
|
+
enabledBorder: InputBorder.none,
|
|
73
|
+
focusedBorder: InputBorder.none,
|
|
74
|
+
),
|
|
75
|
+
),
|
|
76
|
+
);
|
|
77
|
+
}
|
|
78
|
+
```
|
|
79
|
+
|
|
80
|
+
### 关键原则
|
|
81
|
+
|
|
82
|
+
| 属性 | 作用 | 注意事项 |
|
|
83
|
+
|------|------|----------|
|
|
84
|
+
| `Container.alignment: Alignment.center` | 让 TextField 整体在容器中居中 | 必须配合 `contentPadding: EdgeInsets.zero` |
|
|
85
|
+
| `style.height` 和 `hintStyle.height` | 控制行高 | **必须相同**,否则 placeholder 和输入内容位置不一致 |
|
|
86
|
+
| `contentPadding: EdgeInsets.zero` | 清除默认间距 | 让 Container.alignment 生效 |
|
|
87
|
+
| `isDense: true` | 移除 TextField 默认空间 | 减少干扰因素 |
|
|
88
|
+
| `textAlignVertical` | **不推荐使用** | 只影响输入内容,不影响 placeholder |
|
|
89
|
+
|
|
90
|
+
---
|
|
91
|
+
|
|
92
|
+
## 📌 简版纠错指南
|
|
93
|
+
|
|
94
|
+
### TextField 垂直居中三步法
|
|
95
|
+
|
|
96
|
+
```dart
|
|
97
|
+
// Step 1: Container 提供高度和居中
|
|
98
|
+
Container(
|
|
99
|
+
height: 36,
|
|
100
|
+
alignment: Alignment.center,
|
|
101
|
+
child: TextField(
|
|
102
|
+
// Step 2: style 和 hintStyle 使用相同的 height
|
|
103
|
+
style: TextStyle(fontSize: 14, height: 1.43),
|
|
104
|
+
decoration: InputDecoration(
|
|
105
|
+
hintStyle: TextStyle(fontSize: 14, height: 1.43),
|
|
106
|
+
// Step 3: 清除 padding,启用 isDense
|
|
107
|
+
contentPadding: EdgeInsets.zero,
|
|
108
|
+
isDense: true,
|
|
109
|
+
),
|
|
110
|
+
),
|
|
111
|
+
)
|
|
112
|
+
```
|
|
113
|
+
|
|
114
|
+
### 禁止事项
|
|
115
|
+
|
|
116
|
+
1. ❌ **禁止 style.height 和 hintStyle.height 不一致** - 会导致 placeholder 和输入内容位置不同
|
|
117
|
+
2. ❌ **禁止同时使用 textAlignVertical 和 height** - 会产生冲突
|
|
118
|
+
3. ❌ **禁止 strutStyle + forceStrutHeight** - 可能压缩文字
|
|
119
|
+
4. ❌ **禁止反复调整 contentPadding 试错** - 先确定行高配置
|
|
120
|
+
|
|
121
|
+
### 调试顺序
|
|
122
|
+
|
|
123
|
+
遇到 TextField 垂直对齐问题时,按以下顺序检查:
|
|
124
|
+
|
|
125
|
+
1. **先确认设计规格** - 容器高度、文本高度、计算行高倍数
|
|
126
|
+
2. **配置 height** - style 和 hintStyle 使用相同的 height
|
|
127
|
+
3. **配置 Container** - alignment: Alignment.center
|
|
128
|
+
4. **清除干扰** - contentPadding: EdgeInsets.zero, isDense: true
|
|
129
|
+
5. **移除冲突属性** - 不要用 textAlignVertical 和 strutStyle
|
|
130
|
+
|
|
131
|
+
---
|
|
132
|
+
|
|
133
|
+
## 🎯 案例复盘
|
|
134
|
+
|
|
135
|
+
### 为什么需要 15+ 轮对话?
|
|
136
|
+
|
|
137
|
+
| 问题 | 正确做法 |
|
|
138
|
+
|------|----------|
|
|
139
|
+
| 没有先计算行高倍数 | 从设计稿提取:文本高度 ÷ 字号 = height |
|
|
140
|
+
| 逐个尝试属性 | 一次性配置完整方案 |
|
|
141
|
+
| 改了 A 引发 B | 理解属性之间的关系 |
|
|
142
|
+
| 没有统一 style 和 hintStyle | 两者必须完全一致 |
|
|
143
|
+
|
|
144
|
+
### 正确的工作流
|
|
145
|
+
|
|
146
|
+
```
|
|
147
|
+
1. 从 Sketch 获取完整规格(容器高度、文本 Y 坐标、文本高度)
|
|
148
|
+
2. 计算 height = 文本高度 ÷ 字号
|
|
149
|
+
3. 一次性配置完整方案(Container.alignment + 相同 height + contentPadding.zero + isDense)
|
|
150
|
+
4. 验证三者(placeholder、光标、输入内容)是否对齐
|
|
151
|
+
```
|
|
152
|
+
|
|
153
|
+
---
|
|
154
|
+
|
|
155
|
+
**创建日期**: 2026-01-19
|
|
156
|
+
**案例来源**: 汇款记录筛选弹窗金额输入框
|
|
157
|
+
**解决方案**: Container.alignment + 统一 height + contentPadding.zero
|