168 lines
6.0 KiB
Dart
168 lines
6.0 KiB
Dart
import 'package:flutter/material.dart';
|
||
import 'package:flutter_screenutil/flutter_screenutil.dart';
|
||
|
||
/// ============================================================
|
||
/// GridView 网格布局演示页
|
||
///
|
||
/// 一、两种使用方式:
|
||
/// 1. 直接运行本页面:runApp(const MaterialApp(home: GridDemoPage()))
|
||
/// 2. 复制某段核心代码到任意 Scaffold 的 body 中。
|
||
///
|
||
/// 二、三个常用构造:
|
||
/// GridView.count : 固定列数,写死 children(一次性全部创建)
|
||
/// GridView.builder : 懒加载,itemCount + itemBuilder,大量数据首选
|
||
/// GridView.extent : 按"每项最大宽度"自适应列数(屏幕越宽列越多)
|
||
///
|
||
/// 三、核心参数:
|
||
/// crossAxisCount : 列数(GridView.count 专用)
|
||
/// maxCrossAxisExtent: 每项最大宽度(GridView.extent 专用)
|
||
/// mainAxisSpacing : 主轴(上下行)间距
|
||
/// crossAxisSpacing : 交叉轴(左右列)间距
|
||
/// childAspectRatio : 每项 宽:高 比例,默认 1(正方形)
|
||
///
|
||
/// 四、高频坑:
|
||
/// 1. GridView 放 Column 必须 SizedBox 定高或 Expanded(同 ListView);
|
||
/// 2. childAspectRatio 太小(如 0.5)时文字会被挤压溢出,调比例或缩小字号。
|
||
///
|
||
/// 五、和 CustomScrollView(Sliver) 的本质区别(重点记忆):
|
||
/// GridView = CustomScrollView + 1 个 SliverGrid(框架内部自动包的)。
|
||
/// SDK 源码:GridView extends BoxScrollView,而 ScrollView.build()
|
||
/// 返回的就是 CustomScrollView——所以 GridView 是"只有网格这一种内容"
|
||
/// 的简化套餐,两者都是"单个滚动 + 懒加载"。
|
||
/// · 页面只有网格 → body: GridView.builder(...) 直接用
|
||
/// · 页面要混排 AppBar + 网格 + 列表 → CustomScrollView + slivers
|
||
/// · 本页外层 SingleChildScrollView 只是"展示台":为并排演示 3 种构造,
|
||
/// 内部塞的是固定高 GridView(嵌套滚动)。真实网格页别学这个外层!
|
||
/// ============================================================
|
||
class GridDemoPage extends StatelessWidget {
|
||
const GridDemoPage({super.key});
|
||
|
||
// 网格配色(循环使用)
|
||
static const List<Color> _colors = [
|
||
Color(0xFF3B82F6), Color(0xFF8E6FE8), Color(0xFFF05252),
|
||
Color(0xFFFF9800), Color(0xFF4CAF50), Color(0xFF00BCD4),
|
||
];
|
||
|
||
@override
|
||
Widget build(BuildContext context) {
|
||
return Scaffold(
|
||
appBar: AppBar(title: const Text('GridView 网格 Demo')),
|
||
body: SingleChildScrollView(
|
||
padding: EdgeInsets.all(16.w),
|
||
child: Column(
|
||
crossAxisAlignment: CrossAxisAlignment.start,
|
||
children: [
|
||
const _SectionTitle('Demo1:GridView.count 固定 3 列'),
|
||
Text(
|
||
'写死 children,一次性全创建,适合少量固定网格',
|
||
style: TextStyle(fontSize: 13.sp, color: Color(0xFF888888)),
|
||
),
|
||
SizedBox(height: 8.h),
|
||
SizedBox(
|
||
height: 260.h,
|
||
child: GridView.count(
|
||
crossAxisCount: 3, // 列数
|
||
mainAxisSpacing: 8.w, // 上下间距
|
||
crossAxisSpacing: 8.w, // 左右间距
|
||
childAspectRatio: 1, // 宽:高 = 1:1 正方形
|
||
children: [
|
||
for (int i = 0; i < 12; i++) _GridItem(index: i),
|
||
],
|
||
),
|
||
),
|
||
SizedBox(height: 32.h),
|
||
|
||
const _SectionTitle('Demo2:GridView.builder 懒加载'),
|
||
Text(
|
||
'itemCount: 100,只创建可见的格子,滚到哪建到哪',
|
||
style: TextStyle(fontSize: 13.sp, color: Color(0xFF888888)),
|
||
),
|
||
SizedBox(height: 8.h),
|
||
SizedBox(
|
||
height: 260.h,
|
||
child: GridView.builder(
|
||
gridDelegate: SliverGridDelegateWithFixedCrossAxisCount(
|
||
crossAxisCount: 4, // 4 列小格子
|
||
mainAxisSpacing: 8.w,
|
||
crossAxisSpacing: 8.w,
|
||
childAspectRatio: 1,
|
||
),
|
||
itemCount: 100, // 假装 100 个格子
|
||
itemBuilder: (context, index) => _GridItem(index: index),
|
||
),
|
||
),
|
||
SizedBox(height: 32.h),
|
||
|
||
const _SectionTitle('Demo3:GridView.extent 自适应列宽'),
|
||
Text(
|
||
'maxCrossAxisExtent: 120 → 每项最多 120 宽,屏幕越宽列数自动变多',
|
||
style: TextStyle(fontSize: 13.sp, color: Color(0xFF888888)),
|
||
),
|
||
SizedBox(height: 8.h),
|
||
SizedBox(
|
||
height: 260.h,
|
||
child: GridView.extent(
|
||
maxCrossAxisExtent: 80.w, // 每项最多 80 宽
|
||
mainAxisSpacing: 8.w,
|
||
crossAxisSpacing: 8.w,
|
||
childAspectRatio: 1,
|
||
children: [
|
||
for (int i = 0; i < 12; i++) _GridItem(index: i),
|
||
],
|
||
),
|
||
),
|
||
],
|
||
),
|
||
),
|
||
);
|
||
}
|
||
}
|
||
|
||
/// 单个网格项:彩色方块 + 序号
|
||
class _GridItem extends StatelessWidget {
|
||
const _GridItem({required this.index});
|
||
|
||
final int index;
|
||
|
||
@override
|
||
Widget build(BuildContext context) {
|
||
return Container(
|
||
decoration: BoxDecoration(
|
||
color: GridDemoPage._colors[index % GridDemoPage._colors.length],
|
||
borderRadius: BorderRadius.circular(8.r),
|
||
),
|
||
alignment: Alignment.center,
|
||
child: Text(
|
||
'$index',
|
||
style: TextStyle(
|
||
color: Colors.white,
|
||
fontSize: 18.sp,
|
||
fontWeight: FontWeight.bold,
|
||
),
|
||
),
|
||
);
|
||
}
|
||
}
|
||
|
||
/// 区块标题
|
||
class _SectionTitle extends StatelessWidget {
|
||
const _SectionTitle(this.title);
|
||
|
||
final String title;
|
||
|
||
@override
|
||
Widget build(BuildContext context) {
|
||
return Padding(
|
||
padding: EdgeInsets.only(bottom: 4.h),
|
||
child: Text(
|
||
title,
|
||
style: TextStyle(
|
||
fontSize: 16.sp,
|
||
fontWeight: FontWeight.bold,
|
||
color: Color(0xFF333333),
|
||
),
|
||
),
|
||
);
|
||
}
|
||
}
|