Commit 3ca92161 authored by Adam Barth's avatar Adam Barth Committed by GitHub

Remove ScrollableGrid (#8020)

All the clients have migrated to GridView.  Also, remove RenderGrid,
which isn't needed by GridView.
parent a49c854f
......@@ -32,7 +32,6 @@ export 'src/rendering/editable_line.dart';
export 'src/rendering/error.dart';
export 'src/rendering/flex.dart';
export 'src/rendering/flow.dart';
export 'src/rendering/grid.dart';
export 'src/rendering/image.dart';
export 'src/rendering/layer.dart';
export 'src/rendering/list.dart';
......
......@@ -55,7 +55,7 @@ Map<MaterialListType, double> kListItemExtent = const <MaterialListType, double>
/// * [ScrollableList], on which this widget is based.
/// * [TwoLevelList], for lists that have subsections that can collapse and
/// expand.
/// * [ScrollableGrid]
/// * [GridView]
/// * <https://material.google.com/components/lists.html>
class MaterialList extends StatelessWidget {
/// Creates a material list.
......
// Copyright 2015 The Chromium 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/foundation.dart';
import 'dart:math' as math;
import 'dart:typed_data';
import 'box.dart';
import 'object.dart';
import 'viewport.dart';
bool _debugIsMonotonic(List<double> offsets) {
bool result = true;
assert(() {
double current = 0.0;
for (double offset in offsets) {
if (current > offset) {
result = false;
break;
}
current = offset;
}
return true;
});
return result;
}
List<double> _generateRegularOffsets(int count, double extent) {
final int length = count + 1;
final List<double> result = new Float64List(length);
for (int i = 0; i < length; ++i)
result[i] = i * extent;
return result;
}
/// Specifies the geometry of tiles in a grid.
///
/// A grid specificiation divides a fixed width and height into a certain number
/// of rows and columns, each with a specific size.
///
/// See also:
///
/// * [CustomGrid]
/// * [GridDelegate]
/// * [RenderGrid]
class GridSpecification {
/// Creates a grid specification from an explicit list of offsets.
GridSpecification.fromOffsets({
this.columnOffsets,
this.rowOffsets,
this.columnSpacing: 0.0,
this.rowSpacing: 0.0,
this.padding: EdgeInsets.zero
}) {
assert(_debugIsMonotonic(columnOffsets));
assert(_debugIsMonotonic(rowOffsets));
assert(columnSpacing != null && columnSpacing >= 0.0);
assert(rowSpacing != null && rowSpacing >= 0.0);
assert(padding != null && padding.isNonNegative);
}
/// Creates a grid specification containing a certain number of equally sized tiles.
///
/// The `tileWidth` and `tileHeight` is the horizontal and vertical
/// (respectively) extent that each child will be allocated in the grid. The
/// tiles will have [columnSpacing] space between them horizontally and
/// [rowSpacing] space between them vertically.
///
/// If the tiles are to completely fill the grid, then their size should be
/// based on the grid's padded interior and the column and row spacing.
GridSpecification.fromRegularTiles({
double tileWidth,
double tileHeight,
int columnCount,
int rowCount,
double columnSpacing: 0.0,
double rowSpacing: 0.0,
this.padding: EdgeInsets.zero
}) : columnOffsets = _generateRegularOffsets(columnCount, tileWidth + columnSpacing),
rowOffsets = _generateRegularOffsets(rowCount, tileHeight + rowSpacing),
columnSpacing = columnSpacing,
rowSpacing = rowSpacing {
assert(_debugIsMonotonic(columnOffsets));
assert(_debugIsMonotonic(rowOffsets));
assert(columnSpacing != null && columnSpacing >= 0.0);
assert(rowSpacing != null && rowSpacing >= 0.0);
assert(padding != null && padding.isNonNegative);
}
/// The offsets of the column boundaries in the grid.
///
/// The first offset is the offset of the left edge of the left-most tile
/// from the left edge of the interior of the grid's padding (0.0 if the padding
/// is EdgeOffsets.zero). The difference between successive entries is the
/// tile width plus the column spacing.
///
/// The last offset is the offset of the right edge of the right-most tile
/// from the left edge of the interior of the grid's padding (less the
/// [columnSpacing]).
///
/// If there are n columns in the grid, there should be n + 1 entries in this
/// list. The right edge of the last column is defined as columnOffsets(n), i.e.
/// the left edge of an extra column.
final List<double> columnOffsets;
/// The offsets of the row boundaries in the grid.
///
/// The first offset is the offset of the top edge of the top-most tile from
/// the top edge of the interior of the grid's padding (usually if the padding
/// is EdgeOffsets.zero). The difference between successive entries is the
/// tile height plus the row spacing
///
/// The last offset is the offset of the bottom edge of the bottom-most tile
/// from the top edge of the interior of the grid's padding. (less the
/// [rowSpacing])
///
/// If there are n rows in the grid, there should be n + 1 entries in this
/// list. The bottom edge of the last row is defined as rowOffsets(n), i.e.
/// the top edge of an extra row.
final List<double> rowOffsets;
/// The horizontal padding between columns.
final double columnSpacing;
/// The vertical padding between rows.
final double rowSpacing;
/// The interior padding of the grid.
///
/// The grid's size encloses the spaced rows and columns and is then inflated
/// by the padding.
final EdgeInsets padding;
/// The size of the grid.
Size get gridSize {
return new Size(
columnOffsets.last + padding.horizontal - columnSpacing,
rowOffsets.last + padding.vertical - rowSpacing
);
}
/// The number of columns in this grid.
int get columnCount => columnOffsets.length - 1;
/// The number of rows in this grid.
int get rowCount => rowOffsets.length - 1;
}
/// Where to place a child within a grid.
///
/// See also:
///
/// * [CustomGrid]
/// * [GridDelegate]
/// * [RenderGrid]
class GridChildPlacement {
/// Creates a placement for a child in a grid.
///
/// The [column] and [row] arguments must not be null. By default, the child
/// spans a single column and row.
GridChildPlacement({
this.column,
this.row,
this.columnSpan: 1,
this.rowSpan: 1
}) {
assert(column != null && column >= 0);
assert(row != null && row >= 0);
assert(columnSpan != null && columnSpan > 0);
assert(rowSpan != null && rowSpan > 0);
}
/// The column in which to place the child.
final int column;
/// The row in which to place the child.
final int row;
/// How many columns the child should span.
final int columnSpan;
/// How many rows the child should span.
final int rowSpan;
}
/// An abstract interface to control the layout of a [RenderGrid].
abstract class GridDelegate {
/// Override this method to control size of the columns and rows.
GridSpecification getGridSpecification(BoxConstraints constraints, int childCount);
/// Override this method to control where children are placed in the grid.
///
/// During layout, the grid calls this function for each child, passing the
/// [placementData] associated with that child as context. The returned
/// [GridChildPlacement] is then used to determine the size and position of
/// that child within the grid.
GridChildPlacement getChildPlacement(GridSpecification specification, int index, @checked Object placementData);
/// Override this method to return true when the children need to be laid out.
bool shouldRelayout(@checked GridDelegate oldDelegate) => true;
Size _getGridSize(BoxConstraints constraints, int childCount) {
return getGridSpecification(constraints, childCount).gridSize;
}
/// Insets for the entire grid.
EdgeInsets get padding => EdgeInsets.zero;
// TODO(ianh): It's a bit dubious to be using the getSize function from the delegate to
// figure out the intrinsic dimensions. We really should either not support intrinsics,
// or we should expose intrinsic delegate callbacks and throw if they're not implemented.
/// Returns the minimum width that this grid could be without failing to paint
/// its contents within itself.
///
/// Override this to provide a more efficient or more correct solution. The
/// default implementation actually instantiates a grid specification and
/// measures the grid at the given height and child count.
///
/// For more details on implementing this method, see
/// [RenderBox.computeMinIntrinsicWidth].
double getMinIntrinsicWidth(double height, int childCount) {
final double width = _getGridSize(new BoxConstraints.tightForFinite(height: height), childCount).width;
if (width.isFinite)
return width;
return 0.0;
}
/// Returns the smallest width beyond which increasing the width never
/// decreases the preferred height.
///
/// Override this to provide a more efficient or more correct solution. The
/// default implementation actually instantiates a grid specification and
/// measures the grid at the given height and child count.
///
/// For more details on implementing this method, see
/// [RenderBox.computeMaxIntrinsicWidth].
double getMaxIntrinsicWidth(double height, int childCount) {
final double width = _getGridSize(new BoxConstraints.tightForFinite(height: height), childCount).width;
if (width.isFinite)
return width;
return 0.0;
}
/// Return the minimum height that this grid could be without failing to paint
/// its contents within itself.
///
/// Override this to provide a more efficient or more correct solution. The
/// default implementation actually instantiates a grid specification and
/// measures the grid at the given height and child count.
///
/// For more details on implementing this method, see
/// [RenderBox.computeMinIntrinsicHeight].
double getMinIntrinsicHeight(double width, int childCount) {
final double height = _getGridSize(new BoxConstraints.tightForFinite(width: width), childCount).height;
if (height.isFinite)
return height;
return 0.0;
}
/// Returns the smallest height beyond which increasing the height never
/// decreases the preferred width.
///
/// Override this to provide a more efficient or more correct solution. The
/// default implementation actually instantiates a grid specification and
/// measures the grid at the given height and child count.
///
/// For more details on implementing this method, see
/// [RenderBox.computeMaxIntrinsicHeight].
double getMaxIntrinsicHeight(double width, int childCount) {
final double height = _getGridSize(new BoxConstraints.tightForFinite(width: width), childCount).height;
if (height.isFinite)
return height;
return 0.0;
}
}
/// A [GridDelegate] the places its children in order throughout the grid.
///
/// Subclasses must still provide a mechanism for sizing the grid by
/// implementing [getGridSpecification], and should also provide efficent
/// versions of the intrinsic sizing functions ([getMinIntrinsicWidth] and
/// company).
abstract class GridDelegateWithInOrderChildPlacement extends GridDelegate {
/// Initializes [columnSpacing], [rowSpacing], and [padding] for subclasses.
///
/// By default, the [columnSpacing], [rowSpacing], and [padding] are zero.
GridDelegateWithInOrderChildPlacement({
this.columnSpacing: 0.0,
this.rowSpacing: 0.0,
this.padding: EdgeInsets.zero
}) {
assert(columnSpacing != null && columnSpacing >= 0.0);
assert(rowSpacing != null && rowSpacing >= 0.0);
assert(padding != null && padding.isNonNegative);
}
/// The horizontal padding between columns.
final double columnSpacing;
/// The vertical padding between rows.
final double rowSpacing;
/// Insets for the entire grid.
@override
final EdgeInsets padding;
@override
GridChildPlacement getChildPlacement(GridSpecification specification, int index, Object placementData) {
final int columnCount = specification.columnOffsets.length - 1;
return new GridChildPlacement(
column: index % columnCount,
row: index ~/ columnCount
);
}
@override
bool shouldRelayout(GridDelegateWithInOrderChildPlacement oldDelegate) {
return columnSpacing != oldDelegate.columnSpacing
|| rowSpacing != oldDelegate.rowSpacing
|| padding != oldDelegate.padding;
}
}
/// A [GridDelegate] that divides the grid's width evenly for a fixed number of columns.
///
/// Grids using this delegate cannot validly be placed inside an unconstrained
/// horizontal space, since they attempt to divide the incoming horizontal
/// maximum width constraint.
class FixedColumnCountGridDelegate extends GridDelegateWithInOrderChildPlacement {
/// Creates a grid delegate that uses a fixed column count.
///
/// The [columnCount] argument must not be null.
FixedColumnCountGridDelegate({
@required this.columnCount,
double columnSpacing: 0.0,
double rowSpacing: 0.0,
EdgeInsets padding: EdgeInsets.zero,
this.tileAspectRatio: 1.0
}) : super(columnSpacing: columnSpacing, rowSpacing: rowSpacing, padding: padding) {
assert(columnCount != null && columnCount >= 0);
assert(tileAspectRatio != null && tileAspectRatio > 0.0);
}
/// The number of columns in the grid.
final int columnCount;
/// The ratio of the width to the height of each tile in the grid.
final double tileAspectRatio;
@override
GridSpecification getGridSpecification(BoxConstraints constraints, int childCount) {
assert(constraints.maxWidth < double.INFINITY);
final int rowCount = (childCount / columnCount).ceil();
final double interiorWidth = constraints.maxWidth - padding.horizontal;
final double columnWidth = interiorWidth / columnCount;
final double tileWidth = math.max(0.0, columnWidth - columnSpacing);
final double tileHeight = tileWidth / tileAspectRatio;
return new GridSpecification.fromRegularTiles(
tileWidth: tileWidth,
tileHeight: tileHeight,
columnCount: columnCount,
rowCount: rowCount,
columnSpacing: columnSpacing,
rowSpacing: rowSpacing,
padding: padding
);
}
@override
bool shouldRelayout(FixedColumnCountGridDelegate oldDelegate) {
return columnCount != oldDelegate.columnCount
|| tileAspectRatio != oldDelegate.tileAspectRatio
|| super.shouldRelayout(oldDelegate);
}
@override
double getMinIntrinsicWidth(double height, int childCount) {
// TODO(ianh): Strictly, this should examine the children.
return 0.0;
}
@override
double getMaxIntrinsicWidth(double height, int childCount) {
// TODO(ianh): Strictly, this should examine the children.
return 0.0;
}
@override
double getMinIntrinsicHeight(double width, int childCount) {
// TODO(ianh): Strictly, this should examine the children.
return 0.0;
}
@override
double getMaxIntrinsicHeight(double width, int childCount) {
// TODO(ianh): Strictly, this should examine the children.
return 0.0;
}
}
/// A [GridDelegate] that fills the width with a variable number of tiles.
///
/// This delegate will select a tile width that is as large as possible subject
/// to the following conditions:
///
/// - The tile width evenly divides the width of the grid.
/// - The tile width is at most [maxTileWidth].
class MaxTileWidthGridDelegate extends GridDelegateWithInOrderChildPlacement {
/// Creates a grid delegate that uses a max tile width.
///
/// The [maxTileWidth] argument must not be null.
MaxTileWidthGridDelegate({
@required this.maxTileWidth,
this.tileAspectRatio: 1.0,
double columnSpacing: 0.0,
double rowSpacing: 0.0,
EdgeInsets padding: EdgeInsets.zero
}) : super(columnSpacing: columnSpacing, rowSpacing: rowSpacing, padding: padding) {
assert(maxTileWidth != null && maxTileWidth >= 0.0);
assert(tileAspectRatio != null && tileAspectRatio > 0.0);
}
/// The maximum width of a tile in the grid.
final double maxTileWidth;
/// The ratio of the width to the height of each tile in the grid.
final double tileAspectRatio;
@override
GridSpecification getGridSpecification(BoxConstraints constraints, int childCount) {
if (!constraints.maxWidth.isFinite) {
// if we're unbounded, just shrink-wrap around a single line of tiles
return new GridSpecification.fromRegularTiles(
tileWidth: maxTileWidth,
tileHeight: maxTileWidth / tileAspectRatio,
columnCount: childCount,
rowCount: 1,
columnSpacing: columnSpacing,
rowSpacing: rowSpacing,
padding: padding
);
}
final double gridWidth = math.max(0.0, constraints.maxWidth - padding.horizontal);
// We inflate the gridWidth by columnSpacing because the columnSpacing for
// the rightmost tile in the grid doesn't actually consume space in the
// grid because the rightmost tile is flush to the right interior edge of
// the grid.
final double totalColumnExtent = gridWidth + columnSpacing;
final double maxColumnWidth = maxTileWidth + columnSpacing;
final int columnCount = (totalColumnExtent / maxColumnWidth).ceil();
final int rowCount = (childCount / columnCount).ceil();
final double columnWidth = totalColumnExtent / columnCount;
final double tileWidth = columnWidth - columnSpacing;
final double tileHeight = tileWidth / tileAspectRatio;
return new GridSpecification.fromRegularTiles(
tileWidth: tileWidth,
tileHeight: tileHeight,
columnCount: columnCount,
rowCount: rowCount,
columnSpacing: columnSpacing,
rowSpacing: rowSpacing,
padding: padding
);
}
@override
bool shouldRelayout(MaxTileWidthGridDelegate oldDelegate) {
return maxTileWidth != oldDelegate.maxTileWidth
|| tileAspectRatio != oldDelegate.tileAspectRatio
|| super.shouldRelayout(oldDelegate);
}
@override
double getMinIntrinsicWidth(double height, int childCount) {
// TODO(ianh): Strictly, this should examine the children.
return 0.0;
}
@override
double getMaxIntrinsicWidth(double height, int childCount) {
return maxTileWidth * childCount;
}
// TODO(ianh): Provide efficient intrinsic height functions.
}
/// Parent data for use with [RenderGrid]
class GridParentData extends ContainerBoxParentDataMixin<RenderBox> {
/// Opaque data passed to the [GridDelegate.getChildPlacement] method of the grid's [GridDelegate].
Object placementData;
@override
String toString() => '${super.toString()}; placementData=$placementData';
}
/// Implements the grid layout algorithm
///
/// In grid layout, children are arranged into rows and columns in on a two
/// dimensional grid. The [GridDelegate] determines how to arrange the
/// children on the grid.
///
/// The arrangment of rows and columns in the grid cannot depend on the contents
/// of the tiles in the grid, which makes grid layout most useful for images and
/// card-like layouts rather than for document-like layouts that adjust to the
/// amount of text contained in the tiles.
///
/// Additionally, grid layout materializes all of its children, which makes it
/// most useful for grids containing a moderate number of tiles.
class RenderGrid extends RenderVirtualViewport<GridParentData> {
/// Creates a grid render object.
///
/// The [delegate] argument must not be null.
RenderGrid({
List<RenderBox> children,
GridDelegate delegate,
int virtualChildBase: 0,
int virtualChildCount,
Offset paintOffset: Offset.zero,
LayoutCallback<BoxConstraints> callback
}) : _delegate = delegate, _virtualChildBase = virtualChildBase, super(
virtualChildCount: virtualChildCount,
paintOffset: paintOffset,
callback: callback
) {
assert(delegate != null);
addAll(children);
}
/// The delegate that controls the layout of the children.
///
/// For example, a [FixedColumnCountGridDelegate] for grids that have a fixed
/// number of columns or a [MaxTileWidthGridDelegate] for grids that have a
/// maximum tile width.
///
/// If the new delegate is the same as the previous one, this does nothing.
///
/// If the new delegate is the same class as the previous one, then the new
/// delegate has its [GridDelegate.shouldRelayout] called; if the result is
/// true, then the delegate will be called.
///
/// If the new delegate is a different class than the previous one, then the
/// delegate will be called.
///
/// The delegate must not be null.
GridDelegate get delegate => _delegate;
GridDelegate _delegate;
set delegate (GridDelegate newDelegate) {
assert(newDelegate != null);
if (_delegate == newDelegate)
return;
if (newDelegate.runtimeType != _delegate.runtimeType || newDelegate.shouldRelayout(_delegate)) {
_specification = null;
markNeedsLayout();
}
_delegate = newDelegate;
}
@override
set mainAxis(Axis value) {
assert(() {
if (value != Axis.vertical)
throw new FlutterError('RenderGrid doesn\'t yet support horizontal scrolling.');
return true;
});
super.mainAxis = value;
}
@override
int get virtualChildCount => super.virtualChildCount ?? childCount;
/// The virtual index of the first child.
///
/// When asking the delegate for the position of each child, the grid will add
/// the virtual child i to the indices of its children.
int get virtualChildBase => _virtualChildBase;
int _virtualChildBase;
set virtualChildBase(int value) {
assert(value != null);
if (_virtualChildBase == value)
return;
_virtualChildBase = value;
markNeedsLayout();
}
@override
void setupParentData(RenderBox child) {
if (child.parentData is! GridParentData)
child.parentData = new GridParentData();
}
@override
double computeMinIntrinsicWidth(double height) {
return _delegate.getMinIntrinsicWidth(height, virtualChildCount);
}
@override
double computeMaxIntrinsicWidth(double height) {
return _delegate.getMaxIntrinsicWidth(height, virtualChildCount);
}
@override
double computeMinIntrinsicHeight(double width) {
return _delegate.getMinIntrinsicHeight(width, virtualChildCount);
}
@override
double computeMaxIntrinsicHeight(double width) {
return _delegate.getMaxIntrinsicHeight(width, virtualChildCount);
}
@override
double computeDistanceToActualBaseline(TextBaseline baseline) {
return defaultComputeDistanceToHighestActualBaseline(baseline);
}
/// The specification of this grid.
///
/// The grid specification cannot be set directly. Instead, set a [delegate]
/// to control the specification.
GridSpecification get specification => _specification;
GridSpecification _specification;
int _specificationChildCount;
BoxConstraints _specificationConstraints;
void _updateGridSpecification() {
if (_specification == null
|| _specificationChildCount != virtualChildCount
|| _specificationConstraints != constraints) {
_specification = delegate.getGridSpecification(constraints, virtualChildCount);
_specificationChildCount = virtualChildCount;
_specificationConstraints = constraints;
}
}
@override
void performLayout() {
_updateGridSpecification();
final Size gridSize = _specification.gridSize;
size = constraints.constrain(gridSize);
if (callback != null)
invokeLayoutCallback<BoxConstraints>(callback);
double gridTopPadding = 0.0;
double gridLeftPadding = 0.0;
switch (mainAxis) {
case Axis.vertical:
gridLeftPadding = _specification.padding.left;
break;
case Axis.horizontal:
gridTopPadding = _specification.padding.top;
break;
}
int childIndex = virtualChildBase;
RenderBox child = firstChild;
while (child != null) {
final GridParentData childParentData = child.parentData;
GridChildPlacement placement = delegate.getChildPlacement(_specification, childIndex, childParentData.placementData);
assert(placement.column >= 0);
assert(placement.row >= 0);
assert(placement.column + placement.columnSpan < _specification.columnOffsets.length);
assert(placement.row + placement.rowSpan < _specification.rowOffsets.length);
final double tileLeft = gridLeftPadding + _specification.columnOffsets[placement.column];
final double tileRight = gridLeftPadding + _specification.columnOffsets[placement.column + placement.columnSpan] - _specification.columnSpacing;
final double tileTop = gridTopPadding + _specification.rowOffsets[placement.row];
final double tileBottom = gridTopPadding + _specification.rowOffsets[placement.row + placement.rowSpan] - _specification.rowSpacing;
final double childWidth = math.max(0.0, tileRight - tileLeft);
final double childHeight = math.max(0.0, tileBottom - tileTop);
child.layout(new BoxConstraints(
minWidth: childWidth,
maxWidth: childWidth,
minHeight: childHeight,
maxHeight: childHeight
));
childParentData.offset = new Offset(tileLeft, tileTop);
childIndex += 1;
assert(child.parentData == childParentData);
child = childParentData.nextSibling;
}
}
}
......@@ -19,19 +19,13 @@ export 'package:flutter/rendering.dart' show
CrossAxisAlignment,
CustomClipper,
CustomPainter,
FixedColumnCountGridDelegate,
FlexFit,
FlowDelegate,
FlowPaintingContext,
FractionalOffsetTween,
GridChildPlacement,
GridDelegate,
GridDelegateWithInOrderChildPlacement,
GridSpecification,
HitTestBehavior,
MainAxisAlignment,
MainAxisSize,
MaxTileWidthGridDelegate,
MultiChildLayoutDelegate,
Overflow,
PaintingContext,
......
......@@ -222,7 +222,7 @@ class LazyBlock extends StatelessWidget {
}
// Warning: keep the dartdoc comments that follow in sync with the copies in
// Scrollable, ScrollableGrid, ScrollableViewport, ScrollableList, and
// Scrollable, ScrollableViewport, ScrollableList, and
// ScrollableLazyList. And see: https://github.com/dart-lang/dartdoc/issues/1161.
/// The scroll offset this widget should use when first created.
......
......@@ -375,8 +375,8 @@ typedef double SnapOffsetCallback(double scrollOffset, Size containerSize);
/// If you have a list of widgets and want them to be able to scroll if there is
/// insufficient room, consider using [Block].
///
/// Commonly used classes that are based on Scrollable include [ScrollableList],
/// [ScrollableGrid], and [ScrollableViewport].
/// Commonly used classes that are based on Scrollable include [ScrollableList]
/// and [ScrollableViewport].
///
/// Widgets that subclass [Scrollable] typically use state objects that subclass
/// [ScrollableState].
......@@ -400,8 +400,8 @@ class Scrollable extends StatefulWidget {
}
// Warning: keep the dartdoc comments that follow in sync with the copies in
// ScrollableViewport, LazyBlock, ScrollableLazyList, ScrollableList, and
// ScrollableGrid. And see: https://github.com/dart-lang/dartdoc/issues/1161.
// ScrollableViewport and LazyBlock.
// And see: https://github.com/dart-lang/dartdoc/issues/1161.
/// The scroll offset this widget should use when first created.
final double initialScrollOffset;
......@@ -1244,7 +1244,7 @@ class ScrollNotification extends Notification {
///
/// * [Block], if your single child is a [Column].
/// * [ScrollableList], if you have many identically-sized children.
/// * [ScrollableGrid], if your children are in a grid pattern.
/// * [GridView], if your children are in a grid pattern.
/// * [LazyBlock], if you have many children of varying sizes.
class ScrollableViewport extends StatelessWidget {
/// Creates a simple scrolling widget that has a single child.
......@@ -1267,8 +1267,8 @@ class ScrollableViewport extends StatelessWidget {
}
// Warning: keep the dartdoc comments that follow in sync with the copies in
// Scrollable, LazyBlock, ScrollableLazyList, ScrollableList, and
// ScrollableGrid. And see: https://github.com/dart-lang/dartdoc/issues/1161.
// Scrollable, LazyBlock, ScrollableLazyList, and ScrollableList.
// And see: https://github.com/dart-lang/dartdoc/issues/1161.
/// The scroll offset this widget should use when first created.
final double initialScrollOffset;
......
// Copyright 2015 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
////////////////////////////////////////////////////////////////////////////////
// DELETE THIS FILE WHEN REMOVING LEGACY SCROLLING CODE
////////////////////////////////////////////////////////////////////////////////
import 'dart:math' as math;
import 'package:collection/collection.dart' show lowerBound;
import 'package:flutter/foundation.dart';
import 'package:flutter/rendering.dart';
import 'framework.dart';
import 'scroll_configuration.dart';
import 'scrollable.dart';
import 'virtual_viewport.dart';
/// A vertically scrollable grid.
///
/// Requires that [delegate] places its children in row-major order.
///
/// See also:
///
/// * [CustomGrid].
/// * [ScrollableList].
/// * [ScrollableViewport].
class ScrollableGrid extends StatelessWidget {
/// Creates a vertically scrollable grid.
///
/// The [delegate] argument must not be null.
ScrollableGrid({
Key key,
this.initialScrollOffset,
this.onScrollStart,
this.onScroll,
this.onScrollEnd,
this.snapOffsetCallback,
this.scrollableKey,
@required this.delegate,
this.children: const <Widget>[],
}) : super(key: key) {
assert(delegate != null);
}
// Warning: keep the dartdoc comments that follow in sync with the copies in
// Scrollable, LazyBlock, ScrollableViewport, ScrollableList, and
// ScrollableLazyList. And see: https://github.com/dart-lang/dartdoc/issues/1161.
/// The scroll offset this widget should use when first created.
final double initialScrollOffset;
/// Called whenever this widget starts to scroll.
final ScrollListener onScrollStart;
/// Called whenever this widget's scroll offset changes.
final ScrollListener onScroll;
/// Called whenever this widget stops scrolling.
final ScrollListener onScrollEnd;
/// Called to determine the offset to which scrolling should snap,
/// when handling a fling.
///
/// This callback, if set, will be called with the offset that the
/// Scrollable would have scrolled to in the absence of this
/// callback, and a Size describing the size of the Scrollable
/// itself.
///
/// The callback's return value is used as the new scroll offset to
/// aim for.
///
/// If the callback simply returns its first argument (the offset),
/// then it is as if the callback was null.
final SnapOffsetCallback snapOffsetCallback;
/// The key for the Scrollable created by this widget.
final Key scrollableKey;
/// The delegate that controls the layout of the children.
///
/// For example, a [FixedColumnCountGridDelegate] for grids that have a fixed
/// number of columns or a [MaxTileWidthGridDelegate] for grids that have a
/// maximum tile width.
final GridDelegate delegate;
/// The children that will be placed in the grid.
final Iterable<Widget> children;
Widget _buildViewport(BuildContext context, ScrollableState state) {
return new GridViewport(
scrollOffset: state.scrollOffset,
delegate: delegate,
onExtentsChanged: state.handleExtentsChanged,
children: children
);
}
@override
Widget build(BuildContext context) {
final Widget result = new Scrollable(
key: scrollableKey,
initialScrollOffset: initialScrollOffset,
// TODO(abarth): Support horizontal offsets. For horizontally scrolling
// grids. For horizontally scrolling grids, we'll probably need to use a
// delegate that places children in column-major order.
scrollDirection: Axis.vertical,
onScrollStart: onScrollStart,
onScroll: onScroll,
onScrollEnd: onScrollEnd,
snapOffsetCallback: snapOffsetCallback,
builder: _buildViewport,
);
return ScrollConfiguration.wrap(context, result);
}
}
/// A virtual viewport onto a grid of widgets.
///
/// Used by [ScrollableGrid].
///
/// See also:
///
/// * [ListViewport].
/// * [LazyListViewport].
class GridViewport extends VirtualViewportFromIterable {
/// Creates a virtual viewport onto a grid of widgets.
///
/// The [delegate] argument must not be null.
GridViewport({
this.scrollOffset,
this.delegate,
this.onExtentsChanged,
this.children: const <Widget>[],
}) {
assert(delegate != null);
}
/// The [startOffset] without taking the [delegate]'s padding into account.
final double scrollOffset;
@override
double get startOffset {
if (delegate == null)
return scrollOffset;
return scrollOffset - delegate.padding.top;
}
/// The delegate that controls the layout of the children.
///
/// For example, a [FixedColumnCountGridDelegate] for grids that have a fixed
/// number of columns or a [MaxTileWidthGridDelegate] for grids that have a
/// maximum tile width.
final GridDelegate delegate;
/// Called when the interior or exterior dimensions of the viewport change.
final ExtentsChangedCallback onExtentsChanged;
@override
final Iterable<Widget> children;
@override
RenderGrid createRenderObject(BuildContext context) => new RenderGrid(delegate: delegate);
@override
_GridViewportElement createElement() => new _GridViewportElement(this);
}
class _GridViewportElement extends VirtualViewportElement {
_GridViewportElement(GridViewport widget) : super(widget);
@override
GridViewport get widget => super.widget;
@override
RenderGrid get renderObject => super.renderObject;
@override
int get materializedChildBase => _materializedChildBase;
int _materializedChildBase;
@override
int get materializedChildCount => _materializedChildCount;
int _materializedChildCount;
@override
double get startOffsetBase => _startOffsetBase;
double _startOffsetBase;
@override
double get startOffsetLimit =>_startOffsetLimit;
double _startOffsetLimit;
@override
void updateRenderObject(GridViewport oldWidget) {
renderObject.delegate = widget.delegate;
super.updateRenderObject(oldWidget);
}
double _lastReportedContentExtent;
double _lastReportedContainerExtent;
GridSpecification _specification;
@override
void layout(BoxConstraints constraints) {
_specification = renderObject.specification;
double contentExtent = _specification.gridSize.height;
double containerExtent = renderObject.size.height;
int materializedRowBase = math.max(0, lowerBound(_specification.rowOffsets, widget.startOffset) - 1);
int materializedRowLimit = math.min(_specification.rowCount, lowerBound(_specification.rowOffsets, widget.startOffset + containerExtent));
_materializedChildBase = (materializedRowBase * _specification.columnCount).clamp(0, renderObject.virtualChildCount);
_materializedChildCount = (materializedRowLimit * _specification.columnCount).clamp(0, renderObject.virtualChildCount) - _materializedChildBase;
_startOffsetBase = _specification.rowOffsets[materializedRowBase];
_startOffsetLimit = _specification.rowOffsets[materializedRowLimit] - containerExtent;
super.layout(constraints);
if (contentExtent != _lastReportedContentExtent || containerExtent != _lastReportedContainerExtent) {
_lastReportedContentExtent = contentExtent;
_lastReportedContainerExtent = containerExtent;
widget.onExtentsChanged(_lastReportedContentExtent, _lastReportedContainerExtent);
}
}
}
......@@ -61,8 +61,8 @@ class ScrollableList extends StatelessWidget {
}
// Warning: keep the dartdoc comments that follow in sync with the copies in
// Scrollable, LazyBlock, ScrollableLazyList, ScrollableViewport, and
// ScrollableGrid. And see: https://github.com/dart-lang/dartdoc/issues/1161.
// Scrollable, LazyBlock, ScrollableLazyList, ScrollableViewport.
// And see: https://github.com/dart-lang/dartdoc/issues/1161.
/// The scroll offset this widget should use when first created.
final double initialScrollOffset;
......@@ -450,8 +450,8 @@ class ScrollableLazyList extends StatelessWidget {
}
// Warning: keep the dartdoc comments that follow in sync with the copies in
// Scrollable, LazyBlock, ScrollableViewport, ScrollableList, and
// ScrollableGrid. And see: https://github.com/dart-lang/dartdoc/issues/1161.
// Scrollable, LazyBlock, ScrollableViewport, and ScrollableList.
// And see: https://github.com/dart-lang/dartdoc/issues/1161.
/// The scroll offset this widget should use when first created.
final double initialScrollOffset;
......
......@@ -23,8 +23,7 @@ import 'framework.dart';
/// the amount of the thing inside the viewport that is visible from outside
/// the viewport).
///
/// Used by [ScrollableGrid.onExtentsChanged],
/// [ScrollableList.onExtentsChanged], etc.
/// Used by [ScrollableList.onExtentsChanged], etc.
typedef void ExtentsChangedCallback(double contentExtent, double containerExtent);
/// An abstract widget whose children are not all materialized.
......
......@@ -55,7 +55,6 @@ export 'src/widgets/scroll_position.dart';
export 'src/widgets/scroll_simulation.dart';
export 'src/widgets/scroll_view.dart';
export 'src/widgets/scrollable.dart';
export 'src/widgets/scrollable_grid.dart';
export 'src/widgets/scrollable_list.dart';
export 'src/widgets/semantics_debugger.dart';
export 'src/widgets/single_child_scroll_view.dart';
......
// Copyright 2015 The Chromium 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:test/test.dart';
import 'rendering_tester.dart';
void main() {
test('Basic grid layout test', () {
List<RenderBox> children = <RenderBox>[
new RenderDecoratedBox(decoration: const BoxDecoration()),
new RenderDecoratedBox(decoration: const BoxDecoration()),
new RenderDecoratedBox(decoration: const BoxDecoration()),
new RenderDecoratedBox(decoration: const BoxDecoration())
];
RenderGrid grid = new RenderGrid(
children: children,
delegate: new MaxTileWidthGridDelegate(maxTileWidth: 100.0)
);
layout(grid, constraints: const BoxConstraints(maxWidth: 200.0));
children.forEach((RenderBox child) {
expect(child.size.width, equals(100.0), reason: "child width");
expect(child.size.height, equals(100.0), reason: "child height");
});
expect(grid.size.width, equals(200.0), reason: "grid width");
expect(grid.size.height, equals(200.0), reason: "grid height");
expect(grid.debugNeedsLayout, false);
grid.delegate = new MaxTileWidthGridDelegate(maxTileWidth: 60.0);
expect(grid.debugNeedsLayout, true);
pumpFrame();
children.forEach((RenderBox child) {
expect(child.size.width, equals(50.0), reason: "child width");
expect(child.size.height, equals(50.0), reason: "child height");
});
expect(grid.size.width, equals(200.0), reason: "grid width");
expect(grid.size.height, equals(50.0), reason: "grid height");
});
}
// Copyright 2015 The Chromium 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/widgets.dart';
import 'package:flutter_test/flutter_test.dart';
void main() {
testWidgets('Empty GridView', (WidgetTester tester) async {
List<Widget> children = <Widget>[
new DecoratedBox(decoration: const BoxDecoration()),
new DecoratedBox(decoration: const BoxDecoration()),
new DecoratedBox(decoration: const BoxDecoration()),
new DecoratedBox(decoration: const BoxDecoration())
];
await tester.pumpWidget(new Center(
child: new Container(
width: 200.0,
child: new GridView.extent(
maxCrossAxisExtent: 100.0,
shrinkWrap: true,
children: children,
),
),
));
children.forEach((Widget child) {
RenderBox box = tester.renderObject(find.byConfig(child));
expect(box.size.width, equals(100.0), reason: "child width");
expect(box.size.height, equals(100.0), reason: "child height");
});
RenderBox grid = tester.renderObject(find.byType(GridView));
expect(grid.size.width, equals(200.0), reason: "grid width");
expect(grid.size.height, equals(200.0), reason: "grid height");
expect(grid.debugNeedsLayout, false);
await tester.pumpWidget(new Center(
child: new Container(
width: 200.0,
child: new GridView.extent(
maxCrossAxisExtent: 60.0,
shrinkWrap: true,
children: children,
),
),
));
children.forEach((Widget child) {
RenderBox box = tester.renderObject(find.byConfig(child));
expect(box.size.width, equals(50.0), reason: "child width");
expect(box.size.height, equals(50.0), reason: "child height");
});
expect(grid.size.width, equals(200.0), reason: "grid width");
expect(grid.size.height, equals(50.0), reason: "grid height");
});
}
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