| // 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 .all(20.0), | |
| child: Card( | |
| elevation: 8.0, | |
| child: GridView.builder( | |
| padding: const .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 .all(12.0), | |
| decoration: ShapeDecoration( | |
| shape: RoundedRectangleBorder( | |
| 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; | |
| } | |
| } |