// 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; } }