mirror of
https://github.com/flutter/flutter.git
synced 2025-06-03 00:51:18 +00:00
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
This commit is contained in:
parent
a5925149d6
commit
46a178dce6
@ -20,7 +20,10 @@ Color randomColor() {
|
||||
|
||||
RenderBox buildGridExample() {
|
||||
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());
|
||||
|
@ -62,7 +62,7 @@ class AdaptiveItem {
|
||||
}
|
||||
|
||||
class MediaQueryExample extends StatelessComponent {
|
||||
static const double _maxChildExtent = 150.0;
|
||||
static const double _maxTileWidth = 150.0;
|
||||
static const double _gridViewBreakpoint = 450.0;
|
||||
|
||||
Widget _buildBody(BuildContext context) {
|
||||
@ -78,9 +78,9 @@ class MediaQueryExample extends StatelessComponent {
|
||||
} else {
|
||||
return new Block(
|
||||
<Widget>[
|
||||
new Grid(
|
||||
new MaxTileWidthGrid(
|
||||
items.map((AdaptiveItem item) => item.toCard()).toList(),
|
||||
maxChildExtent: _maxChildExtent
|
||||
maxTileWidth: _maxTileWidth
|
||||
)
|
||||
]
|
||||
);
|
||||
|
@ -43,8 +43,17 @@ class EdgeDims {
|
||||
/// Whether every dimension is non-negative.
|
||||
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.
|
||||
ui.Size get collapsedSize => new ui.Size(left + right, top + bottom);
|
||||
/// The total offset in the vertical direction.
|
||||
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) {
|
||||
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 {
|
||||
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.
|
||||
///
|
||||
/// Override in subclasses that implement [performLayout].
|
||||
|
@ -5,67 +5,325 @@
|
||||
import 'box.dart';
|
||||
import 'object.dart';
|
||||
|
||||
class _GridMetrics {
|
||||
// Grid is width-in, height-out. We fill the max width and adjust height
|
||||
// accordingly.
|
||||
factory _GridMetrics({ double width, int childCount, double maxChildExtent }) {
|
||||
assert(width != null);
|
||||
assert(childCount != null);
|
||||
assert(maxChildExtent != null);
|
||||
double childExtent = maxChildExtent;
|
||||
int childrenPerRow = (width / childExtent).floor();
|
||||
// If the child extent divides evenly into the width use that, otherwise + 1
|
||||
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);
|
||||
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;
|
||||
}
|
||||
double childPadding = totalPadding / (childrenPerRow + 1.0);
|
||||
int rowCount = (childCount / childrenPerRow).ceil();
|
||||
return true;
|
||||
});
|
||||
return result;
|
||||
}
|
||||
|
||||
double height = childPadding * (rowCount + 1) + (childExtent * rowCount);
|
||||
Size childSize = new Size(childExtent, childExtent);
|
||||
Size size = new Size(width, height);
|
||||
return new _GridMetrics._(size, childSize, childrenPerRow, childPadding, rowCount);
|
||||
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);
|
||||
}
|
||||
|
||||
const _GridMetrics._(this.size, this.childSize, this.childrenPerRow, this.childPadding, this.rowCount);
|
||||
/// 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);
|
||||
}
|
||||
|
||||
final Size size;
|
||||
final Size childSize;
|
||||
final int childrenPerRow; // aka columnCount
|
||||
final double childPadding;
|
||||
final int rowCount;
|
||||
/// 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);
|
||||
}
|
||||
|
||||
/// Where to place a child within a grid.
|
||||
class GridChildPlacement {
|
||||
GridChildPlacement({
|
||||
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);
|
||||
}
|
||||
|
||||
/// 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;
|
||||
|
||||
/// How much the child should be inset from the column and row boundaries.
|
||||
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]
|
||||
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
|
||||
///
|
||||
/// In grid layout, children are arranged into rows and collumns in on a two
|
||||
/// dimensional grid. The grid determines how many children will be placed in
|
||||
/// each row by making the children as wide as possible while still respecting
|
||||
/// the given [maxChildExtent].
|
||||
/// 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 RenderBox with ContainerRenderObjectMixin<RenderBox, GridParentData>,
|
||||
RenderBoxContainerDefaultsMixin<RenderBox, GridParentData> {
|
||||
RenderGrid({ List<RenderBox> children, double maxChildExtent }) {
|
||||
RenderGrid({
|
||||
List<RenderBox> children,
|
||||
GridDelegate delegate
|
||||
}) : _delegate = delegate {
|
||||
assert(delegate != null);
|
||||
addAll(children);
|
||||
_maxChildExtent = maxChildExtent;
|
||||
}
|
||||
|
||||
double _maxChildExtent;
|
||||
bool _hasVisualOverflow = false;
|
||||
|
||||
double get maxChildExtent => _maxChildExtent;
|
||||
void set maxChildExtent (double value) {
|
||||
if (_maxChildExtent != value) {
|
||||
_maxChildExtent = value;
|
||||
/// The delegate that controls the layout of the children.
|
||||
GridDelegate get delegate => _delegate;
|
||||
GridDelegate _delegate;
|
||||
void set delegate (GridDelegate newDelegate) {
|
||||
assert(newDelegate != null);
|
||||
if (_delegate == newDelegate)
|
||||
return;
|
||||
if (newDelegate.runtimeType != _delegate.runtimeType || newDelegate.shouldRelayout(_delegate))
|
||||
markNeedsLayout();
|
||||
}
|
||||
_delegate = newDelegate;
|
||||
}
|
||||
|
||||
void setupParentData(RenderBox child) {
|
||||
@ -75,63 +333,72 @@ class RenderGrid extends RenderBox with ContainerRenderObjectMixin<RenderBox, Gr
|
||||
|
||||
double getMinIntrinsicWidth(BoxConstraints constraints) {
|
||||
assert(constraints.isNormalized);
|
||||
// We can render at any width.
|
||||
return constraints.constrainWidth(0.0);
|
||||
return _delegate.getMinIntrinsicWidth(constraints, childCount);
|
||||
}
|
||||
|
||||
double getMaxIntrinsicWidth(BoxConstraints constraints) {
|
||||
assert(constraints.isNormalized);
|
||||
double maxWidth = childCount * _maxChildExtent;
|
||||
return constraints.constrainWidth(maxWidth);
|
||||
return _delegate.getMaxIntrinsicWidth(constraints, childCount);
|
||||
}
|
||||
|
||||
double getMinIntrinsicHeight(BoxConstraints constraints) {
|
||||
assert(constraints.isNormalized);
|
||||
double desiredHeight = _computeMetrics().size.height;
|
||||
return constraints.constrainHeight(desiredHeight);
|
||||
return _delegate.getMinIntrinsicHeight(constraints, childCount);
|
||||
}
|
||||
|
||||
double getMaxIntrinsicHeight(BoxConstraints constraints) {
|
||||
assert(constraints.isNormalized);
|
||||
return getMinIntrinsicHeight(constraints);
|
||||
return _delegate.getMaxIntrinsicHeight(constraints, childCount);
|
||||
}
|
||||
|
||||
double computeDistanceToActualBaseline(TextBaseline baseline) {
|
||||
return defaultComputeDistanceToHighestActualBaseline(baseline);
|
||||
}
|
||||
|
||||
_GridMetrics _computeMetrics() {
|
||||
return new _GridMetrics(
|
||||
width: constraints.maxWidth,
|
||||
childCount: childCount,
|
||||
maxChildExtent: _maxChildExtent
|
||||
);
|
||||
}
|
||||
GridSpecification _specification;
|
||||
bool _hasVisualOverflow = false;
|
||||
|
||||
void performLayout() {
|
||||
// We could shrink-wrap our contents when infinite, but for now we don't.
|
||||
assert(constraints.maxWidth < double.INFINITY);
|
||||
_GridMetrics metrics = _computeMetrics();
|
||||
size = constraints.constrain(metrics.size);
|
||||
if (constraints.maxHeight < size.height)
|
||||
_specification = delegate.getGridSpecification(constraints, childCount);
|
||||
Size gridSize = _specification.gridSize;
|
||||
size = constraints.constrain(gridSize);
|
||||
if (gridSize.width > size.width || gridSize.height > size.height)
|
||||
_hasVisualOverflow = true;
|
||||
|
||||
int row = 0;
|
||||
int column = 0;
|
||||
double gridTopPadding = _specification.padding.top;
|
||||
double gridLeftPadding = _specification.padding.left;
|
||||
int index = 0;
|
||||
RenderBox child = firstChild;
|
||||
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;
|
||||
childParentData.offset = new Offset(x, y);
|
||||
|
||||
column += 1;
|
||||
if (column >= metrics.childrenPerRow) {
|
||||
row += 1;
|
||||
column = 0;
|
||||
}
|
||||
GridChildPlacement placement = delegate.getChildPlacement(_specification, index, 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);
|
||||
|
||||
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);
|
||||
child = childParentData.nextSibling;
|
||||
|
@ -33,6 +33,7 @@ export 'package:flutter/rendering.dart' show
|
||||
FontWeight,
|
||||
FractionalOffset,
|
||||
Gradient,
|
||||
GridDelegate,
|
||||
HitTestBehavior,
|
||||
ImageFit,
|
||||
ImageRepeat,
|
||||
@ -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.
|
||||
///
|
||||
/// For details about the grid layout algorithm, see [RenderGrid].
|
||||
class Grid extends MultiChildRenderObjectWidget {
|
||||
Grid(List<Widget> children, { Key key, this.maxChildExtent })
|
||||
class CustomGrid extends GridRenderObjectWidgetBase {
|
||||
CustomGrid(List<Widget> children, { Key key, this.delegate })
|
||||
: 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;
|
||||
}
|
||||
|
||||
void updateRenderObject(RenderGrid renderObject, Grid oldWidget) {
|
||||
renderObject.maxChildExtent = maxChildExtent;
|
||||
/// 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;
|
||||
|
||||
/// 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;
|
||||
|
||||
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() {
|
||||
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));
|
||||
|
||||
children.forEach((RenderBox child) {
|
||||
@ -28,7 +31,7 @@ void main() {
|
||||
expect(grid.size.height, equals(200.0), reason: "grid height");
|
||||
|
||||
expect(grid.needsLayout, equals(false));
|
||||
grid.maxChildExtent = 60.0;
|
||||
grid.delegate = new MaxTileWidthGridDelegate(maxTileWidth: 60.0);
|
||||
expect(grid.needsLayout, equals(true));
|
||||
|
||||
pumpFrame();
|
||||
|
Loading…
Reference in New Issue
Block a user