mirror of
https://github.com/flutter/flutter.git
synced 2025-06-03 00:51:18 +00:00
191 lines
7.2 KiB
Dart
191 lines
7.2 KiB
Dart
// 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;
|
|
}
|
|
}
|