mirror of
https://github.com/flutter/flutter.git
synced 2025-06-03 00:51:18 +00:00
GridView sample code (#131900)
This commit is contained in:
parent
60634c65b2
commit
4d42f1a852
190
examples/api/lib/widgets/scroll_view/grid_view.0.dart
Normal file
190
examples/api/lib/widgets/scroll_view/grid_view.0.dart
Normal file
@ -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
examples/api/test/widgets/scroll_view/grid_view.0_test.dart
Normal file
32
examples/api/test/widgets/scroll_view/grid_view.0_test.dart
Normal file
@ -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;
|
||||
});
|
||||
});
|
||||
}
|
@ -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
|
||||
|
Loading…
Reference in New Issue
Block a user