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