Commit 46a178dc authored by Adam Barth's avatar Adam Barth

Generalize grid layout

This patch make grid layout much more flexible. The behavior is factored
out into a GridDelegate that's modeled after the custom layout
delegates. The patch includes a MaxTileWidthGridDelegate that implements
the old behavior and a FixedColumnCountGridDelegate that implements a
grid layout with a fixed number of columns.

Fixes #1048
parent a5925149
...@@ -20,7 +20,10 @@ Color randomColor() { ...@@ -20,7 +20,10 @@ Color randomColor() {
RenderBox buildGridExample() { RenderBox buildGridExample() {
List<RenderBox> children = new List<RenderBox>.generate(30, (_) => new RenderSolidColorBox(randomColor())); List<RenderBox> children = new List<RenderBox>.generate(30, (_) => new RenderSolidColorBox(randomColor()));
return new RenderGrid(children: children, maxChildExtent: 100.0); return new RenderGrid(
children: children,
delegate: new MaxTileWidthGridDelegate(maxTileWidth: 100.0)
);
} }
main() => new RenderingFlutterBinding(root: buildGridExample()); main() => new RenderingFlutterBinding(root: buildGridExample());
...@@ -62,7 +62,7 @@ class AdaptiveItem { ...@@ -62,7 +62,7 @@ class AdaptiveItem {
} }
class MediaQueryExample extends StatelessComponent { class MediaQueryExample extends StatelessComponent {
static const double _maxChildExtent = 150.0; static const double _maxTileWidth = 150.0;
static const double _gridViewBreakpoint = 450.0; static const double _gridViewBreakpoint = 450.0;
Widget _buildBody(BuildContext context) { Widget _buildBody(BuildContext context) {
...@@ -78,9 +78,9 @@ class MediaQueryExample extends StatelessComponent { ...@@ -78,9 +78,9 @@ class MediaQueryExample extends StatelessComponent {
} else { } else {
return new Block( return new Block(
<Widget>[ <Widget>[
new Grid( new MaxTileWidthGrid(
items.map((AdaptiveItem item) => item.toCard()).toList(), items.map((AdaptiveItem item) => item.toCard()).toList(),
maxChildExtent: _maxChildExtent maxTileWidth: _maxTileWidth
) )
] ]
); );
......
...@@ -43,8 +43,17 @@ class EdgeDims { ...@@ -43,8 +43,17 @@ class EdgeDims {
/// Whether every dimension is non-negative. /// Whether every dimension is non-negative.
bool get isNonNegative => top >= 0.0 && right >= 0.0 && bottom >= 0.0 && left >= 0.0; bool get isNonNegative => top >= 0.0 && right >= 0.0 && bottom >= 0.0 && left >= 0.0;
/// The size that this edge dims would occupy with an empty interior. /// The total offset in the vertical direction.
ui.Size get collapsedSize => new ui.Size(left + right, top + bottom); double get horizontal => left + right;
/// The total offset in the horizontal direction.
double get vertical => top + bottom;
/// The size that this EdgeDims would occupy with an empty interior.
ui.Size get collapsedSize => new ui.Size(horizontal, vertical);
/// An EdgeDims with top and bottom as well as left and right flipped.
EdgeDims get flipped => new EdgeDims.TRBL(bottom, left, top, right);
ui.Rect inflateRect(ui.Rect rect) { ui.Rect inflateRect(ui.Rect rect) {
return new ui.Rect.fromLTRB(rect.left - left, rect.top - top, rect.right + right, rect.bottom + bottom); return new ui.Rect.fromLTRB(rect.left - left, rect.top - top, rect.right + right, rect.bottom + bottom);
......
...@@ -374,7 +374,7 @@ abstract class RenderBox extends RenderObject { ...@@ -374,7 +374,7 @@ abstract class RenderBox extends RenderObject {
return constraints.constrainWidth(0.0); return constraints.constrainWidth(0.0);
} }
/// Return the minimum height that this box could be without failing to render /// Return the minimum height that this box could be without failing to paint
/// its contents within itself. /// its contents within itself.
/// ///
/// Override in subclasses that implement [performLayout]. /// Override in subclasses that implement [performLayout].
......
...@@ -5,67 +5,325 @@ ...@@ -5,67 +5,325 @@
import 'box.dart'; import 'box.dart';
import 'object.dart'; import 'object.dart';
class _GridMetrics { bool _debugIsMonotonic(List<double> offsets) {
// Grid is width-in, height-out. We fill the max width and adjust height bool result = true;
// accordingly. assert(() {
factory _GridMetrics({ double width, int childCount, double maxChildExtent }) { double current = 0.0;
assert(width != null); for (double offset in offsets) {
assert(childCount != null); if (current > offset) {
assert(maxChildExtent != null); result = false;
double childExtent = maxChildExtent; break;
int childrenPerRow = (width / childExtent).floor(); }
// If the child extent divides evenly into the width use that, otherwise + 1 current = offset;
if (width / childExtent != childrenPerRow.toDouble()) childrenPerRow += 1;
double totalPadding = 0.0;
if (childrenPerRow * childExtent > width) {
// TODO(eseidel): We should snap to pixel bounderies.
childExtent = width / childrenPerRow;
} else {
totalPadding = width - (childrenPerRow * childExtent);
} }
double childPadding = totalPadding / (childrenPerRow + 1.0); return true;
int rowCount = (childCount / childrenPerRow).ceil(); });
return result;
}
List<double> _generateRegularOffsets(int count, double size) {
int length = count + 1;
List<double> result = new List<double>(length);
for (int i = 0; i < length; ++i)
result[i] = i * size;
return result;
}
class GridSpecification {
/// Creates a grid specification from an explicit list of offsets.
GridSpecification.fromOffsets({
this.columnOffsets,
this.rowOffsets,
this.padding: EdgeDims.zero
}) {
assert(_debugIsMonotonic(columnOffsets));
assert(_debugIsMonotonic(rowOffsets));
assert(padding != null);
}
/// Creates a grid specification containing a certain number of equally sized tiles.
GridSpecification.fromRegularTiles({
double tileWidth,
double tileHeight,
int columnCount,
int rowCount,
this.padding: EdgeDims.zero
}) : columnOffsets = _generateRegularOffsets(columnCount, tileWidth),
rowOffsets = _generateRegularOffsets(rowCount, tileHeight) {
assert(_debugIsMonotonic(columnOffsets));
assert(_debugIsMonotonic(rowOffsets));
assert(padding != null);
}
/// The offsets of the column boundaries in the grid.
///
/// The first offset is the offset of the left edge of the left-most column
/// from the left edge of the interior of the grid's padding (usually 0.0).
/// The last offset is the offset of the right edge of the right-most column
/// from the left edge of the interior of the grid's padding.
///
/// If there are n columns in the grid, there should be n + 1 entries in this
/// list (because there's an entry before the first column and after the last
/// 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 row from
/// the top edge of the interior of the grid's padding (usually 0.0). The
/// last offset is the offset of the bottom edge of the bottom-most column
/// from the top edge of the interior of the grid's padding.
///
/// If there are n rows in the grid, there should be n + 1 entries in this
/// list (because there's an entry before the first row and after the last
/// row).
final List<double> rowOffsets;
/// The interior padding of the grid.
///
/// The grid's size encloses the rows and columns and is then inflated by the
/// padding.
final EdgeDims padding;
/// The size of the grid.
Size get gridSize => new Size(columnOffsets.last + padding.horizontal, rowOffsets.last + padding.vertical);
}
double height = childPadding * (rowCount + 1) + (childExtent * rowCount); /// Where to place a child within a grid.
Size childSize = new Size(childExtent, childExtent); class GridChildPlacement {
Size size = new Size(width, height); GridChildPlacement({
return new _GridMetrics._(size, childSize, childrenPerRow, childPadding, rowCount); this.column,
this.row,
this.columnSpan: 1,
this.rowSpan: 1,
this.padding: EdgeDims.zero
}) {
assert(column != null);
assert(row != null);
assert(columnSpan != null);
assert(rowSpan != null);
assert(padding != null);
} }
const _GridMetrics._(this.size, this.childSize, this.childrenPerRow, this.childPadding, this.rowCount); /// 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;
final Size size; /// How many rows the child should span.
final Size childSize; final int rowSpan;
final int childrenPerRow; // aka columnCount
final double childPadding; /// How much the child should be inset from the column and row boundaries.
final int rowCount; final EdgeDims padding;
}
/// An abstract interface to control the layout of a [RenderGrid].
abstract class GridDelegate {
/// Override this function to control size of the columns and rows.
GridSpecification getGridSpecification(BoxConstraints constraints, int childCount);
/// Override this function to control where children are placed in the grid.
GridChildPlacement getChildPlacement(GridSpecification specification, int index, Object placementData);
/// Override this method to return true when the children need to be laid out.
bool shouldRelayout(GridDelegate oldDelegate) => true;
Size _getGridSize(BoxConstraints constraints, int childCount) {
return getGridSpecification(constraints, childCount).gridSize;
}
/// Returns the minimum width that this grid could be without failing to paint
/// its contents within itself.
double getMinIntrinsicWidth(BoxConstraints constraints, int childCount) {
return constraints.constrainWidth(_getGridSize(constraints, childCount).width);
}
/// Returns the smallest width beyond which increasing the width never
/// decreases the height.
double getMaxIntrinsicWidth(BoxConstraints constraints, int childCount) {
return constraints.constrainWidth(_getGridSize(constraints, childCount).width);
}
/// Return the minimum height that this grid could be without failing to paint
/// its contents within itself.
double getMinIntrinsicHeight(BoxConstraints constraints, int childCount) {
return constraints.constrainHeight(_getGridSize(constraints, childCount).height);
}
/// Returns the smallest height beyond which increasing the height never
/// decreases the width.
double getMaxIntrinsicHeight(BoxConstraints constraints, int childCount) {
return constraints.constrainHeight(_getGridSize(constraints, childCount).height);
}
}
/// A [GridDelegate] the places its children in order throughout the grid.
abstract class GridDelegateWithInOrderChildPlacement extends GridDelegate {
GridDelegateWithInOrderChildPlacement({ this.padding: EdgeDims.zero });
/// The amount of padding to apply to each child.
final EdgeDims padding;
GridChildPlacement getChildPlacement(GridSpecification specification, int index, Object placementData) {
int columnCount = specification.columnOffsets.length - 1;
return new GridChildPlacement(
column: index % columnCount,
row: index ~/ columnCount,
padding: padding
);
}
bool shouldRelayout(GridDelegateWithInOrderChildPlacement oldDelegate) {
return padding != oldDelegate.padding;
}
}
/// A [GridDelegate] that divides the grid's width evenly amount a fixed number of columns.
class FixedColumnCountGridDelegate extends GridDelegateWithInOrderChildPlacement {
FixedColumnCountGridDelegate({
this.columnCount,
this.tileAspectRatio: 1.0,
EdgeDims padding: EdgeDims.zero
}) : super(padding: padding);
/// 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;
GridSpecification getGridSpecification(BoxConstraints constraints, int childCount) {
assert(constraints.maxWidth < double.INFINITY);
int rowCount = (childCount / columnCount).ceil();
double tileWidth = constraints.maxWidth / columnCount;
double tileHeight = tileWidth / tileAspectRatio;
return new GridSpecification.fromRegularTiles(
tileWidth: tileWidth,
tileHeight: tileHeight,
columnCount: columnCount,
rowCount: rowCount,
padding: padding.flipped
);
}
bool shouldRelayout(FixedColumnCountGridDelegate oldDelegate) {
return columnCount != oldDelegate.columnCount
|| tileAspectRatio != oldDelegate.tileAspectRatio
|| super.shouldRelayout(oldDelegate);
}
double getMinIntrinsicWidth(BoxConstraints constraints, int childCount) {
return constraints.constrainWidth(0.0);
}
double getMaxIntrinsicWidth(BoxConstraints constraints, int childCount) {
return constraints.constrainWidth(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 {
MaxTileWidthGridDelegate({
this.maxTileWidth,
this.tileAspectRatio: 1.0,
EdgeDims padding: EdgeDims.zero
}) : super(padding: padding);
/// 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;
GridSpecification getGridSpecification(BoxConstraints constraints, int childCount) {
assert(constraints.maxWidth < double.INFINITY);
double gridWidth = constraints.maxWidth;
int columnCount = (gridWidth / maxTileWidth).ceil();
int rowCount = (childCount / columnCount).ceil();
double tileWidth = gridWidth / columnCount;
double tileHeight = tileWidth / tileAspectRatio;
return new GridSpecification.fromRegularTiles(
tileWidth: tileWidth,
tileHeight: tileHeight,
columnCount: columnCount,
rowCount: rowCount,
padding: padding.flipped
);
}
bool shouldRelayout(MaxTileWidthGridDelegate oldDelegate) {
return maxTileWidth != oldDelegate.maxTileWidth
|| tileAspectRatio != oldDelegate.tileAspectRatio
|| super.shouldRelayout(oldDelegate);
}
double getMinIntrinsicWidth(BoxConstraints constraints, int childCount) {
return constraints.constrainWidth(0.0);
}
double getMaxIntrinsicWidth(BoxConstraints constraints, int childCount) {
return constraints.constrainWidth(maxTileWidth * childCount);
}
} }
/// Parent data for use with [RenderGrid] /// Parent data for use with [RenderGrid]
class GridParentData extends ContainerBoxParentDataMixin<RenderBox> {} class GridParentData extends ContainerBoxParentDataMixin<RenderBox> {
/// Opaque data passed to the getChildPlacement method of the grid's [GridDelegate].
Object placementData;
void merge(GridParentData other) {
if (other.placementData != null)
placementData = other.placementData;
super.merge(other);
}
String toString() => '${super.toString()}; placementData=$placementData';
}
/// Implements the grid layout algorithm /// Implements the grid layout algorithm
/// ///
/// In grid layout, children are arranged into rows and collumns in on a two /// In grid layout, children are arranged into rows and columns in on a two
/// dimensional grid. The grid determines how many children will be placed in /// dimensional grid. The [GridDelegate] determines how to arrange the
/// each row by making the children as wide as possible while still respecting /// children on the grid.
/// the given [maxChildExtent]. ///
/// 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 RenderBox with ContainerRenderObjectMixin<RenderBox, GridParentData>, class RenderGrid extends RenderBox with ContainerRenderObjectMixin<RenderBox, GridParentData>,
RenderBoxContainerDefaultsMixin<RenderBox, GridParentData> { RenderBoxContainerDefaultsMixin<RenderBox, GridParentData> {
RenderGrid({ List<RenderBox> children, double maxChildExtent }) { RenderGrid({
List<RenderBox> children,
GridDelegate delegate
}) : _delegate = delegate {
assert(delegate != null);
addAll(children); addAll(children);
_maxChildExtent = maxChildExtent;
} }
double _maxChildExtent; /// The delegate that controls the layout of the children.
bool _hasVisualOverflow = false; GridDelegate get delegate => _delegate;
GridDelegate _delegate;
double get maxChildExtent => _maxChildExtent; void set delegate (GridDelegate newDelegate) {
void set maxChildExtent (double value) { assert(newDelegate != null);
if (_maxChildExtent != value) { if (_delegate == newDelegate)
_maxChildExtent = value; return;
if (newDelegate.runtimeType != _delegate.runtimeType || newDelegate.shouldRelayout(_delegate))
markNeedsLayout(); markNeedsLayout();
} _delegate = newDelegate;
} }
void setupParentData(RenderBox child) { void setupParentData(RenderBox child) {
...@@ -75,63 +333,72 @@ class RenderGrid extends RenderBox with ContainerRenderObjectMixin<RenderBox, Gr ...@@ -75,63 +333,72 @@ class RenderGrid extends RenderBox with ContainerRenderObjectMixin<RenderBox, Gr
double getMinIntrinsicWidth(BoxConstraints constraints) { double getMinIntrinsicWidth(BoxConstraints constraints) {
assert(constraints.isNormalized); assert(constraints.isNormalized);
// We can render at any width. return _delegate.getMinIntrinsicWidth(constraints, childCount);
return constraints.constrainWidth(0.0);
} }
double getMaxIntrinsicWidth(BoxConstraints constraints) { double getMaxIntrinsicWidth(BoxConstraints constraints) {
assert(constraints.isNormalized); assert(constraints.isNormalized);
double maxWidth = childCount * _maxChildExtent; return _delegate.getMaxIntrinsicWidth(constraints, childCount);
return constraints.constrainWidth(maxWidth);
} }
double getMinIntrinsicHeight(BoxConstraints constraints) { double getMinIntrinsicHeight(BoxConstraints constraints) {
assert(constraints.isNormalized); assert(constraints.isNormalized);
double desiredHeight = _computeMetrics().size.height; return _delegate.getMinIntrinsicHeight(constraints, childCount);
return constraints.constrainHeight(desiredHeight);
} }
double getMaxIntrinsicHeight(BoxConstraints constraints) { double getMaxIntrinsicHeight(BoxConstraints constraints) {
assert(constraints.isNormalized); assert(constraints.isNormalized);
return getMinIntrinsicHeight(constraints); return _delegate.getMaxIntrinsicHeight(constraints, childCount);
} }
double computeDistanceToActualBaseline(TextBaseline baseline) { double computeDistanceToActualBaseline(TextBaseline baseline) {
return defaultComputeDistanceToHighestActualBaseline(baseline); return defaultComputeDistanceToHighestActualBaseline(baseline);
} }
_GridMetrics _computeMetrics() { GridSpecification _specification;
return new _GridMetrics( bool _hasVisualOverflow = false;
width: constraints.maxWidth,
childCount: childCount,
maxChildExtent: _maxChildExtent
);
}
void performLayout() { void performLayout() {
// We could shrink-wrap our contents when infinite, but for now we don't. _specification = delegate.getGridSpecification(constraints, childCount);
assert(constraints.maxWidth < double.INFINITY); Size gridSize = _specification.gridSize;
_GridMetrics metrics = _computeMetrics(); size = constraints.constrain(gridSize);
size = constraints.constrain(metrics.size); if (gridSize.width > size.width || gridSize.height > size.height)
if (constraints.maxHeight < size.height)
_hasVisualOverflow = true; _hasVisualOverflow = true;
int row = 0; double gridTopPadding = _specification.padding.top;
int column = 0; double gridLeftPadding = _specification.padding.left;
int index = 0;
RenderBox child = firstChild; RenderBox child = firstChild;
while (child != null) { while (child != null) {
child.layout(new BoxConstraints.tight(metrics.childSize));
double x = (column + 1) * metrics.childPadding + (column * metrics.childSize.width);
double y = (row + 1) * metrics.childPadding + (row * metrics.childSize.height);
final GridParentData childParentData = child.parentData; final GridParentData childParentData = child.parentData;
childParentData.offset = new Offset(x, y);
column += 1; GridChildPlacement placement = delegate.getChildPlacement(_specification, index, childParentData.placementData);
if (column >= metrics.childrenPerRow) { assert(placement.column >= 0);
row += 1; assert(placement.row >= 0);
column = 0; assert(placement.column + placement.columnSpan < _specification.columnOffsets.length);
} assert(placement.row + placement.rowSpan < _specification.rowOffsets.length);
double tileLeft = _specification.columnOffsets[placement.column] + gridLeftPadding;
double tileRight = _specification.columnOffsets[placement.column + placement.columnSpan] + gridLeftPadding;
double tileTop = _specification.rowOffsets[placement.row] + gridTopPadding;
double tileBottom = _specification.rowOffsets[placement.row + placement.rowSpan] + gridTopPadding;
double childWidth = tileRight - tileLeft - placement.padding.horizontal;
double childHeight = tileBottom - tileTop - placement.padding.vertical;
child.layout(new BoxConstraints(
minWidth: childWidth,
maxWidth: childWidth,
minHeight: childHeight,
maxHeight: childHeight
));
childParentData.offset = new Offset(
tileLeft + placement.padding.left,
tileTop + placement.padding.top
);
++index;
assert(child.parentData == childParentData); assert(child.parentData == childParentData);
child = childParentData.nextSibling; child = childParentData.nextSibling;
......
...@@ -33,6 +33,7 @@ export 'package:flutter/rendering.dart' show ...@@ -33,6 +33,7 @@ export 'package:flutter/rendering.dart' show
FontWeight, FontWeight,
FractionalOffset, FractionalOffset,
Gradient, Gradient,
GridDelegate,
HitTestBehavior, HitTestBehavior,
ImageFit, ImageFit,
ImageRepeat, ImageRepeat,
...@@ -1125,21 +1126,125 @@ class Positioned extends ParentDataWidget<StackRenderObjectWidgetBase> { ...@@ -1125,21 +1126,125 @@ class Positioned extends ParentDataWidget<StackRenderObjectWidgetBase> {
} }
} }
abstract class GridRenderObjectWidgetBase extends MultiChildRenderObjectWidget {
GridRenderObjectWidgetBase({
List<Widget> children,
Key key
}) : super(key: key, children: children) {
_delegate = createDelegate();
}
GridDelegate _delegate;
/// The delegate that controls the layout of the children.
GridDelegate createDelegate();
RenderGrid createRenderObject() => new RenderGrid(delegate: _delegate);
void updateRenderObject(RenderGrid renderObject, GridRenderObjectWidgetBase oldWidget) {
renderObject.delegate = _delegate;
}
}
/// Uses the grid layout algorithm for its children. /// Uses the grid layout algorithm for its children.
/// ///
/// For details about the grid layout algorithm, see [RenderGrid]. /// For details about the grid layout algorithm, see [RenderGrid].
class Grid extends MultiChildRenderObjectWidget { class CustomGrid extends GridRenderObjectWidgetBase {
Grid(List<Widget> children, { Key key, this.maxChildExtent }) CustomGrid(List<Widget> children, { Key key, this.delegate })
: super(key: key, children: children) { : super(key: key, children: children) {
assert(maxChildExtent != null); assert(delegate != null);
} }
final double maxChildExtent; /// The delegate that controls the layout of the children.
final GridDelegate delegate;
RenderGrid createRenderObject() => new RenderGrid(maxChildExtent: maxChildExtent); GridDelegate createDelegate() => delegate;
}
/// Uses a grid layout with a fixed column count.
///
/// For details about the grid layout algorithm, see [MaxTileWidthGridDelegate].
class FixedColumnCountGrid extends GridRenderObjectWidgetBase {
FixedColumnCountGrid(List<Widget> children, {
Key key,
this.columnCount,
this.tileAspectRatio: 1.0,
this.padding: EdgeDims.zero
}) : super(key: key, children: children) {
assert(columnCount != null);
}
/// The number of columns in the grid.
final int columnCount;
void updateRenderObject(RenderGrid renderObject, Grid oldWidget) { /// The ratio of the width to the height of each tile in the grid.
renderObject.maxChildExtent = maxChildExtent; final double tileAspectRatio;
/// The amount of padding to apply to each child.
final EdgeDims padding;
FixedColumnCountGridDelegate createDelegate() {
return new FixedColumnCountGridDelegate(
columnCount: columnCount,
tileAspectRatio: tileAspectRatio,
padding: padding
);
}
}
/// Uses a grid layout with a max tile width.
///
/// For details about the grid layout algorithm, see [MaxTileWidthGridDelegate].
class MaxTileWidthGrid extends GridRenderObjectWidgetBase {
MaxTileWidthGrid(List<Widget> children, {
Key key,
this.maxTileWidth,
this.tileAspectRatio: 1.0,
this.padding: EdgeDims.zero
}) : super(key: key, children: children) {
assert(maxTileWidth != null);
}
/// 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;
/// The amount of padding to apply to each child.
final EdgeDims padding;
MaxTileWidthGridDelegate createDelegate() {
return new MaxTileWidthGridDelegate(
maxTileWidth: maxTileWidth,
tileAspectRatio: tileAspectRatio,
padding: padding
);
}
}
/// Supplies per-child data to the grid's [GridDelegate].
class GridPlacementData<DataType, WidgetType extends RenderObjectWidget> extends ParentDataWidget<WidgetType> {
GridPlacementData({ Key key, this.placementData, Widget child })
: super(key: key, child: child);
/// Opaque data passed to the getChildPlacement method of the grid's [GridDelegate].
final DataType placementData;
void applyParentData(RenderObject renderObject) {
assert(renderObject.parentData is GridParentData);
final GridParentData parentData = renderObject.parentData;
if (parentData.placementData != placementData) {
parentData.placementData = placementData;
AbstractNode targetParent = renderObject.parent;
if (targetParent is RenderObject)
targetParent.markNeedsLayout();
}
}
void debugFillDescription(List<String> description) {
super.debugFillDescription(description);
description.add('placementData: $placementData');
} }
} }
......
...@@ -16,7 +16,10 @@ void main() { ...@@ -16,7 +16,10 @@ void main() {
new RenderDecoratedBox(decoration: new BoxDecoration()) new RenderDecoratedBox(decoration: new BoxDecoration())
]; ];
RenderGrid grid = new RenderGrid(children: children, maxChildExtent: 100.0); RenderGrid grid = new RenderGrid(
children: children,
delegate: new MaxTileWidthGridDelegate(maxTileWidth: 100.0)
);
layout(grid, constraints: const BoxConstraints(maxWidth: 200.0)); layout(grid, constraints: const BoxConstraints(maxWidth: 200.0));
children.forEach((RenderBox child) { children.forEach((RenderBox child) {
...@@ -28,7 +31,7 @@ void main() { ...@@ -28,7 +31,7 @@ void main() {
expect(grid.size.height, equals(200.0), reason: "grid height"); expect(grid.size.height, equals(200.0), reason: "grid height");
expect(grid.needsLayout, equals(false)); expect(grid.needsLayout, equals(false));
grid.maxChildExtent = 60.0; grid.delegate = new MaxTileWidthGridDelegate(maxTileWidth: 60.0);
expect(grid.needsLayout, equals(true)); expect(grid.needsLayout, equals(true));
pumpFrame(); pumpFrame();
......
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