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,244 @@
|
|
|
1
|
+
# Flutter clipBehavior 阴影裁剪问题
|
|
2
|
+
|
|
3
|
+
> **问题标签**: `clip`, `shadow`, `layout`, `overflow`, `neumorphism`
|
|
4
|
+
> **问题类型**: 布局裁剪
|
|
5
|
+
> **框架**: Flutter
|
|
6
|
+
> **严重程度**: 中等(视觉问题)
|
|
7
|
+
|
|
8
|
+
---
|
|
9
|
+
|
|
10
|
+
## 🔍 问题识别
|
|
11
|
+
|
|
12
|
+
### 自动检测特征
|
|
13
|
+
|
|
14
|
+
```dart
|
|
15
|
+
// 代码模式匹配 - 嵌套容器带阴影
|
|
16
|
+
Container(
|
|
17
|
+
// 外层容器有 padding
|
|
18
|
+
padding: EdgeInsets.all(6),
|
|
19
|
+
child: Container(
|
|
20
|
+
decoration: BoxDecoration(
|
|
21
|
+
boxShadow: [...], // 内层有阴影
|
|
22
|
+
),
|
|
23
|
+
),
|
|
24
|
+
)
|
|
25
|
+
```
|
|
26
|
+
|
|
27
|
+
### 用户描述关键词
|
|
28
|
+
- "阴影模糊一片"
|
|
29
|
+
- "阴影没有清晰边界"
|
|
30
|
+
- "阴影被裁剪了"
|
|
31
|
+
- "新拟态效果不清晰"
|
|
32
|
+
- "选中项看不出凸起效果"
|
|
33
|
+
|
|
34
|
+
### 问题特征
|
|
35
|
+
- [ ] 新拟态选中项阴影看起来"模糊一片"
|
|
36
|
+
- [ ] 阴影没有清晰的边界
|
|
37
|
+
- [ ] 选中项与背景融为一体
|
|
38
|
+
- [ ] 类似效果的其他组件显示正常
|
|
39
|
+
|
|
40
|
+
---
|
|
41
|
+
|
|
42
|
+
## ❌ 常见错误排查路线(避免重复)
|
|
43
|
+
|
|
44
|
+
| 尝试方向 | 为什么无效 | 浪费时间 |
|
|
45
|
+
|----------|-----------|---------|
|
|
46
|
+
| 调整阴影参数 (blur/offset) | 问题不在阴影本身 | 2-3 轮对话 |
|
|
47
|
+
| 嵌套两层 Container (外层阴影/内层渐变) | 阴影仍在裁剪区域内 | 2-3 轮对话 |
|
|
48
|
+
| 添加白色边框产生锐利边缘 | 边框过于明显,产生分层感 | 1-2 轮对话 |
|
|
49
|
+
| 使用叠加渐变模拟内阴影 | 效果不自然 | 1-2 轮对话 |
|
|
50
|
+
| 只设置 Stack 的 clipBehavior | 外层 Container 仍会裁剪 | 1 轮对话 |
|
|
51
|
+
|
|
52
|
+
**总计浪费**: 7-11 轮对话
|
|
53
|
+
|
|
54
|
+
---
|
|
55
|
+
|
|
56
|
+
## ✅ 正确解决方案
|
|
57
|
+
|
|
58
|
+
### 核心原理
|
|
59
|
+
|
|
60
|
+
**问题根源**:Flutter 的 `Container`、`Padding`、`ClipRRect` 等组件**默认会裁剪**超出边界的内容,包括 `BoxShadow` 阴影。
|
|
61
|
+
|
|
62
|
+
```
|
|
63
|
+
问题结构:
|
|
64
|
+
Container (默认 clipBehavior: Clip.hardEdge)
|
|
65
|
+
└── Padding (padding: 6)
|
|
66
|
+
└── Container
|
|
67
|
+
└── 带阴影的滑块 ← 阴影被最外层 Container 裁剪!
|
|
68
|
+
```
|
|
69
|
+
|
|
70
|
+
### 解决步骤
|
|
71
|
+
|
|
72
|
+
#### 1. 设置 clipBehavior: Clip.none
|
|
73
|
+
|
|
74
|
+
```dart
|
|
75
|
+
Container(
|
|
76
|
+
clipBehavior: Clip.none, // ← 关键!允许内容溢出
|
|
77
|
+
decoration: BoxDecoration(
|
|
78
|
+
color: config.backgroundColor,
|
|
79
|
+
borderRadius: BorderRadius.circular(config.borderRadius),
|
|
80
|
+
),
|
|
81
|
+
child: Stack(
|
|
82
|
+
clipBehavior: Clip.none, // ← Stack 也要设置
|
|
83
|
+
children: [
|
|
84
|
+
// ...
|
|
85
|
+
],
|
|
86
|
+
),
|
|
87
|
+
)
|
|
88
|
+
```
|
|
89
|
+
|
|
90
|
+
#### 2. 重构布局结构
|
|
91
|
+
|
|
92
|
+
**错误的结构**(会裁剪):
|
|
93
|
+
```dart
|
|
94
|
+
Container(
|
|
95
|
+
padding: EdgeInsets.all(6), // padding 会导致裁剪
|
|
96
|
+
child: Stack(
|
|
97
|
+
children: [
|
|
98
|
+
Container(boxShadow: [...]), // 阴影被外层裁剪
|
|
99
|
+
],
|
|
100
|
+
),
|
|
101
|
+
)
|
|
102
|
+
```
|
|
103
|
+
|
|
104
|
+
**正确的结构**(不裁剪):
|
|
105
|
+
```dart
|
|
106
|
+
Container(
|
|
107
|
+
clipBehavior: Clip.none,
|
|
108
|
+
child: Stack(
|
|
109
|
+
clipBehavior: Clip.none,
|
|
110
|
+
children: [
|
|
111
|
+
Positioned( // 使用 Positioned 代替 padding
|
|
112
|
+
left: 6,
|
|
113
|
+
top: 6,
|
|
114
|
+
bottom: 6,
|
|
115
|
+
right: 6,
|
|
116
|
+
child: Container(boxShadow: [...]),
|
|
117
|
+
),
|
|
118
|
+
],
|
|
119
|
+
),
|
|
120
|
+
)
|
|
121
|
+
```
|
|
122
|
+
|
|
123
|
+
#### 3. 完整示例
|
|
124
|
+
|
|
125
|
+
```dart
|
|
126
|
+
@override
|
|
127
|
+
Widget build(BuildContext context) {
|
|
128
|
+
final config = _getConfig();
|
|
129
|
+
final selectedIndex = items.indexWhere((item) => item.value == selectedValue);
|
|
130
|
+
final innerPadding = padding ?? const EdgeInsets.all(6);
|
|
131
|
+
|
|
132
|
+
return LayoutBuilder(
|
|
133
|
+
builder: (context, outerConstraints) {
|
|
134
|
+
final totalWidth = outerConstraints.maxWidth;
|
|
135
|
+
final innerWidth = totalWidth - innerPadding.horizontal;
|
|
136
|
+
final itemWidth = innerWidth / items.length;
|
|
137
|
+
|
|
138
|
+
return Container(
|
|
139
|
+
height: height,
|
|
140
|
+
clipBehavior: Clip.none, // 关键点1:允许溢出
|
|
141
|
+
decoration: BoxDecoration(
|
|
142
|
+
color: config.backgroundColor,
|
|
143
|
+
borderRadius: BorderRadius.circular(config.borderRadius),
|
|
144
|
+
boxShadow: config.shadow,
|
|
145
|
+
),
|
|
146
|
+
child: Stack(
|
|
147
|
+
clipBehavior: Clip.none, // 关键点2:Stack 也允许溢出
|
|
148
|
+
children: [
|
|
149
|
+
// 滑动背景 - 使用 Positioned 精确定位
|
|
150
|
+
if (selectedIndex != -1)
|
|
151
|
+
Positioned(
|
|
152
|
+
left: innerPadding.left + (itemWidth * selectedIndex),
|
|
153
|
+
top: innerPadding.top,
|
|
154
|
+
bottom: innerPadding.bottom,
|
|
155
|
+
width: itemWidth,
|
|
156
|
+
child: AnimatedContainer(
|
|
157
|
+
duration: $d.fast,
|
|
158
|
+
curve: Curves.easeInOut,
|
|
159
|
+
decoration: BoxDecoration(
|
|
160
|
+
gradient: config.selectedGradient,
|
|
161
|
+
borderRadius: BorderRadius.circular(12),
|
|
162
|
+
boxShadow: config.selectedShadow, // 阴影在这里
|
|
163
|
+
),
|
|
164
|
+
),
|
|
165
|
+
),
|
|
166
|
+
|
|
167
|
+
// 文本层 - 使用 Padding 而非容器的 padding
|
|
168
|
+
Padding(
|
|
169
|
+
padding: innerPadding,
|
|
170
|
+
child: Row(
|
|
171
|
+
children: items.map((item) {
|
|
172
|
+
return Expanded(
|
|
173
|
+
child: GestureDetector(
|
|
174
|
+
onTap: () => onChanged(item.value),
|
|
175
|
+
child: Container(
|
|
176
|
+
alignment: Alignment.center,
|
|
177
|
+
child: Text(item.label, ...),
|
|
178
|
+
),
|
|
179
|
+
),
|
|
180
|
+
);
|
|
181
|
+
}).toList(),
|
|
182
|
+
),
|
|
183
|
+
),
|
|
184
|
+
],
|
|
185
|
+
),
|
|
186
|
+
);
|
|
187
|
+
},
|
|
188
|
+
);
|
|
189
|
+
}
|
|
190
|
+
```
|
|
191
|
+
|
|
192
|
+
---
|
|
193
|
+
|
|
194
|
+
## 📋 关键改动点
|
|
195
|
+
|
|
196
|
+
### 1. clipBehavior 设置
|
|
197
|
+
- ✅ Container: `clipBehavior: Clip.none`
|
|
198
|
+
- ✅ Stack: `clipBehavior: Clip.none`
|
|
199
|
+
- ✅ 所有可能裁剪的父组件都要设置
|
|
200
|
+
|
|
201
|
+
### 2. 布局方式
|
|
202
|
+
- ❌ Container 的 padding 属性
|
|
203
|
+
- ✅ Positioned 精确定位
|
|
204
|
+
- ✅ Padding widget 包裹内容
|
|
205
|
+
|
|
206
|
+
### 3. 使用 LayoutBuilder
|
|
207
|
+
提前计算尺寸,避免布局错误:
|
|
208
|
+
```dart
|
|
209
|
+
LayoutBuilder(
|
|
210
|
+
builder: (context, constraints) {
|
|
211
|
+
final totalWidth = constraints.maxWidth;
|
|
212
|
+
final innerWidth = totalWidth - padding.horizontal;
|
|
213
|
+
final itemWidth = innerWidth / items.length;
|
|
214
|
+
// ...
|
|
215
|
+
},
|
|
216
|
+
)
|
|
217
|
+
```
|
|
218
|
+
|
|
219
|
+
---
|
|
220
|
+
|
|
221
|
+
## 💡 clipBehavior 值说明
|
|
222
|
+
|
|
223
|
+
| 值 | 行为 | 性能 | 使用场景 |
|
|
224
|
+
|----|------|------|---------|
|
|
225
|
+
| `Clip.none` | 不裁剪,允许溢出 | 最好 | **需要显示阴影** |
|
|
226
|
+
| `Clip.hardEdge` | 硬边裁剪(默认) | 好 | 一般容器 |
|
|
227
|
+
| `Clip.antiAlias` | 抗锯齿裁剪 | 中 | 圆角需要平滑 |
|
|
228
|
+
| `Clip.antiAliasWithSaveLayer` | 最高质量裁剪 | 差 | 尽量避免 |
|
|
229
|
+
|
|
230
|
+
**推荐**:需要阴影时使用 `Clip.none`,其他情况保持默认。
|
|
231
|
+
|
|
232
|
+
---
|
|
233
|
+
|
|
234
|
+
## 🔗 相关案例
|
|
235
|
+
|
|
236
|
+
- [shadow-透出问题](./shadow-透出问题.md) - 另一个阴影相关问题
|
|
237
|
+
- [layout-尺寸不匹配](./layout-尺寸不匹配.md) - 使用 Positioned 精确布局
|
|
238
|
+
|
|
239
|
+
---
|
|
240
|
+
|
|
241
|
+
**来源**: my_flutter 项目实战经验
|
|
242
|
+
**创建日期**: 2025-12-31
|
|
243
|
+
**最后验证**: 2026-01-16
|
|
244
|
+
**节省时间**: 7-11 轮对话
|
|
@@ -0,0 +1,240 @@
|
|
|
1
|
+
# Flutter 组件字段缺失问题
|
|
2
|
+
|
|
3
|
+
> **问题标签**: `component`, `props`, `missing-field`, `custom-widget`
|
|
4
|
+
> **问题类型**: 组件配置
|
|
5
|
+
> **框架**: Flutter
|
|
6
|
+
> **严重程度**: 低(编译错误,容易发现)
|
|
7
|
+
|
|
8
|
+
---
|
|
9
|
+
|
|
10
|
+
## 🔍 问题识别
|
|
11
|
+
|
|
12
|
+
### 自动检测特征
|
|
13
|
+
|
|
14
|
+
```dart
|
|
15
|
+
// 编译错误信息
|
|
16
|
+
The named parameter 'showBorder' isn't defined
|
|
17
|
+
The named parameter 'shadow' isn't defined
|
|
18
|
+
```
|
|
19
|
+
|
|
20
|
+
### 用户描述关键词
|
|
21
|
+
- "组件不支持这个属性"
|
|
22
|
+
- "字段未定义"
|
|
23
|
+
- "参数不存在"
|
|
24
|
+
- "自定义组件缺少配置"
|
|
25
|
+
|
|
26
|
+
### 问题特征
|
|
27
|
+
- [ ] 编译错误:`The named parameter 'xxx' isn't defined`
|
|
28
|
+
- [ ] 自定义组件无法接收某些属性
|
|
29
|
+
- [ ] IDE 提示参数不存在
|
|
30
|
+
- [ ] 想配置组件样式但没有对应的属性
|
|
31
|
+
|
|
32
|
+
---
|
|
33
|
+
|
|
34
|
+
## ❌ 常见错误排查路线(避免重复)
|
|
35
|
+
|
|
36
|
+
| 尝试方向 | 为什么无效 | 浪费时间 |
|
|
37
|
+
|----------|-----------|---------|
|
|
38
|
+
| 直接在使用处添加样式 | 破坏组件封装性 | 1 轮对话 |
|
|
39
|
+
| 修改组件内部硬编码 | 失去可配置性 | 1-2 轮对话 |
|
|
40
|
+
| 复制组件代码到页面 | 违反 DRY 原则 | 1 轮对话 |
|
|
41
|
+
|
|
42
|
+
**总计浪费**: 2-4 轮对话
|
|
43
|
+
|
|
44
|
+
---
|
|
45
|
+
|
|
46
|
+
## ✅ 正确解决方案
|
|
47
|
+
|
|
48
|
+
### 核心原理
|
|
49
|
+
|
|
50
|
+
**问题根源**:自定义组件(如 `FlexInput`)在设计时未预留足够的可配置字段。
|
|
51
|
+
|
|
52
|
+
### 解决步骤
|
|
53
|
+
|
|
54
|
+
#### 1. 检查组件定义
|
|
55
|
+
|
|
56
|
+
找到组件源文件(如 `lib/core/themes/flex_widgets.dart`)
|
|
57
|
+
|
|
58
|
+
#### 2. 补充缺失字段
|
|
59
|
+
|
|
60
|
+
```dart
|
|
61
|
+
class FlexInput extends StatelessWidget {
|
|
62
|
+
const FlexInput({
|
|
63
|
+
super.key,
|
|
64
|
+
// ... 已有字段
|
|
65
|
+
this.controller,
|
|
66
|
+
this.hintText,
|
|
67
|
+
this.inputType = TextInputType.text,
|
|
68
|
+
|
|
69
|
+
// ← 新增字段
|
|
70
|
+
this.showBorder = true,
|
|
71
|
+
this.shadow,
|
|
72
|
+
this.borderColor,
|
|
73
|
+
this.borderWidth = 1.0,
|
|
74
|
+
});
|
|
75
|
+
|
|
76
|
+
// 已有属性
|
|
77
|
+
final TextEditingController? controller;
|
|
78
|
+
final String? hintText;
|
|
79
|
+
final TextInputType inputType;
|
|
80
|
+
|
|
81
|
+
// ← 新增属性
|
|
82
|
+
final bool showBorder;
|
|
83
|
+
final List<BoxShadow>? shadow;
|
|
84
|
+
final Color? borderColor;
|
|
85
|
+
final double borderWidth;
|
|
86
|
+
|
|
87
|
+
@override
|
|
88
|
+
Widget build(BuildContext context) {
|
|
89
|
+
return Container(
|
|
90
|
+
decoration: BoxDecoration(
|
|
91
|
+
// ← 使用新增的属性
|
|
92
|
+
border: showBorder
|
|
93
|
+
? Border.all(
|
|
94
|
+
color: borderColor ?? $c.border,
|
|
95
|
+
width: borderWidth,
|
|
96
|
+
)
|
|
97
|
+
: null,
|
|
98
|
+
boxShadow: shadow,
|
|
99
|
+
// ... 其他样式
|
|
100
|
+
),
|
|
101
|
+
child: TextField(
|
|
102
|
+
controller: controller,
|
|
103
|
+
decoration: InputDecoration(
|
|
104
|
+
hintText: hintText,
|
|
105
|
+
// ...
|
|
106
|
+
),
|
|
107
|
+
),
|
|
108
|
+
);
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
```
|
|
112
|
+
|
|
113
|
+
#### 3. 使用新字段
|
|
114
|
+
|
|
115
|
+
```dart
|
|
116
|
+
// 现在可以配置了
|
|
117
|
+
FlexInput(
|
|
118
|
+
hintText: 'Enter phone',
|
|
119
|
+
showBorder: false, // ← 新增
|
|
120
|
+
shadow: [ // ← 新增
|
|
121
|
+
BoxShadow(
|
|
122
|
+
color: Color(0x0D000000),
|
|
123
|
+
blurRadius: 3,
|
|
124
|
+
offset: Offset(0, 1),
|
|
125
|
+
),
|
|
126
|
+
],
|
|
127
|
+
)
|
|
128
|
+
```
|
|
129
|
+
|
|
130
|
+
---
|
|
131
|
+
|
|
132
|
+
## 📋 组件设计最佳实践
|
|
133
|
+
|
|
134
|
+
### 必须预留的常用字段
|
|
135
|
+
|
|
136
|
+
#### 样式相关
|
|
137
|
+
```dart
|
|
138
|
+
// 边框
|
|
139
|
+
final bool showBorder;
|
|
140
|
+
final Color? borderColor;
|
|
141
|
+
final double borderWidth;
|
|
142
|
+
final BorderRadius? borderRadius;
|
|
143
|
+
|
|
144
|
+
// 阴影
|
|
145
|
+
final List<BoxShadow>? shadow;
|
|
146
|
+
|
|
147
|
+
// 背景
|
|
148
|
+
final Color? backgroundColor;
|
|
149
|
+
final Gradient? gradient;
|
|
150
|
+
|
|
151
|
+
// 尺寸
|
|
152
|
+
final double? width;
|
|
153
|
+
final double? height;
|
|
154
|
+
final EdgeInsets? padding;
|
|
155
|
+
final EdgeInsets? margin;
|
|
156
|
+
```
|
|
157
|
+
|
|
158
|
+
#### 交互相关
|
|
159
|
+
```dart
|
|
160
|
+
// 状态
|
|
161
|
+
final bool enabled;
|
|
162
|
+
final bool readOnly;
|
|
163
|
+
|
|
164
|
+
// 回调
|
|
165
|
+
final VoidCallback? onTap;
|
|
166
|
+
final ValueChanged<String>? onChanged;
|
|
167
|
+
final VoidCallback? onSubmitted;
|
|
168
|
+
|
|
169
|
+
// 验证
|
|
170
|
+
final String? Function(String?)? validator;
|
|
171
|
+
final bool showError;
|
|
172
|
+
```
|
|
173
|
+
|
|
174
|
+
### 组件设计检查清单
|
|
175
|
+
|
|
176
|
+
- [ ] 是否支持自定义颜色?
|
|
177
|
+
- [ ] 是否支持自定义尺寸?
|
|
178
|
+
- [ ] 是否支持显示/隐藏边框?
|
|
179
|
+
- [ ] 是否支持阴影配置?
|
|
180
|
+
- [ ] 是否支持禁用/只读状态?
|
|
181
|
+
- [ ] 回调函数是否足够?
|
|
182
|
+
|
|
183
|
+
---
|
|
184
|
+
|
|
185
|
+
## 💡 预防措施
|
|
186
|
+
|
|
187
|
+
### 1. 使用 Token 系统
|
|
188
|
+
|
|
189
|
+
```dart
|
|
190
|
+
// ✅ 推荐:使用 Token 作为默认值
|
|
191
|
+
final Color borderColor;
|
|
192
|
+
|
|
193
|
+
FlexInput({
|
|
194
|
+
this.borderColor = $c.border, // Token 默认值
|
|
195
|
+
})
|
|
196
|
+
|
|
197
|
+
// ❌ 避免:硬编码
|
|
198
|
+
final Color borderColor = Color(0xFFDDDDDD);
|
|
199
|
+
```
|
|
200
|
+
|
|
201
|
+
### 2. 参考 Material/Cupertino 组件
|
|
202
|
+
|
|
203
|
+
Flutter 官方组件的属性设计很完善,可以参考:
|
|
204
|
+
|
|
205
|
+
```dart
|
|
206
|
+
// TextField 的部分属性
|
|
207
|
+
TextField(
|
|
208
|
+
decoration: InputDecoration(
|
|
209
|
+
border: ...,
|
|
210
|
+
enabledBorder: ...,
|
|
211
|
+
focusedBorder: ...,
|
|
212
|
+
filled: ...,
|
|
213
|
+
fillColor: ...,
|
|
214
|
+
),
|
|
215
|
+
style: ...,
|
|
216
|
+
enabled: ...,
|
|
217
|
+
readOnly: ...,
|
|
218
|
+
)
|
|
219
|
+
```
|
|
220
|
+
|
|
221
|
+
### 3. 渐进式添加属性
|
|
222
|
+
|
|
223
|
+
**不要一开始就添加所有属性**,遵循 YAGNI 原则:
|
|
224
|
+
1. 先实现基础功能
|
|
225
|
+
2. 使用时发现缺少属性
|
|
226
|
+
3. 及时补充属性
|
|
227
|
+
|
|
228
|
+
---
|
|
229
|
+
|
|
230
|
+
## 🔗 相关案例
|
|
231
|
+
|
|
232
|
+
- [layout-尺寸不匹配](./layout-尺寸不匹配.md) - 组件的精确尺寸配置
|
|
233
|
+
- [shadow-透出问题](./shadow-透出问题.md) - CustomPainter 组件的属性设计
|
|
234
|
+
|
|
235
|
+
---
|
|
236
|
+
|
|
237
|
+
**来源**: my_flutter 项目实战经验
|
|
238
|
+
**创建日期**: 2025-12-31
|
|
239
|
+
**最后验证**: 2026-01-16
|
|
240
|
+
**节省时间**: 2-4 轮对话
|
|
@@ -0,0 +1,236 @@
|
|
|
1
|
+
# Flutter TextField 边框和背景问题
|
|
2
|
+
|
|
3
|
+
> **问题标签**: `textfield`, `input`, `border`, `focus`, `theme`
|
|
4
|
+
> **问题类型**: 主题冲突
|
|
5
|
+
> **框架**: Flutter
|
|
6
|
+
> **严重程度**: 中等
|
|
7
|
+
|
|
8
|
+
---
|
|
9
|
+
|
|
10
|
+
## 🔍 问题识别
|
|
11
|
+
|
|
12
|
+
### 自动检测特征
|
|
13
|
+
|
|
14
|
+
```dart
|
|
15
|
+
// 只设置了 border 但没设置其他状态
|
|
16
|
+
TextField(
|
|
17
|
+
decoration: InputDecoration(
|
|
18
|
+
border: InputBorder.none, // 不够!
|
|
19
|
+
),
|
|
20
|
+
)
|
|
21
|
+
```
|
|
22
|
+
|
|
23
|
+
### 用户描述关键词
|
|
24
|
+
- "TextField 聚焦时出现蓝色边框"
|
|
25
|
+
- "输入框背景颜色变了"
|
|
26
|
+
- "只设置了 border: InputBorder.none 但还有边框"
|
|
27
|
+
- "新拟态输入框样式被覆盖"
|
|
28
|
+
|
|
29
|
+
### 问题特征
|
|
30
|
+
- [ ] TextField 聚焦时出现蓝色边框
|
|
31
|
+
- [ ] TextField 聚焦时背景颜色变化
|
|
32
|
+
- [ ] 只设置了 `border: InputBorder.none` 但边框仍存在
|
|
33
|
+
- [ ] 新拟态输入框样式被全局主题覆盖
|
|
34
|
+
|
|
35
|
+
---
|
|
36
|
+
|
|
37
|
+
## ❌ 常见错误排查路线(避免重复)
|
|
38
|
+
|
|
39
|
+
| 尝试方向 | 为什么无效 | 浪费时间 |
|
|
40
|
+
|----------|-----------|---------|
|
|
41
|
+
| 只设置 `border: InputBorder.none` | 不覆盖 focusedBorder 等状态 | 1-2 轮对话 |
|
|
42
|
+
| 组件级覆盖但不完整 | 全局主题的其他状态仍生效 | 2-3 轮对话 |
|
|
43
|
+
| 修改颜色为透明 | 边框仍存在,只是看不见 | 1 轮对话 |
|
|
44
|
+
| 不知道是全局主题的问题 | 反复修改组件级样式 | 2-3 轮对话 |
|
|
45
|
+
|
|
46
|
+
**总计浪费**: 6-9 轮对话
|
|
47
|
+
|
|
48
|
+
---
|
|
49
|
+
|
|
50
|
+
## ✅ 正确解决方案
|
|
51
|
+
|
|
52
|
+
### 核心原理
|
|
53
|
+
|
|
54
|
+
#### 原因1: Flutter InputDecoration 边框机制
|
|
55
|
+
|
|
56
|
+
Flutter 的 `InputDecoration.border` 只是**默认值**,各状态有独立的边框属性:
|
|
57
|
+
- `enabledBorder` - 启用状态
|
|
58
|
+
- `focusedBorder` - 聚焦状态 ⚠️ **最常见问题**
|
|
59
|
+
- `disabledBorder` - 禁用状态
|
|
60
|
+
- `errorBorder` - 错误状态
|
|
61
|
+
- `focusedErrorBorder` - 聚焦+错误状态
|
|
62
|
+
|
|
63
|
+
**只设置 `border` 不会覆盖其他状态的边框!**
|
|
64
|
+
|
|
65
|
+
#### 原因2: 全局主题干扰
|
|
66
|
+
|
|
67
|
+
`ThemeData.inputDecorationTheme` 会注入默认样式:
|
|
68
|
+
|
|
69
|
+
```dart
|
|
70
|
+
// 全局主题中的问题配置
|
|
71
|
+
inputDecorationTheme: InputDecorationTheme(
|
|
72
|
+
focusedBorder: OutlineInputBorder(
|
|
73
|
+
borderSide: BorderSide(color: Colors.blue, width: 2), // 蓝框来源!
|
|
74
|
+
),
|
|
75
|
+
filled: true, // 导致背景变色!
|
|
76
|
+
),
|
|
77
|
+
```
|
|
78
|
+
|
|
79
|
+
### 解决步骤
|
|
80
|
+
|
|
81
|
+
#### 方案1: 全局主题移除边框(推荐)
|
|
82
|
+
|
|
83
|
+
修改 `lib/core/themes/app_theme.dart` 和 `theme_manager.dart`:
|
|
84
|
+
|
|
85
|
+
```dart
|
|
86
|
+
ThemeData(
|
|
87
|
+
// ... 其他配置
|
|
88
|
+
inputDecorationTheme: InputDecorationTheme(
|
|
89
|
+
filled: true,
|
|
90
|
+
fillColor: Colors.transparent, // 透明填充
|
|
91
|
+
|
|
92
|
+
// ← 关键:所有状态都设为无边框
|
|
93
|
+
border: InputBorder.none,
|
|
94
|
+
enabledBorder: InputBorder.none,
|
|
95
|
+
focusedBorder: InputBorder.none,
|
|
96
|
+
disabledBorder: InputBorder.none,
|
|
97
|
+
errorBorder: InputBorder.none,
|
|
98
|
+
focusedErrorBorder: InputBorder.none,
|
|
99
|
+
|
|
100
|
+
// 其他样式...
|
|
101
|
+
isDense: true,
|
|
102
|
+
contentPadding: EdgeInsets.symmetric(horizontal: 12, vertical: 8),
|
|
103
|
+
),
|
|
104
|
+
)
|
|
105
|
+
```
|
|
106
|
+
|
|
107
|
+
#### 方案2: 组件级完整覆盖
|
|
108
|
+
|
|
109
|
+
在 `TextField` 或自定义输入组件中:
|
|
110
|
+
|
|
111
|
+
```dart
|
|
112
|
+
TextField(
|
|
113
|
+
decoration: InputDecoration(
|
|
114
|
+
// 禁用填充背景
|
|
115
|
+
filled: false,
|
|
116
|
+
fillColor: Colors.transparent,
|
|
117
|
+
|
|
118
|
+
// ← 关键:移除所有状态下的边框
|
|
119
|
+
border: InputBorder.none,
|
|
120
|
+
enabledBorder: InputBorder.none,
|
|
121
|
+
focusedBorder: InputBorder.none, // 必须!
|
|
122
|
+
disabledBorder: InputBorder.none,
|
|
123
|
+
errorBorder: InputBorder.none,
|
|
124
|
+
focusedErrorBorder: InputBorder.none,
|
|
125
|
+
|
|
126
|
+
// 紧凑布局
|
|
127
|
+
isDense: true,
|
|
128
|
+
contentPadding: EdgeInsets.zero,
|
|
129
|
+
|
|
130
|
+
// 提示文本
|
|
131
|
+
hintText: '请输入',
|
|
132
|
+
hintStyle: TextStyle(color: Colors.grey),
|
|
133
|
+
),
|
|
134
|
+
)
|
|
135
|
+
```
|
|
136
|
+
|
|
137
|
+
---
|
|
138
|
+
|
|
139
|
+
## 📋 完整修复示例
|
|
140
|
+
|
|
141
|
+
### 全局主题配置
|
|
142
|
+
|
|
143
|
+
```dart
|
|
144
|
+
// lib/core/themes/app_theme.dart
|
|
145
|
+
class AppTheme {
|
|
146
|
+
static ThemeData lightTheme = ThemeData(
|
|
147
|
+
// ... 其他配置
|
|
148
|
+
|
|
149
|
+
inputDecorationTheme: const InputDecorationTheme(
|
|
150
|
+
// 禁用填充背景,避免 focus 时背景色变化
|
|
151
|
+
filled: false,
|
|
152
|
+
fillColor: Colors.transparent,
|
|
153
|
+
|
|
154
|
+
// 移除所有状态下的边框
|
|
155
|
+
border: InputBorder.none,
|
|
156
|
+
enabledBorder: InputBorder.none,
|
|
157
|
+
focusedBorder: InputBorder.none,
|
|
158
|
+
disabledBorder: InputBorder.none,
|
|
159
|
+
errorBorder: InputBorder.none,
|
|
160
|
+
focusedErrorBorder: InputBorder.none,
|
|
161
|
+
|
|
162
|
+
// 紧凑布局
|
|
163
|
+
isDense: true,
|
|
164
|
+
contentPadding: EdgeInsets.symmetric(horizontal: 12, vertical: 8),
|
|
165
|
+
),
|
|
166
|
+
);
|
|
167
|
+
}
|
|
168
|
+
```
|
|
169
|
+
|
|
170
|
+
### 自定义输入组件
|
|
171
|
+
|
|
172
|
+
```dart
|
|
173
|
+
class AppInputSection extends StatelessWidget {
|
|
174
|
+
Widget _buildTextField() {
|
|
175
|
+
return TextField(
|
|
176
|
+
controller: controller,
|
|
177
|
+
style: TextStyle(
|
|
178
|
+
fontSize: 32,
|
|
179
|
+
fontWeight: FontWeight.w800,
|
|
180
|
+
color: AppColors.textDark,
|
|
181
|
+
),
|
|
182
|
+
decoration: InputDecoration(
|
|
183
|
+
// 双重保险:组件级也禁用
|
|
184
|
+
filled: false,
|
|
185
|
+
fillColor: Colors.transparent,
|
|
186
|
+
|
|
187
|
+
// 完整覆盖所有边框状态
|
|
188
|
+
border: InputBorder.none,
|
|
189
|
+
enabledBorder: InputBorder.none,
|
|
190
|
+
focusedBorder: InputBorder.none,
|
|
191
|
+
disabledBorder: InputBorder.none,
|
|
192
|
+
errorBorder: InputBorder.none,
|
|
193
|
+
focusedErrorBorder: InputBorder.none,
|
|
194
|
+
|
|
195
|
+
// 紧凑布局
|
|
196
|
+
isDense: true,
|
|
197
|
+
contentPadding: EdgeInsets.zero,
|
|
198
|
+
|
|
199
|
+
// 提示文本
|
|
200
|
+
hintText: hint,
|
|
201
|
+
hintStyle: TextStyle(color: AppColors.textDarkTertiary),
|
|
202
|
+
),
|
|
203
|
+
onChanged: onChanged,
|
|
204
|
+
);
|
|
205
|
+
}
|
|
206
|
+
}
|
|
207
|
+
```
|
|
208
|
+
|
|
209
|
+
---
|
|
210
|
+
|
|
211
|
+
## 💡 边框状态完整对照表
|
|
212
|
+
|
|
213
|
+
| 状态 | 属性 | 何时生效 | 优先级 |
|
|
214
|
+
|------|------|---------|--------|
|
|
215
|
+
| 默认 | `border` | 其他状态未设置时 | 最低 |
|
|
216
|
+
| 启用 | `enabledBorder` | 输入框可用但未聚焦 | 中 |
|
|
217
|
+
| 聚焦 | `focusedBorder` | 输入框获得焦点 | **高** ⭐ |
|
|
218
|
+
| 禁用 | `disabledBorder` | 输入框被禁用 | 中 |
|
|
219
|
+
| 错误 | `errorBorder` | 验证失败 | 中 |
|
|
220
|
+
| 聚焦错误 | `focusedErrorBorder` | 验证失败且聚焦 | 高 |
|
|
221
|
+
|
|
222
|
+
**最常见的错位**:只设置 `border`,忽略了 `focusedBorder`!
|
|
223
|
+
|
|
224
|
+
---
|
|
225
|
+
|
|
226
|
+
## 🔗 相关案例
|
|
227
|
+
|
|
228
|
+
- [shadow-透出问题](./shadow-透出问题.md) - 新拟态输入框的阴影实现
|
|
229
|
+
- [layout-尺寸不匹配](./layout-尺寸不匹配.md) - 输入框精确布局
|
|
230
|
+
|
|
231
|
+
---
|
|
232
|
+
|
|
233
|
+
**来源**: my_flutter 项目实战经验
|
|
234
|
+
**创建日期**: 2025-12-31
|
|
235
|
+
**最后验证**: 2026-01-16
|
|
236
|
+
**节省时间**: 6-9 轮对话
|