1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
// Copyright 2014 The Flutter Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
import 'dart:math' as math;
import 'package:flutter/material.dart';
import 'package:flutter/rendering.dart';
void main() => runApp(const GridViewExampleApp());
class GridViewExampleApp extends StatelessWidget {
const GridViewExampleApp({super.key});
@override
Widget build(BuildContext context) {
return MaterialApp(
home: Padding(
padding: const EdgeInsets.all(20.0),
child: Card(
elevation: 8.0,
child: GridView.builder(
padding: const EdgeInsets.all(12.0),
gridDelegate: CustomGridDelegate(dimension: 240.0),
// Try uncommenting some of these properties to see the effect on the grid:
// itemCount: 20, // The default is that the number of grid tiles is infinite.
// scrollDirection: Axis.horizontal, // The default is vertical.
// reverse: true, // The default is false, going down (or left to right).
itemBuilder: (BuildContext context, int index) {
final math.Random random = math.Random(index);
return GridTile(
header: GridTileBar(
title: Text('$index', style: const TextStyle(color: Colors.black)),
),
child: Container(
margin: const EdgeInsets.all(12.0),
decoration: ShapeDecoration(
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(12.0),
),
gradient: const RadialGradient(
colors: <Color>[ Color(0x0F88EEFF), Color(0x2F0099BB) ],
),
),
child: FlutterLogo(
style: FlutterLogoStyle.values[random.nextInt(FlutterLogoStyle.values.length)],
),
),
);
},
),
),
),
);
}
}
class CustomGridDelegate extends SliverGridDelegate {
CustomGridDelegate({ required this.dimension });
// This is the desired height of each row (and width of each square).
// When there is not enough room, we shrink this to the width of the scroll view.
final double dimension;
// The layout is two rows of squares, then one very wide cell, repeat.
@override
SliverGridLayout getLayout(SliverConstraints constraints) {
// Determine how many squares we can fit per row.
int count = constraints.crossAxisExtent ~/ dimension;
if (count < 1) {
count = 1; // Always fit at least one regardless.
}
final double squareDimension = constraints.crossAxisExtent / count;
return CustomGridLayout(
crossAxisCount: count,
fullRowPeriod: 3, // Number of rows per block (one of which is the full row).
dimension: squareDimension,
);
}
@override
bool shouldRelayout(CustomGridDelegate oldDelegate) {
return dimension != oldDelegate.dimension;
}
}
class CustomGridLayout extends SliverGridLayout {
const CustomGridLayout({
required this.crossAxisCount,
required this.dimension,
required this.fullRowPeriod,
}) : assert(crossAxisCount > 0),
assert(fullRowPeriod > 1),
loopLength = crossAxisCount * (fullRowPeriod - 1) + 1,
loopHeight = fullRowPeriod * dimension;
final int crossAxisCount;
final double dimension;
final int fullRowPeriod;
// Computed values.
final int loopLength;
final double loopHeight;
@override
double computeMaxScrollOffset(int childCount) {
// This returns the scroll offset of the end side of the childCount'th child.
// In the case of this example, this method is not used, since the grid is
// infinite. However, if one set an itemCount on the GridView above, this
// function would be used to determine how far to allow the user to scroll.
if (childCount == 0 || dimension == 0) {
return 0;
}
return (childCount ~/ loopLength) * loopHeight
+ ((childCount % loopLength) ~/ crossAxisCount) * dimension;
}
@override
SliverGridGeometry getGeometryForChildIndex(int index) {
// This returns the position of the index'th tile.
//
// The SliverGridGeometry object returned from this method has four
// properties. For a grid that scrolls down, as in this example, the four
// properties are equivalent to x,y,width,height. However, since the
// GridView is direction agnostic, the names used for SliverGridGeometry are
// also direction-agnostic.
//
// Try changing the scrollDirection and reverse properties on the GridView
// to see how this algorithm works in any direction (and why, therefore, the
// names are direction-agnostic).
final int loop = index ~/ loopLength;
final int loopIndex = index % loopLength;
if (loopIndex == loopLength - 1) {
// Full width case.
return SliverGridGeometry(
scrollOffset: (loop + 1) * loopHeight - dimension, // "y"
crossAxisOffset: 0, // "x"
mainAxisExtent: dimension, // "height"
crossAxisExtent: crossAxisCount * dimension, // "width"
);
}
// Square case.
final int rowIndex = loopIndex ~/ crossAxisCount;
final int columnIndex = loopIndex % crossAxisCount;
return SliverGridGeometry(
scrollOffset: (loop * loopHeight) + (rowIndex * dimension), // "y"
crossAxisOffset: columnIndex * dimension, // "x"
mainAxisExtent: dimension, // "height"
crossAxisExtent: dimension, // "width"
);
}
@override
int getMinChildIndexForScrollOffset(double scrollOffset) {
// This returns the first index that is visible for a given scrollOffset.
//
// The GridView only asks for the geometry of children that are visible
// between the scroll offset passed to getMinChildIndexForScrollOffset and
// the scroll offset passed to getMaxChildIndexForScrollOffset.
//
// It is the responsibility of the SliverGridLayout to ensure that
// getGeometryForChildIndex is consistent with getMinChildIndexForScrollOffset
// and getMaxChildIndexForScrollOffset.
//
// Not every child between the minimum child index and the maximum child
// index need be visible (some may have scroll offsets that are outside the
// view; this happens commonly when the grid view places tiles out of
// order). However, doing this means the grid view is less efficient, as it
// will do work for children that are not visible. It is preferred that the
// children are returned in the order that they are laid out.
final int rows = scrollOffset ~/ dimension;
final int loops = rows ~/ fullRowPeriod;
final int extra = rows % fullRowPeriod;
return loops * loopLength + extra * crossAxisCount;
}
@override
int getMaxChildIndexForScrollOffset(double scrollOffset) {
// (See commentary above.)
final int rows = scrollOffset ~/ dimension;
final int loops = rows ~/ fullRowPeriod;
final int extra = rows % fullRowPeriod;
final int count = loops * loopLength + extra * crossAxisCount;
if (extra == fullRowPeriod - 1) {
return count;
}
return count + crossAxisCount - 1;
}
}