Files
App.K12.Student.Miniapp_Flu…/lib/pages/demo/smooth_sheet_demo.dart
T
2026-09-04 15:22:20 +08:00

346 lines
11 KiB
Dart
Raw 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';
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,
),
),
],
),
);
}
}