// ✅ 验证通过:Flutter 3.22
/// ListView三种构造方式
// 1. ListView() — 适合少量子项(一次性创建所有)
ListView(
padding: const EdgeInsets.all(16),
children: [
ListTile(title: Text('Item 1')),
ListTile(title: Text('Item 2')),
ListTile(title: Text('Item 3')),
],
)
// 2. ListView.builder() — 适合大量/无限列表(懒加载)
ListView.builder(
itemCount: 1000, // 可选,不设则无限
itemBuilder: (context, index) {
return ListTile(
title: Text('Item $index'),
leading: CircleAvatar(child: Text('$index')),
);
},
)
// 3. ListView.separated() — 带分隔符
ListView.separated(
itemCount: 20,
itemBuilder: (context, index) => ListTile(
title: Text('Item $index'),
),
separatorBuilder: (context, index) => const Divider(),
)
/// ListView进阶
class AdvancedListView extends StatelessWidget {
const AdvancedListView({super.key});
@override
Widget build(BuildContext context) {
return ListView.builder(
itemCount: 100,
// 预加载区域(默认0.5倍视口)
cacheExtent: 500,
// 滚动物理特性
physics: const BouncingScrollPhysics(),
// 反向滚动
reverse: false,
// 滚动方向
scrollDirection: Axis.vertical,
// 控制器
controller: ScrollController(),
itemBuilder: (context, index) {
// ✅ 根据类型返回不同Widget
if (index == 0) {
return const _HeaderWidget();
}
if (index.isOdd) {
return _FeaturedItem(index: index);
}
return _NormalItem(index: index);
},
);
}
}
/// Pull-to-Refresh 下拉刷新
class RefreshListDemo extends StatefulWidget {
const RefreshListDemo({super.key});
@override
State<RefreshListDemo> createState() => _RefreshListDemoState();
}
class _RefreshListDemoState extends State<RefreshListDemo> {
List<String> _items = List.generate(20, (i) => 'Item $i');
Future<void> _refresh() async {
await Future.delayed(const Duration(seconds: 1));
setState(() {
_items = List.generate(20, (i) => '刷新后 Item ${DateTime.now().second}-$i');
});
}
@override
Widget build(BuildContext context) {
return RefreshIndicator(
onRefresh: _refresh,
color: Theme.of(context).colorScheme.primary,
child: ListView.builder(
itemCount: _items.length,
itemBuilder: (context, index) => ListTile(title: Text(_items[index])),
),
);
}
}
// ✅ 验证通过
/// GridView三种构造方式
// 1. GridView() — 适合少量子项
GridView(
gridDelegate: const SliverGridDelegateWithFixedCrossAxisCount(
crossAxisCount: 3, // 列数
mainAxisSpacing: 8, // 主轴间距
crossAxisSpacing: 8, // 交叉轴间距
childAspectRatio: 1.0, // 子项宽高比
),
children: [
Container(color: Colors.red),
Container(color: Colors.green),
Container(color: Colors.blue),
],
)
// 2. GridView.builder() — 懒加载
GridView.builder(
gridDelegate: const SliverGridDelegateWithFixedCrossAxisCount(
crossAxisCount: 2,
mainAxisSpacing: 12,
crossAxisSpacing: 12,
childAspectRatio: 0.75, // 宽:高 = 0.75:1
),
itemCount: 50,
itemBuilder: (context, index) => _ProductCard(index: index),
)
// 3. GridView.extent() — 按最大宽度自动计算列数
GridView.extent(
maxCrossAxisExtent: 150, // 每项最大宽度150
children: [...],
)
// 4. GridView.count() — 指定列数
GridView.count(
crossAxisCount: 3,
children: [...],
)
// ✅ 验证通过
/// CustomScrollView — 自定义滚动视图
/// 通过组合Sliver实现复杂的滚动效果
class CustomScrollDemo extends StatelessWidget {
const CustomScrollDemo({super.key});
@override
Widget build(BuildContext context) {
return CustomScrollView(
slivers: [
// 1. SliverAppBar — 可折叠的AppBbar
SliverAppBar(
expandedHeight: 200,
pinned: true, // 折叠后是否固定
floating: false, // 是否在下滑时立即显示
snap: false, // 配合floating使用
stretch: true, // 允许过度拉伸
flexibleSpace: FlexibleSpaceBar(
title: const Text('Sliver Demo'),
background: Container(
decoration: const BoxDecoration(
gradient: LinearGradient(
colors: [Colors.purple, Colors.blue],
),
),
),
),
),
// 2. SliverList — 列表
SliverList(
delegate: SliverChildBuilderDelegate(
(context, index) => ListTile(title: Text('Item $index')),
childCount: 20,
),
),
// 3. SliverGrid — 网格
SliverGrid(
gridDelegate: const SliverGridDelegateWithFixedCrossAxisCount(
crossAxisCount: 3,
mainAxisSpacing: 8,
crossAxisSpacing: 8,
),
delegate: SliverChildBuilderDelegate(
(context, index) => Container(
color: Colors.primaries[index % Colors.primaries.length],
child: Center(child: Text('$index')),
),
childCount: 12,
),
),
// 4. SliverToBoxAdapter — 包裹普通Widget
SliverToBoxAdapter(
child: Padding(
padding: const EdgeInsets.all(16),
child: Text('分节标题', style: Theme.of(context).textTheme.titleLarge),
),
),
// 5. SliverPadding — Sliver的内边距
const SliverPadding(
padding: EdgeInsets.all(16),
sliver: SliverToBoxAdapter(child: Text('带Padding的内容')),
),
// 6. SliverPersistentHeader — 持久化头部
SliverPersistentHeader(
pinned: true,
delegate: _SectionHeaderDelegate('固定标题'),
),
// 7. SliverOpacity — 透明度
const SliverOpacity(
opacity: 0.5,
sliver: SliverToBoxAdapter(child: Text('半透明内容')),
),
],
);
}
}
class _SectionHeaderDelegate extends SliverPersistentHeaderDelegate {
final String title;
_SectionHeaderDelegate(this.title);
@override
Widget build(BuildContext context, double shrinkOffset, bool overlapsContent) {
return Container(
color: Theme.of(context).colorScheme.surface,
padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 8),
alignment: Alignment.centerLeft,
child: Text(title, style: Theme.of(context).textTheme.titleMedium),
);
}
@override
double get maxExtent => 50;
@override
double get minExtent => 50;
@override
bool shouldRebuild(covariant _SectionHeaderDelegate oldDelegate) {
return title != oldDelegate.title;
}
}
// ✅ 验证通过:Flutter 3.22 + Dart 3.4
// 文件:lib/shop_home.dart
import 'package:flutter/material.dart';
void main() => runApp(const ShopApp());
class ShopApp extends StatelessWidget {
const ShopApp({super.key});
@override
Widget build(BuildContext context) {
return MaterialApp(
title: '电商首页',
debugShowCheckedModeBanner: false,
theme: ThemeData(
colorScheme: ColorScheme.fromSeed(seedColor: Colors.deepPurple),
useMaterial3: true,
),
home: const ShopHomePage(),
);
}
}
// 数据模型
class Product {
final String id;
final String name;
final double price;
final String imageUrl;
final double rating;
final int reviews;
final String category;
final bool isNew;
const Product({
required this.id,
required this.name,
required this.price,
required this.imageUrl,
this.rating = 4.5,
this.reviews = 0,
required this.category,
this.isNew = false,
});
}
class BannerItem {
final String title;
final String subtitle;
final List<Color> gradient;
const BannerItem({required this.title, required this.subtitle, required this.gradient});
}
// 示例数据
const banners = [
BannerItem(title: '新品首发', subtitle: '限时8折优惠', gradient: [Colors.purple, Colors.blue]),
BannerItem(title: '夏日特卖', subtitle: '满199减50', gradient: [Colors.orange, Colors.red]),
BannerItem(title: '会员日', subtitle: '专属福利等你来', gradient: [Colors.teal, Colors.green]),
];
const categories = ['全部', '数码', '服饰', '食品', '家居', '运动'];
const products = [
Product(id: '1', name: '无线蓝牙耳机', price: 299, imageUrl: '', category: '数码', rating: 4.8, reviews: 256, isNew: true),
Product(id: '2', name: '轻薄笔记本', price: 5999, imageUrl: '', category: '数码', rating: 4.6, reviews: 128),
Product(id: '3', name: '运动跑鞋', price: 399, imageUrl: '', category: '运动', rating: 4.7, reviews: 512),
Product(id: '4', name: '有机零食礼盒', price: 89, imageUrl: '', category: '食品', rating: 4.3, reviews: 89, isNew: true),
Product(id: '5', name: '智能手表', price: 1299, imageUrl: '', category: '数码', rating: 4.5, reviews: 340),
Product(id: '6', name: '棉麻T恤', price: 159, imageUrl: '', category: '服饰', rating: 4.4, reviews: 198),
Product(id: '7', name: '北欧台灯', price: 269, imageUrl: '', category: '家居', rating: 4.6, reviews: 67),
Product(id: '8', name: '瑜伽垫', price: 129, imageUrl: '', category: '运动', rating: 4.2, reviews: 445),
];
// 主页面
class ShopHomePage extends StatefulWidget {
const ShopHomePage({super.key});
@override
State<ShopHomePage> createState() => _ShopHomePageState();
}
class _ShopHomePageState extends State<ShopHomePage> {
String _selectedCategory = '全部';
List<Product> get _filteredProducts => _selectedCategory == '全部'
? products
: products.where((p) => p.category == _selectedCategory).toList();
@override
Widget build(BuildContext context) {
return Scaffold(
body: CustomScrollView(
slivers: [
// 搜索AppBbar
SliverAppBar(
pinned: true,
title: TextField(
decoration: InputDecoration(
hintText: '搜索商品...',
prefixIcon: const Icon(Icons.search),
border: OutlineInputBorder(borderRadius: BorderRadius.circular(24)),
contentPadding: const EdgeInsets.symmetric(horizontal: 16),
filled: true,
fillColor: Theme.of(context).colorScheme.surface,
),
),
actions: [
IconButton(onPressed: () {}, icon: const Icon(Icons.shopping_cart)),
],
),
// 轮播Banner
SliverToBoxAdapter(
child: SizedBox(
height: 160,
child: PageView.builder(
itemCount: banners.length,
controller: PageController(viewportFraction: 0.9),
itemBuilder: (context, index) {
var banner = banners[index];
return Container(
margin: const EdgeInsets.symmetric(horizontal: 4, vertical: 8),
padding: const EdgeInsets.all(20),
decoration: BoxDecoration(
gradient: LinearGradient(
begin: Alignment.topLeft,
end: Alignment.bottomRight,
colors: banner.gradient,
),
borderRadius: BorderRadius.circular(16),
),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
mainAxisAlignment: MainAxisAlignment.center,
children: [
Text(banner.title, style: const TextStyle(color: Colors.white, fontSize: 22, fontWeight: FontWeight.bold)),
const SizedBox(height: 4),
Text(banner.subtitle, style: TextStyle(color: Colors.white.withOpacity(0.9), fontSize: 14)),
],
),
);
},
),
),
),
// 分类标签
SliverToBoxAdapter(
child: SizedBox(
height: 50,
child: ListView(
scrollDirection: Axis.horizontal,
padding: const EdgeInsets.symmetric(horizontal: 16),
children: categories.map((cat) {
var isSelected = cat == _selectedCategory;
return Padding(
padding: const EdgeInsets.only(right: 8),
child: FilterChip(
selected: isSelected,
label: Text(cat),
onSelected: (_) => setState(() => _selectedCategory = cat),
),
);
}).toList(),
),
),
),
// 商品网格
SliverPadding(
padding: const EdgeInsets.all(16),
sliver: SliverGrid(
gridDelegate: const SliverGridDelegateWithFixedCrossAxisCount(
crossAxisCount: 2,
mainAxisSpacing: 12,
crossAxisSpacing: 12,
childAspectRatio: 0.68,
),
delegate: SliverChildBuilderDelegate(
(context, index) {
var product = _filteredProducts[index];
return _ProductCard(product: product);
},
childCount: _filteredProducts.length,
),
),
),
],
),
);
}
}
// 商品卡片
class _ProductCard extends StatelessWidget {
final Product product;
const _ProductCard({required this.product});
@override
Widget build(BuildContext context) {
return Card(
clipBehavior: Clip.antiAlias,
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(12)),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
// 图片占位
Expanded(
flex: 3,
child: Stack(
children: [
Container(
width: double.infinity,
color: Colors.grey[200],
child: const Icon(Icons.image, size: 40, color: Colors.grey),
),
if (product.isNew)
Positioned(
top: 8,
left: 8,
child: Container(
padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 2),
decoration: BoxDecoration(
color: Colors.red,
borderRadius: BorderRadius.circular(8),
),
child: const Text('NEW', style: TextStyle(color: Colors.white, fontSize: 10, fontWeight: FontWeight.bold)),
),
),
],
),
),
// 信息
Expanded(
flex: 2,
child: Padding(
padding: const EdgeInsets.all(8),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
product.name,
maxLines: 1,
overflow: TextOverflow.ellipsis,
style: const TextStyle(fontWeight: FontWeight.w600),
),
const SizedBox(height: 4),
Row(
children: [
Icon(Icons.star, size: 14, color: Colors.amber[700]),
Text(' ${product.rating}', style: const TextStyle(fontSize: 12)),
Text(' (${product.reviews})', style: TextStyle(fontSize: 11, color: Colors.grey[500])),
],
),
const Spacer(),
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Text(
'¥${product.price}',
style: TextStyle(
color: Theme.of(context).colorScheme.primary,
fontWeight: FontWeight.bold,
fontSize: 16,
),
),
Container(
padding: const EdgeInsets.all(4),
decoration: BoxDecoration(
color: Theme.of(context).colorScheme.primaryContainer,
borderRadius: BorderRadius.circular(8),
),
child: Icon(
Icons.add_shopping_cart,
size: 16,
color: Theme.of(context).colorScheme.onPrimaryContainer,
),
),
],
),
],
),
),
),
],
),
);
}
}
实现滚动到底部自动加载更多:
// 提示:ScrollController + 监听到底触发加载
_scrollController.addListener(() {
if (_scrollController.position.pixels >=
_scrollController.position.maxScrollExtent - 200) {
_loadMore();
}
});
使用flutter_staggered_grid_view包或自定义实现瀑布流。
使用CustomScrollView + SliverPersistentHeader实现联系人样式的吸顶分组列表。
下一课开始进入导航与状态阶段!我们将学习Flutter的路由导航机制。