forked from flutter/flutter
-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
GridView sample code (flutter#131900)
- Loading branch information
Showing
4 changed files
with
278 additions
and
16 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,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; | ||
} | ||
} |
32 changes: 32 additions & 0 deletions
32
examples/api/test/widgets/scroll_view/grid_view.0_test.dart
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,32 @@ | ||
// 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 'package:flutter/rendering.dart'; | ||
import 'package:flutter_api_samples/widgets/scroll_view/grid_view.0.dart'; | ||
import 'package:flutter_test/flutter_test.dart'; | ||
|
||
void main() { | ||
testWidgets('$CustomGridLayout', (WidgetTester tester) async { | ||
const CustomGridLayout layout = CustomGridLayout( | ||
crossAxisCount: 2, | ||
fullRowPeriod: 3, | ||
dimension: 100, | ||
); | ||
final List<double> scrollOffsets = List<double>.generate(10, (int i) => layout.computeMaxScrollOffset(i)); | ||
expect(scrollOffsets, <double>[0.0, 0.0, 100.0, 100.0, 200.0, 300.0, 300.0, 400.0, 400.0, 500.0]); | ||
final List<int> minOffsets = List<int>.generate(10, (int i) => layout.getMinChildIndexForScrollOffset(i * 80.0)); | ||
expect(minOffsets, <int>[0, 0, 2, 4, 5, 7, 7, 9, 10, 12]); | ||
final List<int> maxOffsets = List<int>.generate(10, (int i) => layout.getMaxChildIndexForScrollOffset(i * 80.0)); | ||
expect(maxOffsets, <double>[1, 1, 3, 4, 6, 8, 8, 9, 11, 13]); | ||
final List<SliverGridGeometry> offsets = List<SliverGridGeometry>.generate(20, (int i) => layout.getGeometryForChildIndex(i)); | ||
offsets.reduce((SliverGridGeometry a, SliverGridGeometry b) { | ||
if (a.scrollOffset == b.scrollOffset) { | ||
expect(a.crossAxisOffset, lessThan(b.crossAxisOffset)); | ||
} else { | ||
expect(a.scrollOffset, lessThan(b.scrollOffset)); | ||
} | ||
return b; | ||
}); | ||
}); | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters