Unverified Commit 4d42f1a8 authored by Ian Hickson's avatar Ian Hickson Committed by GitHub

GridView sample code (#131900)

parent 60634c65
// 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;
}
}
// 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;
});
});
}
......@@ -13,6 +13,16 @@ import 'sliver_multi_box_adaptor.dart';
/// Describes the placement of a child in a [RenderSliverGrid].
///
/// This class is similar to [Rect], in that it gives a two-dimensional position
/// and a two-dimensional dimension, but is direction-agnostic.
///
/// {@tool dartpad}
/// This example shows how a custom [SliverGridLayout] uses [SliverGridGeometry]
/// to lay out the children.
///
/// ** See code in examples/api/lib/widgets/scroll_view/grid_view.0.dart **
/// {@end-tool}
///
/// See also:
///
/// * [SliverGridLayout], which represents the geometry of all the tiles in a
......@@ -60,7 +70,7 @@ class SliverGridGeometry {
double get trailingScrollOffset => scrollOffset + mainAxisExtent;
/// Returns a tight [BoxConstraints] that forces the child to have the
/// required size.
/// required size, given a [SliverConstraints].
BoxConstraints getBoxConstraints(SliverConstraints constraints) {
return constraints.asBoxConstraints(
minExtent: mainAxisExtent,
......@@ -83,13 +93,22 @@ class SliverGridGeometry {
/// The size and position of all the tiles in a [RenderSliverGrid].
///
/// Rather that providing a grid with a [SliverGridLayout] directly, you instead
/// provide the grid a [SliverGridDelegate], which can compute a
/// [SliverGridLayout] given the current [SliverConstraints].
/// Rather that providing a grid with a [SliverGridLayout] directly, the grid is
/// provided a [SliverGridDelegate], which computes a [SliverGridLayout] given a
/// set of [SliverConstraints]. This allows the algorithm to dynamically respond
/// to changes in the environment (e.g. the user rotating the device).
///
/// The tiles can be placed arbitrarily, but it is more efficient to place tiles
/// in roughly in order by scroll offset because grids reify a contiguous
/// sequence of children.
/// roughly in order by scroll offset because grids reify a contiguous sequence
/// of children.
///
/// {@tool dartpad}
/// This example shows how to construct a custom [SliverGridLayout] to lay tiles
/// in a grid form with some cells stretched to fit the entire width of the
/// grid (sometimes called "hero tiles").
///
/// ** See code in examples/api/lib/widgets/scroll_view/grid_view.0.dart **
/// {@end-tool}
///
/// See also:
///
......@@ -240,9 +259,16 @@ class SliverGridRegularTileLayout extends SliverGridLayout {
///
/// Given the current constraints on the grid, a [SliverGridDelegate] computes
/// the layout for the tiles in the grid. The tiles can be placed arbitrarily,
/// but it is more efficient to place tiles in roughly in order by scroll offset
/// but it is more efficient to place tiles roughly in order by scroll offset
/// because grids reify a contiguous sequence of children.
///
/// {@tool dartpad}
/// This example shows how a [SliverGridDelegate] returns a [SliverGridLayout]
/// configured based on the provided [SliverConstraints] in [getLayout].
///
/// ** See code in examples/api/lib/widgets/scroll_view/grid_view.0.dart **
/// {@end-tool}
///
/// See also:
///
/// * [SliverGridDelegateWithFixedCrossAxisCount], which creates a layout with
......
......@@ -1681,6 +1681,10 @@ class ListView extends BoxScrollView {
/// [SliverList] or [SliverAppBar], can be put in the [CustomScrollView.slivers]
/// list.
///
/// {@macro flutter.widgets.ScrollView.PageStorage}
///
/// ## Examples
///
/// {@tool snippet}
/// This example demonstrates how to create a [GridView] with two columns. The
/// children are spaced apart using the `crossAxisSpacing` and `mainAxisSpacing`
......@@ -1786,6 +1790,25 @@ class ListView extends BoxScrollView {
/// ```
/// {@end-tool}
///
/// {@tool dartpad}
/// This example shows a custom implementation of selection in list and grid views.
/// Use the button in the top right (possibly hidden under the DEBUG banner) to toggle between
/// [ListView] and [GridView].
/// Long press any [ListTile] or [GridTile] to enable selection mode.
///
/// ** See code in examples/api/lib/widgets/scroll_view/list_view.0.dart **
/// {@end-tool}
///
/// {@tool dartpad}
/// This example shows a custom [SliverGridDelegate].
///
/// ** See code in examples/api/lib/widgets/scroll_view/grid_view.0.dart **
/// {@end-tool}
///
/// ## Troubleshooting
///
/// ### Padding
///
/// By default, [GridView] will automatically pad the limits of the
/// grid's scrollable to avoid partial obstructions indicated by
/// [MediaQuery]'s padding. To avoid this behavior, override with a
......@@ -1817,15 +1840,6 @@ class ListView extends BoxScrollView {
/// ```
/// {@end-tool}
///
/// {@tool dartpad}
/// This example shows a custom implementation of [ListTile] selection in a [GridView] or [ListView].
/// Long press any ListTile to enable selection mode.
///
/// ** See code in examples/api/lib/widgets/scroll_view/list_view.0.dart **
/// {@end-tool}
///
/// {@macro flutter.widgets.ScrollView.PageStorage}
///
/// See also:
///
/// * [SingleChildScrollView], which is a scrollable widget that has a single
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment