Files
2026-09-04 15:22:20 +08:00

188 lines
7.4 KiB
Dart
Raw Permalink 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.
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),
),
),
],
],
),
),
),
);
}
}