import 'package:flutter/material.dart'; import 'package:flutter_screenutil/flutter_screenutil.dart'; /// ============================================================ /// Wrap 自动换行布局演示页 /// /// 一、两种使用方式: /// 1. 直接运行本页面:runApp(const MaterialApp(home: WrapDemoPage())) /// 2. 复制 body 核心代码到任意 Scaffold 的 body 中(见下方)。 /// /// 二、作用: /// Row 一行排不下就直接溢出报错;Wrap 空间不够时会【自动换行】, /// 标签流、搜索历史标签这类数量不固定的场景必用。 /// /// 三、核心属性: /// direction : 排布方向。Axis.horizontal 水平排布(默认);Axis.vertical 垂直排布 /// spacing : 同一行内 item 之间的水平间距 /// runSpacing: 行与行之间的垂直间距(run 是"行/批"的意思) /// /// 四、对比记忆: /// Row : 横向排列,溢出直接报错,不会换行 /// Wrap : 横向排列,空间不足自动换行 /// ============================================================ class WrapDemoPage extends StatelessWidget { const WrapDemoPage({super.key}); @override Widget build(BuildContext context) { return Scaffold( appBar: AppBar(title: const Text('Wrap 标签流 Demo')), // ---------- 下方即为核心代码,可直接复制到 Scaffold body ---------- body: Padding( padding: EdgeInsets.all(16.w), // 不写const,每次build会新建对象,产生不必要开销 child: Wrap( spacing: 24, // 同一行 item 水平间距 runSpacing: 20, // 上下两行之间的距离 children: [ // ---- 最简单的 Chip:只有 label(必填)---- const Chip(label: Text("Vue")), // ---- 带头像 + 边框 + 圆角胶囊形状 ---- Chip( label: const Text("React"), avatar: Icon(Icons.tag, size: 18.w), // 标签前面的小图标 backgroundColor: const Color(0xFFE3F2FD), // 背景色 labelStyle: TextStyle(color: Colors.black, fontSize: 14.sp), // 文字样式 side: const BorderSide(color: Color(0xFF64B5F6)), // 边框 shape: const StadiumBorder(), // 全圆角胶囊形状 labelPadding: EdgeInsets.symmetric(horizontal: 6.w), // 文字内边距 ), // ---- 可删除的 Chip:右上角出小叉号,点击触发 onDeleted ---- Chip( label: const Text("Flutter"), avatar: Icon(Icons.rocket_launch, size: 18.w), deleteIcon: Icon(Icons.close, size: 16.w), // 自定义叉号图标 backgroundColor: Colors.blue[100], labelStyle: const TextStyle(color: Colors.black), shape: StadiumBorder(), onDeleted: () => print("删除 Flutter"), // 点击叉号回调 ), // ---- 可点击的 Chip:点击整个标签触发 onPressed ---- // Flutter 3.x 注意:Chip 已移除 onPressed,可点击请用 ActionChip ActionChip( label: const Text("UniApp"), avatar: Icon(Icons.smartphone, size: 18.w), backgroundColor: const Color(0xFFFCE4EC), elevation: 2, // 阴影高度 shadowColor: Colors.pink.shade100, // 阴影颜色 padding: EdgeInsets.all(6.w), // Chip 整体内边距 onPressed: () => print("点击 UniApp"), // 点击标签回调 ), // ---- 选中态 Chip:selected 为 true 时显示对勾 ---- // Flutter 3.x 注意:Chip 已移除 selected,选中态请用 FilterChip FilterChip( label: const Text("JavaScript"), selected: true, // 选中态 selectedColor: const Color(0xFF4CAF50), // 选中时的背景色 checkmarkColor: Colors.white, // 对勾颜色 labelStyle: const TextStyle(color: Colors.white), shape: const StadiumBorder(), onSelected: (_) {}, // FilterChip 必填回调:选中状态变化时触发 ), // ---- 深色背景 + 自定义删除图标颜色 ---- Chip( label: const Text("Dart语言"), avatar: Icon(Icons.code, size: 18.w, color: Colors.white), backgroundColor: const Color(0xFF263238), // 深色背景 deleteIcon: Icon(Icons.cancel, size: 16.w, color: Colors.white70), labelStyle: const TextStyle(color: Colors.white), shape: const StadiumBorder(), onDeleted: () => print("删除 Dart语言"), ), // ---- 使用 Material 3 默认样式的 Chip ---- const Chip(label: Text("前端开发")), // ===== 分割线:下面是像素级可控的自绘标签 CustomTag ===== Divider(height: 32.h), const CustomTag( label: "精准UI", icon: Icons.brush, iconColor: Color(0xFF3B82F6), onDeleted: null, // 无删除按钮的纯标签 ), CustomTag( label: "可删除标签", icon: Icons.rocket_launch, onDeleted: () => print("删除 可删除标签"), ), ], ), ), ); } } /// 像素级可控的自绘标签:高度/间距/圆角/边框/点击反馈全手写 /// 比标准 Chip 灵活,适合设计稿要求精确(固定高度、精确间距)的场景 class CustomTag extends StatelessWidget { const CustomTag({ super.key, required this.label, this.icon, this.iconColor = const Color(0xFF3B82F6), this.onDeleted, this.onTap, }); final String label; final IconData? icon; // 可选的标签前图标 final Color iconColor; final VoidCallback? onDeleted; // 点击叉号回调;传 null 则不显示叉号 final VoidCallback? onTap; // 点击整个标签回调 @override Widget build(BuildContext context) { return Material( color: Colors.transparent, // 消除默认白色背景 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, color: Color(0xFF333333), height: 1, // 去掉文字默认行高,避免撑高容器 ), ), if (onDeleted != null) ...[ SizedBox(width: 2.w), // 文字与叉号精确 2px GestureDetector( onTap: onDeleted, child: Icon( Icons.close, size: 12.w, color: Color(0xFF999999), ), ), ], ], ), ), ), ); } }