import 'package:flutter/material.dart'; import 'package:flutter_screenutil/flutter_screenutil.dart'; /// ============================================================ /// CustomScrollView + Sliver 系列演示页 /// /// 一、两种使用方式: /// 1. 直接运行本页面:runApp(const MaterialApp(home: SliverDemoPage())) /// 2. 复制 slivers 数组到任意 CustomScrollView 中。 /// /// 二、核心概念: /// Sliver = "碎片"。CustomScrollView 把多个 sliver 拼成一个 /// 统一的滚动视图(共用一个滚动控制器,整页一起滚)。 /// /// 三、常用 sliver: /// SliverAppBar : 可折叠/固定的 AppBar(pinned 固定、floating 浮出) /// SliverToBoxAdapter : 把普通组件(文字/卡片/图片)塞进滚动流 /// SliverGrid.builder : 网格区块(懒加载) /// SliverList.builder : 列表区块(懒加载) /// SliverPadding : 给某个 sliver 加内边距 /// /// 四、为什么需要 Sliver(重点): /// ListView 不能嵌套 ListView/GridView(同向滚动会冲突); /// Sliver 方案让"AppBar + 网格 + 列表 + 卡片"混排且整页只滚一次。 /// /// 五、和 GridView 的本质区别(重点记忆): /// GridView 只是 CustomScrollView 的简化套餐: /// GridView = CustomScrollView + 1 个 SliverGrid。 /// 两者都是"单个滚动 + 懒加载",区别只在混排能力: /// · 页面只有一种内容(网格/列表)→ GridView / ListView,写起来更简单 /// · 页面要混排多种内容 → 本页这种 CustomScrollView,slivers 拆块拼装 /// 判断口诀:这页是"一个网格"还是"折叠头+网格+长列表"?后者才用 Sliver。 /// ============================================================ class SliverDemoPage extends StatelessWidget { const SliverDemoPage({super.key}); static const List _colors = [ Color(0xFF3B82F6), Color(0xFF8E6FE8), Color(0xFFF05252), Color(0xFFFF9800), Color(0xFF4CAF50), Color(0xFF00BCD4), ]; @override Widget build(BuildContext context) { return Scaffold( body: CustomScrollView( slivers: [ // ① 可折叠 AppBar:下拉展开大图,上滑收起只留标题栏 SliverAppBar( expandedHeight: 180.h, // 展开时高度 pinned: true, // 滚动时固定在顶部 backgroundColor: Color(0xFF3B82F6), flexibleSpace: FlexibleSpaceBar( title: Text('Sliver 系列 Demo'), centerTitle: true, background: DecoratedBox( decoration: BoxDecoration( gradient: LinearGradient( begin: Alignment.topLeft, end: Alignment.bottomRight, colors: [Color(0xFF3B82F6), Color(0xFF8E6FE8)], ), ), ), ), ), // ② 普通组件插进滚动流:SliverToBoxAdapter SliverToBoxAdapter( child: Padding( padding: EdgeInsets.fromLTRB(16.w, 16.h, 16.w, 0), child: Text( '网格区块(SliverGrid,懒加载)', style: TextStyle( fontSize: 16.sp, fontWeight: FontWeight.bold, color: Color(0xFF333333), ), ), ), ), // ③ 网格区块 SliverPadding( padding: EdgeInsets.all(16.w), sliver: SliverGrid.builder( gridDelegate: SliverGridDelegateWithFixedCrossAxisCount( crossAxisCount: 3, mainAxisSpacing: 8.w, crossAxisSpacing: 8.w, ), itemCount: 12, itemBuilder: (context, index) => _GridItem(index: index), ), ), // ④ 区块标题 SliverToBoxAdapter( child: Padding( padding: EdgeInsets.fromLTRB(16.w, 8.h, 16.w, 8.h), child: Text( '列表区块(SliverList,懒加载)', style: TextStyle( fontSize: 16.sp, fontWeight: FontWeight.bold, color: Color(0xFF333333), ), ), ), ), // ⑤ 列表区块:50 条,和上面的网格共用同一个滚动 SliverList.builder( itemCount: 50, itemBuilder: (context, index) => ListTile( leading: CircleAvatar( backgroundColor: _colors[index % _colors.length], child: Text( '$index', style: const TextStyle(color: Colors.white), ), ), title: Text('第 $index 条'), subtitle: Text('Sliver 懒加载,滚动到这里才创建'), trailing: const Icon(Icons.chevron_right), ), ), ], ), ); } } /// 网格项:彩色方块 + 序号 class _GridItem extends StatelessWidget { const _GridItem({required this.index}); final int index; @override Widget build(BuildContext context) { return Container( decoration: BoxDecoration( color: SliverDemoPage._colors[index % SliverDemoPage._colors.length], borderRadius: BorderRadius.circular(8.r), ), alignment: Alignment.center, child: Text( '$index', style: TextStyle( color: Colors.white, fontSize: 18.sp, fontWeight: FontWeight.bold, ), ), ); } }