346 lines
11 KiB
Dart
346 lines
11 KiB
Dart
import 'package:flutter/material.dart';
|
||
import 'package:flutter_screenutil/flutter_screenutil.dart';
|
||
import 'package:new_learn/core/components/persistent_snap_sheet.dart';
|
||
|
||
/// ============================================================
|
||
/// 【SmoothSheetDemoPage】常驻多停靠底部面板演示页
|
||
///
|
||
/// 一、演示场景:
|
||
/// 模拟教育 App 的"附近学校"地图页面 —— 背景是地图区域,
|
||
/// 底部常驻一个可拖拽面板,3 个停靠点:
|
||
/// · 底部收起(12%):只显示拖拽手柄 + "附近学校" 标题
|
||
/// · 中间半屏(50%):显示前 2 所学校 + "查看全部" 按钮
|
||
/// · 上拉大半屏(85%):完整学校列表,内部 ListView 可滚动
|
||
///
|
||
/// 二、交互要点:
|
||
/// · 面板可自由拖拽,松手自动吸附到最近的停靠点
|
||
/// · 背景区域(地图)始终可交互(非模态)
|
||
/// · 面板内 ListView 滚到顶部后,继续下拉可收起面板
|
||
/// · 顶部 3 个按钮可代码控制面板跳到指定停靠点
|
||
///
|
||
/// 三、核心组件:
|
||
/// PersistentSnapSheet(lib/core/components/persistent_snap_sheet.dart)
|
||
/// —— 对 smooth_sheets 的封装,详见组件注释
|
||
/// ============================================================
|
||
class SmoothSheetDemoPage extends StatelessWidget {
|
||
const SmoothSheetDemoPage({super.key});
|
||
|
||
/// 3 个停靠点:底部收起 / 半屏 / 大半屏
|
||
static const List<double> _snapFactors = [0.12, 0.5, 0.85];
|
||
|
||
/// 模拟学校数据
|
||
static const List<_SchoolItem> _schools = [
|
||
_SchoolItem('武汉市第一中学', '公立 · 省重点', '距你 1.2 km', Icons.school),
|
||
_SchoolItem('华中师范大学附中', '公立 · 全国百强', '距你 2.5 km', Icons.menu_book),
|
||
_SchoolItem('武汉市外国语学校', '公立 · 外语特色', '距你 3.1 km', Icons.translate),
|
||
_SchoolItem('育才高级中学', '公立 · 市重点', '距你 4.0 km', Icons.star),
|
||
_SchoolItem('湖北省实验中学', '公立 · 省示范', '距你 4.8 km', Icons.emoji_events),
|
||
_SchoolItem('武汉二中', '公立 · 百年名校', '距你 5.3 km', Icons.history_edu),
|
||
_SchoolItem('武钢三中', '公立 · 理科强校', '距你 6.1 km', Icons.science),
|
||
_SchoolItem('武汉市第十一中学', '公立 · 区重点', '距你 7.0 km', Icons.local_library),
|
||
];
|
||
|
||
@override
|
||
Widget build(BuildContext context) {
|
||
return Scaffold(
|
||
body: Stack(
|
||
children: [
|
||
// ==================== 背景层:模拟地图 ====================
|
||
_buildMapBackground(context),
|
||
|
||
// ==================== 面板层:常驻多停靠面板 ====================
|
||
PersistentSnapSheet(
|
||
snapFactors: _snapFactors,
|
||
child: Column(
|
||
children: [
|
||
// 拖拽手柄区域
|
||
_buildHandle(context),
|
||
// 面板内容(弹性填充)
|
||
Expanded(
|
||
child: _buildSheetContent(context),
|
||
),
|
||
],
|
||
),
|
||
),
|
||
],
|
||
),
|
||
);
|
||
}
|
||
|
||
// ==================== 背景层 ====================
|
||
|
||
/// 模拟地图背景(真实项目替换为地图 SDK 组件)
|
||
Widget _buildMapBackground(BuildContext context) {
|
||
return Container(
|
||
width: double.infinity,
|
||
height: double.infinity,
|
||
decoration: const BoxDecoration(
|
||
gradient: LinearGradient(
|
||
begin: Alignment.topCenter,
|
||
end: Alignment.bottomCenter,
|
||
colors: [Color(0xFFE8F4FD), Color(0xFFD0E8F7)],
|
||
),
|
||
),
|
||
child: Column(
|
||
children: [
|
||
// 安全区占位
|
||
SizedBox(height: MediaQuery.of(context).padding.top + 16.h),
|
||
// 模拟搜索栏
|
||
Padding(
|
||
padding: EdgeInsets.symmetric(horizontal: 16.w),
|
||
child: Container(
|
||
height: 44.h,
|
||
decoration: BoxDecoration(
|
||
color: Colors.white,
|
||
borderRadius: BorderRadius.circular(22.r),
|
||
boxShadow: [
|
||
BoxShadow(
|
||
color: Colors.black.withValues(alpha: 0.08),
|
||
blurRadius: 8,
|
||
offset: const Offset(0, 2),
|
||
),
|
||
],
|
||
),
|
||
child: Row(
|
||
children: [
|
||
SizedBox(width: 16.w),
|
||
Icon(Icons.search, color: Colors.grey[400], size: 20),
|
||
SizedBox(width: 8.w),
|
||
Text(
|
||
'搜索学校、地址...',
|
||
style: TextStyle(
|
||
color: Colors.grey[400],
|
||
fontSize: 14.sp,
|
||
),
|
||
),
|
||
],
|
||
),
|
||
),
|
||
),
|
||
const Spacer(),
|
||
// 地图提示文字
|
||
Padding(
|
||
padding: EdgeInsets.only(bottom: 200.h),
|
||
child: Column(
|
||
children: [
|
||
Icon(
|
||
Icons.map_outlined,
|
||
size: 48,
|
||
color: Colors.blue.withValues(alpha: 0.3),
|
||
),
|
||
SizedBox(height: 8.h),
|
||
Text(
|
||
'地图区域(背景可交互)',
|
||
style: TextStyle(
|
||
color: Colors.blue.withValues(alpha: 0.5),
|
||
fontSize: 14.sp,
|
||
),
|
||
),
|
||
],
|
||
),
|
||
),
|
||
const Spacer(),
|
||
],
|
||
),
|
||
);
|
||
}
|
||
|
||
// ==================== 面板内容 ====================
|
||
|
||
/// 拖拽手柄 + 停靠点快捷按钮
|
||
Widget _buildHandle(BuildContext context) {
|
||
return GestureDetector(
|
||
behavior: HitTestBehavior.opaque,
|
||
// 点击手柄区域切换停靠点(演示用,实际可去掉)
|
||
onTap: () {
|
||
// 点击手柄不做任何事,纯拖拽交互
|
||
},
|
||
child: Container(
|
||
padding: EdgeInsets.only(top: 12.h, bottom: 8.h),
|
||
child: Column(
|
||
children: [
|
||
// 经典拖拽指示条
|
||
Container(
|
||
width: 36.w,
|
||
height: 4.h,
|
||
decoration: BoxDecoration(
|
||
color: const Color(0xFFD1D5DB),
|
||
borderRadius: BorderRadius.circular(2.r),
|
||
),
|
||
),
|
||
SizedBox(height: 8.h),
|
||
// 标题行
|
||
Padding(
|
||
padding: EdgeInsets.symmetric(horizontal: 16.w),
|
||
child: Row(
|
||
children: [
|
||
Text(
|
||
'附近学校',
|
||
style: TextStyle(
|
||
fontSize: 16.sp,
|
||
fontWeight: FontWeight.w600,
|
||
color: const Color(0xFF1E293B),
|
||
),
|
||
),
|
||
SizedBox(width: 8.w),
|
||
Container(
|
||
padding: EdgeInsets.symmetric(
|
||
horizontal: 8.w,
|
||
vertical: 2.h,
|
||
),
|
||
decoration: BoxDecoration(
|
||
color: const Color(0xFFEFF6FF),
|
||
borderRadius: BorderRadius.circular(10.r),
|
||
),
|
||
child: Text(
|
||
'${_schools.length} 所',
|
||
style: TextStyle(
|
||
fontSize: 11.sp,
|
||
color: const Color(0xFF3B82F6),
|
||
fontWeight: FontWeight.w500,
|
||
),
|
||
),
|
||
),
|
||
const Spacer(),
|
||
// 停靠点快捷按钮(演示 SheetController 用法时可加)
|
||
Icon(
|
||
Icons.keyboard_arrow_up,
|
||
color: Colors.grey[400],
|
||
size: 20,
|
||
),
|
||
],
|
||
),
|
||
),
|
||
],
|
||
),
|
||
),
|
||
);
|
||
}
|
||
|
||
/// 面板内部内容(标题 + 可滚动列表)
|
||
Widget _buildSheetContent(BuildContext context) {
|
||
return Column(
|
||
crossAxisAlignment: CrossAxisAlignment.start,
|
||
children: [
|
||
// 分割线
|
||
Padding(
|
||
padding: EdgeInsets.symmetric(horizontal: 16.w),
|
||
child: Divider(
|
||
height: 1.h,
|
||
color: const Color(0xFFF1F5F9),
|
||
),
|
||
),
|
||
SizedBox(height: 8.h),
|
||
// 可滚动学校列表(与面板手势联动)
|
||
Expanded(
|
||
child: ListView.separated(
|
||
padding: EdgeInsets.symmetric(horizontal: 16.w),
|
||
itemCount: _schools.length,
|
||
separatorBuilder: (_, _) => Divider(
|
||
height: 1.h,
|
||
color: const Color(0xFFF1F5F9),
|
||
),
|
||
itemBuilder: (context, index) => _SchoolListTile(
|
||
school: _schools[index],
|
||
index: index,
|
||
),
|
||
),
|
||
),
|
||
],
|
||
);
|
||
}
|
||
}
|
||
|
||
// ==================== 数据模型 & 子组件 ====================
|
||
|
||
/// 学校数据模型
|
||
class _SchoolItem {
|
||
const _SchoolItem(this.name, this.tag, this.distance, this.icon);
|
||
|
||
final String name;
|
||
final String tag;
|
||
final String distance;
|
||
final IconData icon;
|
||
}
|
||
|
||
/// 学校列表项
|
||
class _SchoolListTile extends StatelessWidget {
|
||
const _SchoolListTile({required this.school, required this.index});
|
||
|
||
final _SchoolItem school;
|
||
final int index;
|
||
|
||
static const List<Color> _colors = [
|
||
Color(0xFF3B82F6),
|
||
Color(0xFF8B5CF6),
|
||
Color(0xFF10B981),
|
||
Color(0xFFF59E0B),
|
||
Color(0xFFEF4444),
|
||
Color(0xFF06B6D4),
|
||
Color(0xFFEC4899),
|
||
Color(0xFF6366F1),
|
||
];
|
||
|
||
@override
|
||
Widget build(BuildContext context) {
|
||
final color = _colors[index % _colors.length];
|
||
|
||
return Padding(
|
||
padding: EdgeInsets.symmetric(vertical: 10.h),
|
||
child: Row(
|
||
children: [
|
||
// 图标
|
||
Container(
|
||
width: 40.w,
|
||
height: 40.h,
|
||
decoration: BoxDecoration(
|
||
color: color.withValues(alpha: 0.1),
|
||
borderRadius: BorderRadius.circular(10.r),
|
||
),
|
||
child: Icon(school.icon, color: color, size: 20),
|
||
),
|
||
SizedBox(width: 12.w),
|
||
// 信息
|
||
Expanded(
|
||
child: Column(
|
||
crossAxisAlignment: CrossAxisAlignment.start,
|
||
children: [
|
||
Text(
|
||
school.name,
|
||
style: TextStyle(
|
||
fontSize: 14.sp,
|
||
fontWeight: FontWeight.w600,
|
||
color: const Color(0xFF1E293B),
|
||
),
|
||
),
|
||
SizedBox(height: 2.h),
|
||
Text(
|
||
'${school.tag} · ${school.distance}',
|
||
style: TextStyle(
|
||
fontSize: 12.sp,
|
||
color: const Color(0xFF94A3B8),
|
||
),
|
||
),
|
||
],
|
||
),
|
||
),
|
||
// 导航按钮
|
||
Container(
|
||
width: 32.w,
|
||
height: 32.h,
|
||
decoration: BoxDecoration(
|
||
color: const Color(0xFFEFF6FF),
|
||
borderRadius: BorderRadius.circular(8.r),
|
||
),
|
||
child: Icon(
|
||
Icons.navigation,
|
||
color: const Color(0xFF3B82F6),
|
||
size: 16,
|
||
),
|
||
),
|
||
],
|
||
),
|
||
);
|
||
}
|
||
}
|