5.1 KiB
5.1 KiB
【Demo】Wrap 自动换行布局 + Chip 标签家族
源文件:
lib/pages/demo/wrap_demo.dart| 分类:基础布局 一句话:Row 一行排不下就溢出报错,Wrap 会自动换行 —— 做"标签流"必备。
一、这个 Demo 解决什么问题
"搜索历史标签"、"科目标签"、"技能标签"这类数量不固定的横向排列场景:
Row一行排不下会直接溢出报错(黄色条纹警告),不会换行;Wrap空间不够时自动换到下一行,是标签流的正解。
本 Demo 还完整演示了 Flutter 的 Chip 标签家族:Chip / ActionChip / FilterChip,
以及一个像素级可控的自绘标签 CustomTag(设计稿要求精确高度/间距时的选择)。
二、怎么运行 / 演示内容
runApp(const MaterialApp(home: WrapDemoPage()));
页面是一个 Wrap,里面混排了 8 种 Chip 样式 + 自绘 CustomTag: 带图标、可删除、可点击、选中态、深色底、胶囊圆角……一屏看完所有变体。
三、核心知识点(新手向)
1. Wrap 的核心属性
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 |
// 可删除
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)去掉文字默认行高,避免把容器撑高。
四、关键代码剖析
// 自绘标签骨架(像素级可控的核心结构)
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就不渲染。
五、踩坑记录
Chip.onPressed/Chip.selected在 Flutter 3.x 已移除,分别改用ActionChip/FilterChip(旧代码会编译报错)。Wrap外层要给它有界的宽度(比如直接当 body 用,或放Padding里),否则换行无从谈起。- 自绘标签里文字务必
height: 1,否则默认行高会让"24px 高"变成"30px+ 高"。 print只适合 Demo 调试,真实项目用debugPrint或日志组件(见 dio_demo 的注释)。
六、扩展思考
Flow:Wrap 的高阶版,自定义换行算法(间距均匀分布、瀑布流),性能更好但代码量大;- 标签流 + 搜索历史的真实场景:数据存本地(shared_preferences)→ 读取渲染 Wrap → 点击标签直接搜索;
- Chip 的
shape: StadiumBorder()= 胶囊全圆角,avatar放小图标、labelPadding控制文字内边距; - 本项目
theme_demo里的品牌切换、note_picker_demo里的"加号格",都用了 Wrap/网格思路,可对照学习。