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