Files
App.K12.Student.Miniapp_Flu…/docs/demos/wrap_demo.md
T
2026-09-04 15:22:20 +08:00

145 lines
5.1 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 【Demo】Wrap 自动换行布局 + Chip 标签家族
> 源文件:`lib/pages/demo/wrap_demo.dart` | 分类:基础布局
> 一句话:Row 一行排不下就溢出报错,Wrap 会自动换行 —— 做"标签流"必备。
---
## 一、这个 Demo 解决什么问题
"搜索历史标签"、"科目标签"、"技能标签"这类**数量不固定**的横向排列场景:
- `Row` 一行排不下会**直接溢出报错**(黄色条纹警告),不会换行;
- `Wrap` 空间不够时**自动换到下一行**,是标签流的正解。
本 Demo 还完整演示了 Flutter 的 **Chip 标签家族**:`Chip` / `ActionChip` / `FilterChip`,
以及一个**像素级可控的自绘标签 `CustomTag`**(设计稿要求精确高度/间距时的选择)。
---
## 二、怎么运行 / 演示内容
```dart
runApp(const MaterialApp(home: WrapDemoPage()));
```
页面是一个 Wrap,里面混排了 8 种 Chip 样式 + 自绘 CustomTag:
带图标、可删除、可点击、选中态、深色底、胶囊圆角……一屏看完所有变体。
---
## 三、核心知识点(新手向)
### 1. Wrap 的核心属性
```dart
Wrap(
direction: Axis.horizontal, // 排布方向(默认水平)
spacing: 24, // 同一行内 item 之间的水平间距
runSpacing: 20, // 行与行之间的垂直间距(run = "行/批")
children: [...],
)
```
### 2. 对比记忆
| 组件 | 行为 |
|---|---|
| `Row` | 横向排列,**溢出报错**,不换行 |
| `Wrap` | 横向排列,**空间不足自动换行** |
| `Column` | 纵向排列,溢出报错(想纵向换行用 `Wrap(direction: Axis.vertical)`) |
### 3. Chip 家族速查(Flutter 3.x 注意!)
| 组件 | 用途 | 必填回调 |
|---|---|---|
| `Chip` | 纯展示 / 可删除标签 | `onDeleted` 可选 |
| `ActionChip` | 可点击的标签(**旧 `Chip.onPressed` 已移除**) | `onPressed` |
| `FilterChip` | 带选中态的标签(**旧 `Chip.selected` 已移除**) | `onSelected` |
```dart
// 可删除
Chip(
label: const Text("Flutter"),
deleteIcon: Icon(Icons.close, size: 16.w),
onDeleted: () => print("删除 Flutter"),
)
// 可点击(Flutter 3.x 用 ActionChip)
ActionChip(
label: const Text("UniApp"),
onPressed: () => print("点击 UniApp"),
)
// 选中态(Flutter 3.x 用 FilterChip)
FilterChip(
label: const Text("JavaScript"),
selected: true,
selectedColor: const Color(0xFF4CAF50),
onSelected: (_) {},
)
```
### 4. 自绘标签 CustomTag 为什么存在
标准 Chip 的高度/内边距是 Material 规范定的,**设计稿要求"固定 24px 高、图标与文字精确 4px"时**,
Chip 调不精确 —— 这时用 `Container + Row` 手写一个,完全可控:
- `InkWell` 提供点击涟漪(`borderRadius` 让涟漪跟随圆角);
- `TextStyle(height: 1)` 去掉文字默认行高,避免把容器撑高。
---
## 四、关键代码剖析
```dart
// 自绘标签骨架(像素级可控的核心结构)
return Material(
color: Colors.transparent, // 消除 InkWell 默认的白底
child: InkWell(
onTap: onTap,
borderRadius: BorderRadius.circular(4.r),
child: Container(
height: 24.h, // 精确高度
padding: EdgeInsets.only(left: 6.w, right: 6.w),
decoration: BoxDecoration(
color: const Color(0xFFEDF2FF),
borderRadius: BorderRadius.circular(4.r),
border: Border.all(color: const Color(0xFFD6E4FF)),
),
child: Row(
mainAxisSize: MainAxisSize.min,
children: [
if (icon != null) ...[
Icon(icon, size: 12.w, color: iconColor),
SizedBox(width: 4.w), // 图标与文字精确 4px
],
Text(label, style: TextStyle(fontSize: 12.sp, height: 1)),
if (onDeleted != null) ...[
SizedBox(width: 2.w),
GestureDetector(onTap: onDeleted, child: Icon(Icons.close, size: 12.w)),
],
],
),
),
),
)
```
要点:
- `Material + InkWell` 组合才有水波纹;`InkWell` 直接包 `Container` 时涟漪会被容器背景盖住;
- 用 `if (xx != null) ...[]` 实现"可选 icon / 可选删除叉"——传 `null` 就不渲染。
---
## 五、踩坑记录
1. **`Chip.onPressed` / `Chip.selected` 在 Flutter 3.x 已移除**,分别改用 `ActionChip` / `FilterChip`(旧代码会编译报错)。
2. `Wrap` 外层要给它**有界的宽度**(比如直接当 body 用,或放 `Padding` 里),否则换行无从谈起。
3. 自绘标签里文字务必 `height: 1`,否则默认行高会让"24px 高"变成"30px+ 高"。
4. `print` 只适合 Demo 调试,真实项目用 `debugPrint` 或日志组件(见 dio_demo 的注释)。
---
## 六、扩展思考
- **`Flow`**:Wrap 的高阶版,自定义换行算法(间距均匀分布、瀑布流),性能更好但代码量大;
- 标签流 + 搜索历史的真实场景:数据存本地(shared_preferences)→ 读取渲染 Wrap → 点击标签直接搜索;
- Chip 的 `shape: StadiumBorder()` = 胶囊全圆角,`avatar` 放小图标、`labelPadding` 控制文字内边距;
- 本项目 `theme_demo` 里的品牌切换、`note_picker_demo` 里的"加号格",都用了 Wrap/网格思路,可对照学习。