mirror of
https://github.com/flutter/flutter
synced 2024-10-13 11:42:54 +00:00
commit
f4603f7615
|
@ -131,7 +131,7 @@ class RenderParagraph extends RenderBox {
|
|||
@override
|
||||
String debugDescribeChildren(String prefix) {
|
||||
return '$prefix \u2558\u2550\u2566\u2550\u2550 text \u2550\u2550\u2550\n'
|
||||
'${text.toString("$prefix \u2551 ")}\n'
|
||||
'${text.toString("$prefix \u2551 ")}' // TextSpan includes a newline
|
||||
'$prefix \u255A\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\n'
|
||||
'$prefix\n';
|
||||
}
|
||||
|
|
|
@ -12,6 +12,12 @@ import 'object.dart';
|
|||
class TableCellParentData extends BoxParentData {
|
||||
TableCellVerticalAlignment verticalAlignment;
|
||||
|
||||
/// The column that the child was in the last time it was laid out.
|
||||
int x;
|
||||
|
||||
/// The row that the child was in the last time it was laid out.
|
||||
int y;
|
||||
|
||||
@override
|
||||
String toString() => '${super.toString()}; $verticalAlignment';
|
||||
}
|
||||
|
@ -383,6 +389,15 @@ class RenderTable extends RenderBox {
|
|||
for (int x = 0; x < columnsToCopy; x += 1)
|
||||
_children[x + y * columns] = oldChildren[x + y * oldColumns];
|
||||
}
|
||||
if (oldColumns > columns) {
|
||||
for (int y = 0; y < rows; y += 1) {
|
||||
for (int x = columns; x < oldColumns; x += 1) {
|
||||
int xy = x + y * oldColumns;
|
||||
if (oldChildren[xy] != null)
|
||||
dropChild(oldChildren[xy]);
|
||||
}
|
||||
}
|
||||
}
|
||||
markNeedsLayout();
|
||||
}
|
||||
|
||||
|
@ -393,14 +408,21 @@ class RenderTable extends RenderBox {
|
|||
assert(value >= 0);
|
||||
if (value == rows)
|
||||
return;
|
||||
if (_rows > value) {
|
||||
for (int xy = columns * value; xy < _children.length; xy += 1) {
|
||||
if (_children[xy] != null)
|
||||
dropChild(_children[xy]);
|
||||
}
|
||||
}
|
||||
_rows = value;
|
||||
_children.length = columns * rows;
|
||||
markNeedsLayout();
|
||||
}
|
||||
|
||||
Map<int, TableColumnWidth> _columnWidths;
|
||||
void setColumnWidths(Map<int, TableColumnWidth> value) {
|
||||
assert(value != null);
|
||||
Map<int, TableColumnWidth> get columnWidths => new Map<int, TableColumnWidth>.unmodifiable(_columnWidths);
|
||||
void set columnWidths(Map<int, TableColumnWidth> value) {
|
||||
value ??= new HashMap<int, TableColumnWidth>();
|
||||
if (_columnWidths == value)
|
||||
return;
|
||||
_columnWidths = value;
|
||||
|
@ -460,16 +482,19 @@ class RenderTable extends RenderBox {
|
|||
void setFlatChildren(int columns, List<RenderBox> cells) {
|
||||
if (cells == _children && columns == _columns)
|
||||
return;
|
||||
for (RenderBox oldChild in cells) {
|
||||
if (oldChild != null)
|
||||
dropChild(oldChild);
|
||||
}
|
||||
assert(columns >= 0);
|
||||
if (columns == 0) {
|
||||
// consider the case of a newly empty table
|
||||
if (columns == 0 || cells.length == 0) {
|
||||
assert(cells == null || cells.length == 0);
|
||||
if (_children.length == 0)
|
||||
_columns = columns;
|
||||
if (_children.length == 0) {
|
||||
assert(_rows == 0);
|
||||
return;
|
||||
_columns = 0;
|
||||
}
|
||||
for (RenderBox oldChild in cells) {
|
||||
if (oldChild != null)
|
||||
dropChild(oldChild);
|
||||
}
|
||||
_rows = 0;
|
||||
_children.clear();
|
||||
markNeedsLayout();
|
||||
|
@ -477,34 +502,55 @@ class RenderTable extends RenderBox {
|
|||
}
|
||||
assert(cells != null);
|
||||
assert(cells.length % columns == 0);
|
||||
_columns = columns;
|
||||
_rows = cells.length % columns;
|
||||
_children = cells;
|
||||
for (RenderBox cell in cells) {
|
||||
if (cell != null)
|
||||
adoptChild(cell);
|
||||
// remove cells that are moving away
|
||||
for (int y = 0; y < _columns; y += 1) {
|
||||
for (int x = 0; x < _rows; x += 1) {
|
||||
int xyOld = x + y * _columns;
|
||||
int xyNew = x + y * columns;
|
||||
if (_children[xyOld] != null && (x >= columns || xyNew >= cells.length || _children[xyOld] != cells[xyNew]))
|
||||
dropChild(_children[xyOld]);
|
||||
}
|
||||
}
|
||||
// adopt cells that are arriving
|
||||
int y = 0;
|
||||
while (y * columns < cells.length) {
|
||||
for (int x = 0; x < columns; x += 1) {
|
||||
int xyNew = x + y * columns;
|
||||
int xyOld = x + y * _columns;
|
||||
if (cells[xyNew] != null && (x >= _columns || y >= _rows || _children[xyOld] != cells[xyNew]))
|
||||
adoptChild(cells[xyNew]);
|
||||
}
|
||||
y += 1;
|
||||
}
|
||||
// update our internal values
|
||||
_columns = columns;
|
||||
_rows = cells.length ~/ columns;
|
||||
_children = cells.toList();
|
||||
assert(_children.length == rows * columns);
|
||||
markNeedsLayout();
|
||||
}
|
||||
|
||||
void setChildren(List<List<RenderBox>> cells) {
|
||||
// TODO(ianh): Make this smarter, like setFlatChildren
|
||||
if (cells == null) {
|
||||
setFlatChildren(0, null);
|
||||
return;
|
||||
}
|
||||
for (RenderBox oldChild in cells) {
|
||||
for (RenderBox oldChild in _children) {
|
||||
if (oldChild != null)
|
||||
dropChild(oldChild);
|
||||
}
|
||||
_children.clear();
|
||||
_columns = cells.length > 0 ? cells.first.length : 0;
|
||||
_rows = 0;
|
||||
_children.clear();
|
||||
for (List<RenderBox> row in cells)
|
||||
addRow(row);
|
||||
assert(_children.length == rows * columns);
|
||||
}
|
||||
|
||||
void addRow(List<RenderBox> cells) {
|
||||
assert(cells.length == columns);
|
||||
assert(_children.length == rows * columns);
|
||||
_rows += 1;
|
||||
_children.addAll(cells);
|
||||
for (RenderBox cell in cells) {
|
||||
|
@ -518,6 +564,7 @@ class RenderTable extends RenderBox {
|
|||
assert(x != null);
|
||||
assert(y != null);
|
||||
assert(x >= 0 && x < columns && y >= 0 && y < rows);
|
||||
assert(_children.length == rows * columns);
|
||||
final int xy = x + y * columns;
|
||||
RenderBox oldChild = _children[xy];
|
||||
if (oldChild != null)
|
||||
|
@ -543,6 +590,7 @@ class RenderTable extends RenderBox {
|
|||
|
||||
@override
|
||||
void visitChildren(RenderObjectVisitor visitor) {
|
||||
assert(_children.length == rows * columns);
|
||||
for (RenderBox child in _children) {
|
||||
if (child != null)
|
||||
visitor(child);
|
||||
|
@ -552,6 +600,7 @@ class RenderTable extends RenderBox {
|
|||
@override
|
||||
double getMinIntrinsicWidth(BoxConstraints constraints) {
|
||||
assert(constraints.debugAssertIsNormalized);
|
||||
assert(_children.length == rows * columns);
|
||||
double totalMinWidth = 0.0;
|
||||
for (int x = 0; x < columns; x += 1) {
|
||||
TableColumnWidth columnWidth = _columnWidths[x] ?? defaultColumnWidth;
|
||||
|
@ -564,6 +613,7 @@ class RenderTable extends RenderBox {
|
|||
@override
|
||||
double getMaxIntrinsicWidth(BoxConstraints constraints) {
|
||||
assert(constraints.debugAssertIsNormalized);
|
||||
assert(_children.length == rows * columns);
|
||||
double totalMaxWidth = 0.0;
|
||||
for (int x = 0; x < columns; x += 1) {
|
||||
TableColumnWidth columnWidth = _columnWidths[x] ?? defaultColumnWidth;
|
||||
|
@ -578,6 +628,7 @@ class RenderTable extends RenderBox {
|
|||
// winner of the 2016 world's most expensive intrinsic dimension function award
|
||||
// honorable mention, most likely to improve if taught about memoization award
|
||||
assert(constraints.debugAssertIsNormalized);
|
||||
assert(_children.length == rows * columns);
|
||||
final List<double> widths = computeColumnWidths(constraints);
|
||||
double rowTop = 0.0;
|
||||
for (int y = 0; y < rows; y += 1) {
|
||||
|
@ -626,6 +677,7 @@ class RenderTable extends RenderBox {
|
|||
}
|
||||
|
||||
List<double> computeColumnWidths(BoxConstraints constraints) {
|
||||
assert(_children.length == rows * columns);
|
||||
final List<double> widths = new List<double>(columns);
|
||||
final List<double> flexes = new List<double>(columns);
|
||||
double totalMinWidth = 0.0;
|
||||
|
@ -677,7 +729,10 @@ class RenderTable extends RenderBox {
|
|||
|
||||
@override
|
||||
void performLayout() {
|
||||
assert(_children.length == rows * columns);
|
||||
if (rows * columns == 0) {
|
||||
// TODO(ianh): if columns is zero, this should be zero width
|
||||
// TODO(ianh): if columns is not zero, this should be based on the column width specifications
|
||||
size = constraints.constrain(const Size(double.INFINITY, 0.0));
|
||||
return;
|
||||
}
|
||||
|
@ -704,6 +759,8 @@ class RenderTable extends RenderBox {
|
|||
RenderBox child = _children[xy];
|
||||
if (child != null) {
|
||||
TableCellParentData childParentData = child.parentData;
|
||||
childParentData.x = x;
|
||||
childParentData.y = y;
|
||||
switch (childParentData.verticalAlignment ?? defaultVerticalAlignment) {
|
||||
case TableCellVerticalAlignment.baseline:
|
||||
assert(textBaseline != null);
|
||||
|
@ -769,6 +826,7 @@ class RenderTable extends RenderBox {
|
|||
|
||||
@override
|
||||
bool hitTestChildren(HitTestResult result, { Point position }) {
|
||||
assert(_children.length == rows * columns);
|
||||
for (int index = _children.length - 1; index >= 0; index -= 1) {
|
||||
RenderBox child = _children[index];
|
||||
if (child != null) {
|
||||
|
@ -784,6 +842,7 @@ class RenderTable extends RenderBox {
|
|||
|
||||
@override
|
||||
void paint(PaintingContext context, Offset offset) {
|
||||
assert(_children.length == rows * columns);
|
||||
if (rows * columns == 0)
|
||||
return;
|
||||
assert(_rowTops.length == rows);
|
||||
|
@ -797,37 +856,39 @@ class RenderTable extends RenderBox {
|
|||
Rect bounds = offset & size;
|
||||
Canvas canvas = context.canvas;
|
||||
canvas.saveLayer(bounds, new Paint());
|
||||
switch (border.verticalInside.style) {
|
||||
case BorderStyle.solid:
|
||||
Paint paint = new Paint()
|
||||
..color = border.verticalInside.color
|
||||
..strokeWidth = border.verticalInside.width
|
||||
..style = PaintingStyle.stroke;
|
||||
Path path = new Path();
|
||||
for (int x = 1; x < columns; x += 1) {
|
||||
path.moveTo(bounds.left + _columnLefts[x], bounds.top);
|
||||
path.lineTo(bounds.left + _columnLefts[x], bounds.bottom);
|
||||
}
|
||||
canvas.drawPath(path, paint);
|
||||
break;
|
||||
case BorderStyle.none: break;
|
||||
if (border != null) {
|
||||
switch (border.verticalInside.style) {
|
||||
case BorderStyle.solid:
|
||||
Paint paint = new Paint()
|
||||
..color = border.verticalInside.color
|
||||
..strokeWidth = border.verticalInside.width
|
||||
..style = PaintingStyle.stroke;
|
||||
Path path = new Path();
|
||||
for (int x = 1; x < columns; x += 1) {
|
||||
path.moveTo(bounds.left + _columnLefts[x], bounds.top);
|
||||
path.lineTo(bounds.left + _columnLefts[x], bounds.bottom);
|
||||
}
|
||||
canvas.drawPath(path, paint);
|
||||
break;
|
||||
case BorderStyle.none: break;
|
||||
}
|
||||
switch (border.horizontalInside.style) {
|
||||
case BorderStyle.solid:
|
||||
Paint paint = new Paint()
|
||||
..color = border.horizontalInside.color
|
||||
..strokeWidth = border.horizontalInside.width
|
||||
..style = PaintingStyle.stroke;
|
||||
Path path = new Path();
|
||||
for (int y = 1; y < rows; y += 1) {
|
||||
path.moveTo(bounds.left, bounds.top + _rowTops[y]);
|
||||
path.lineTo(bounds.right, bounds.top + _rowTops[y]);
|
||||
}
|
||||
canvas.drawPath(path, paint);
|
||||
break;
|
||||
case BorderStyle.none: break;
|
||||
}
|
||||
border.paint(canvas, bounds);
|
||||
}
|
||||
switch (border.horizontalInside.style) {
|
||||
case BorderStyle.solid:
|
||||
Paint paint = new Paint()
|
||||
..color = border.horizontalInside.color
|
||||
..strokeWidth = border.horizontalInside.width
|
||||
..style = PaintingStyle.stroke;
|
||||
Path path = new Path();
|
||||
for (int y = 1; y < rows; y += 1) {
|
||||
path.moveTo(bounds.left, bounds.top + _rowTops[y]);
|
||||
path.lineTo(bounds.right, bounds.top + _rowTops[y]);
|
||||
}
|
||||
canvas.drawPath(path, paint);
|
||||
break;
|
||||
case BorderStyle.none: break;
|
||||
}
|
||||
border.paint(canvas, bounds);
|
||||
canvas.restore();
|
||||
}
|
||||
|
||||
|
@ -851,21 +912,25 @@ class RenderTable extends RenderBox {
|
|||
StringBuffer result = new StringBuffer();
|
||||
result.writeln('$prefix \u2502');
|
||||
int lastIndex = _children.length - 1;
|
||||
for (int y = 0; y < rows; y += 1) {
|
||||
for (int x = 0; x < columns; x += 1) {
|
||||
final int xy = x + y * columns;
|
||||
RenderBox child = _children[xy];
|
||||
if (child != null) {
|
||||
if (xy < lastIndex) {
|
||||
result.write('${child.toStringDeep("$prefix \u251C\u2500child ($x, $y): ", "$prefix \u2502")}');
|
||||
if (lastIndex < 0) {
|
||||
result.writeln('$prefix \u2514\u2500table is empty');
|
||||
} else {
|
||||
for (int y = 0; y < rows; y += 1) {
|
||||
for (int x = 0; x < columns; x += 1) {
|
||||
final int xy = x + y * columns;
|
||||
RenderBox child = _children[xy];
|
||||
if (child != null) {
|
||||
if (xy < lastIndex) {
|
||||
result.write('${child.toStringDeep("$prefix \u251C\u2500child ($x, $y): ", "$prefix \u2502")}');
|
||||
} else {
|
||||
result.write('${child.toStringDeep("$prefix \u2514\u2500child ($x, $y): ", "$prefix ")}');
|
||||
}
|
||||
} else {
|
||||
result.write('${child.toStringDeep("$prefix \u2514\u2500child ($x, $y): ", "$prefix ")}');
|
||||
}
|
||||
} else {
|
||||
if (xy < lastIndex) {
|
||||
result.writeln('$prefix \u251C\u2500child ($x, $y) is null');
|
||||
} else {
|
||||
result.writeln('$prefix \u2514\u2500child ($x, $y) is null');
|
||||
if (xy < lastIndex) {
|
||||
result.writeln('$prefix \u251C\u2500child ($x, $y) is null');
|
||||
} else {
|
||||
result.writeln('$prefix \u2514\u2500child ($x, $y) is null');
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -15,21 +15,19 @@ export 'package:flutter/painting.dart';
|
|||
export 'package:flutter/rendering.dart' show
|
||||
Axis,
|
||||
BoxConstraints,
|
||||
CrossAxisAlignment,
|
||||
CustomClipper,
|
||||
CustomPainter,
|
||||
FixedColumnCountGridDelegate,
|
||||
CrossAxisAlignment,
|
||||
FlexDirection,
|
||||
MainAxisAlignment,
|
||||
FractionalOffsetTween,
|
||||
GridDelegate,
|
||||
GridDelegateWithInOrderChildPlacement,
|
||||
GridSpecification,
|
||||
HitTestBehavior,
|
||||
MainAxisAlignment,
|
||||
MaxTileWidthGridDelegate,
|
||||
MultiChildLayoutDelegate,
|
||||
SingleChildLayoutDelegate,
|
||||
RenderObjectPainter,
|
||||
PaintingContext,
|
||||
PlainTextSpan,
|
||||
PointerCancelEvent,
|
||||
|
@ -42,7 +40,9 @@ export 'package:flutter/rendering.dart' show
|
|||
PointerUpEvent,
|
||||
PointerUpEventListener,
|
||||
RelativeRect,
|
||||
RenderObjectPainter,
|
||||
ShaderCallback,
|
||||
SingleChildLayoutDelegate,
|
||||
ValueChanged,
|
||||
ViewportAnchor,
|
||||
ViewportDimensions,
|
||||
|
|
|
@ -20,6 +20,8 @@ export 'package:flutter/services.dart' show FlutterError;
|
|||
/// original Widget's Key.
|
||||
///
|
||||
/// Keys must be unique amongst the Elements with the same parent.
|
||||
///
|
||||
/// Subclasses of Key should either subclass [LocalKey] or [GlobalKey].
|
||||
abstract class Key {
|
||||
/// Construct a ValueKey<String> with the given String.
|
||||
/// This is the simplest way to create keys.
|
||||
|
@ -29,12 +31,18 @@ abstract class Key {
|
|||
const Key.constructor(); // so that subclasses can call us, since the Key() factory constructor shadows the implicit constructor
|
||||
}
|
||||
|
||||
/// A key that is not a [GlobalKey].
|
||||
abstract class LocalKey extends Key {
|
||||
/// Default constructor, used by subclasses.
|
||||
const LocalKey() : super.constructor();
|
||||
}
|
||||
|
||||
/// A kind of [Key] that uses a value of a particular type to identify itself.
|
||||
///
|
||||
/// For example, a ValueKey<String> is equal to another ValueKey<String> if
|
||||
/// their values match.
|
||||
class ValueKey<T> extends Key {
|
||||
const ValueKey(this.value) : super.constructor();
|
||||
class ValueKey<T> extends LocalKey {
|
||||
const ValueKey(this.value);
|
||||
|
||||
final T value;
|
||||
|
||||
|
@ -54,8 +62,8 @@ class ValueKey<T> extends Key {
|
|||
}
|
||||
|
||||
/// A [Key] that is only equal to itself.
|
||||
class UniqueKey extends Key {
|
||||
const UniqueKey() : super.constructor();
|
||||
class UniqueKey extends LocalKey {
|
||||
const UniqueKey();
|
||||
|
||||
@override
|
||||
String toString() => '[$hashCode]';
|
||||
|
@ -65,8 +73,8 @@ class UniqueKey extends Key {
|
|||
///
|
||||
/// Used to tie the identity of a Widget to the identity of an object used to
|
||||
/// generate that Widget.
|
||||
class ObjectKey extends Key {
|
||||
const ObjectKey(this.value) : super.constructor();
|
||||
class ObjectKey extends LocalKey {
|
||||
const ObjectKey(this.value);
|
||||
|
||||
final Object value;
|
||||
|
||||
|
@ -744,7 +752,7 @@ abstract class Element implements BuildContext {
|
|||
Element updateChild(Element child, Widget newWidget, dynamic newSlot) {
|
||||
if (newWidget == null) {
|
||||
if (child != null)
|
||||
_deactivateChild(child);
|
||||
deactivateChild(child);
|
||||
return null;
|
||||
}
|
||||
if (child != null) {
|
||||
|
@ -760,10 +768,10 @@ abstract class Element implements BuildContext {
|
|||
assert(child.widget == newWidget);
|
||||
return child;
|
||||
}
|
||||
_deactivateChild(child);
|
||||
deactivateChild(child);
|
||||
assert(child._parent == null);
|
||||
}
|
||||
return _inflateWidget(newWidget, newSlot);
|
||||
return inflateWidget(newWidget, newSlot);
|
||||
}
|
||||
|
||||
static void finalizeTree() {
|
||||
|
@ -870,7 +878,7 @@ abstract class Element implements BuildContext {
|
|||
return element;
|
||||
}
|
||||
|
||||
Element _inflateWidget(Widget newWidget, dynamic newSlot) {
|
||||
Element inflateWidget(Widget newWidget, dynamic newSlot) {
|
||||
Key key = newWidget.key;
|
||||
if (key is GlobalKey) {
|
||||
Element newChild = _retakeInactiveElement(key, newWidget);
|
||||
|
@ -901,7 +909,7 @@ abstract class Element implements BuildContext {
|
|||
});
|
||||
}
|
||||
|
||||
void _deactivateChild(Element child) {
|
||||
void deactivateChild(Element child) {
|
||||
assert(child != null);
|
||||
assert(child._parent == this);
|
||||
child._parent = null;
|
||||
|
@ -1339,7 +1347,7 @@ abstract class ComponentElement extends BuildableElement {
|
|||
@override
|
||||
bool detachChild(Element child) {
|
||||
assert(child == _child);
|
||||
_deactivateChild(_child);
|
||||
deactivateChild(_child);
|
||||
_child = null;
|
||||
return true;
|
||||
}
|
||||
|
@ -1733,7 +1741,7 @@ abstract class RenderObjectElement extends BuildableElement {
|
|||
if (oldChild.widget.key != null)
|
||||
oldKeyedChildren[oldChild.widget.key] = oldChild;
|
||||
else
|
||||
_deactivateChild(oldChild);
|
||||
deactivateChild(oldChild);
|
||||
}
|
||||
oldChildrenTop += 1;
|
||||
}
|
||||
|
@ -1795,7 +1803,7 @@ abstract class RenderObjectElement extends BuildableElement {
|
|||
if (haveOldChildren && oldKeyedChildren.isNotEmpty) {
|
||||
for (Element oldChild in oldKeyedChildren.values) {
|
||||
if (detachedChildren == null || !detachedChildren.contains(oldChild))
|
||||
_deactivateChild(oldChild);
|
||||
deactivateChild(oldChild);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1897,7 +1905,7 @@ class SingleChildRenderObjectElement extends RenderObjectElement {
|
|||
@override
|
||||
bool detachChild(Element child) {
|
||||
assert(child == _child);
|
||||
_deactivateChild(_child);
|
||||
deactivateChild(_child);
|
||||
_child = null;
|
||||
return true;
|
||||
}
|
||||
|
@ -1984,7 +1992,7 @@ class MultiChildRenderObjectElement extends RenderObjectElement {
|
|||
@override
|
||||
bool detachChild(Element child) {
|
||||
_detachedChildren.add(child);
|
||||
_deactivateChild(child);
|
||||
deactivateChild(child);
|
||||
return true;
|
||||
}
|
||||
|
||||
|
@ -1994,7 +2002,7 @@ class MultiChildRenderObjectElement extends RenderObjectElement {
|
|||
_children = new List<Element>(widget.children.length);
|
||||
Element previousChild;
|
||||
for (int i = 0; i < _children.length; ++i) {
|
||||
Element newChild = _inflateWidget(widget.children[i], previousChild);
|
||||
Element newChild = inflateWidget(widget.children[i], previousChild);
|
||||
_children[i] = newChild;
|
||||
previousChild = newChild;
|
||||
}
|
||||
|
|
231
packages/flutter/lib/src/widgets/table.dart
Normal file
231
packages/flutter/lib/src/widgets/table.dart
Normal file
|
@ -0,0 +1,231 @@
|
|||
// 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 'dart:collection';
|
||||
|
||||
import 'package:flutter/rendering.dart';
|
||||
|
||||
import 'debug.dart';
|
||||
import 'framework.dart';
|
||||
|
||||
export 'package:flutter/rendering.dart' show
|
||||
FixedColumnWidth,
|
||||
FlexColumnWidth,
|
||||
FractionColumnWidth,
|
||||
IntrinsicColumnWidth,
|
||||
MaxColumnWidth,
|
||||
MinColumnWidth,
|
||||
TableBorder,
|
||||
TableCellVerticalAlignment,
|
||||
TableColumnWidth;
|
||||
|
||||
class TableRow {
|
||||
const TableRow({ this.key, this.children });
|
||||
final LocalKey key;
|
||||
final List<Widget> children;
|
||||
}
|
||||
|
||||
class _TableElementRow {
|
||||
const _TableElementRow({ this.key, this.children });
|
||||
final LocalKey key;
|
||||
final List<Element> children;
|
||||
}
|
||||
|
||||
/// Uses the table layout algorithm for its children.
|
||||
///
|
||||
/// For details about the table layout algorithm, see [RenderTable].
|
||||
/// To control the alignment of children, see [TableCell].
|
||||
class Table extends RenderObjectWidget {
|
||||
Table({
|
||||
Key key,
|
||||
this.children: const <TableRow>[],
|
||||
this.columnWidths,
|
||||
this.defaultColumnWidth: const FlexColumnWidth(1.0),
|
||||
this.border,
|
||||
this.defaultVerticalAlignment: TableCellVerticalAlignment.top,
|
||||
this.textBaseline
|
||||
}) : super(key: key) {
|
||||
assert(children != null);
|
||||
assert(defaultColumnWidth != null);
|
||||
assert(defaultVerticalAlignment != null);
|
||||
assert(() {
|
||||
List<Widget> flatChildren = children.expand((TableRow row) => row.children).toList(growable: false);
|
||||
return !debugChildrenHaveDuplicateKeys(this, flatChildren);
|
||||
});
|
||||
assert(!children.any((TableRow row1) => row1.key != null && children.any((TableRow row2) => row1 != row2 && row1.key == row2.key)));
|
||||
}
|
||||
|
||||
final List<TableRow> children;
|
||||
final Map<int, TableColumnWidth> columnWidths;
|
||||
final TableColumnWidth defaultColumnWidth;
|
||||
final TableBorder border;
|
||||
final TableCellVerticalAlignment defaultVerticalAlignment;
|
||||
final TextBaseline textBaseline;
|
||||
|
||||
@override
|
||||
_TableElement createElement() => new _TableElement(this);
|
||||
|
||||
@override
|
||||
RenderTable createRenderObject(BuildContext context) {
|
||||
return new RenderTable(
|
||||
columns: children.length > 0 ? children[0].children.length : 0,
|
||||
rows: children.length,
|
||||
columnWidths: columnWidths,
|
||||
defaultColumnWidth: defaultColumnWidth,
|
||||
border: border,
|
||||
defaultVerticalAlignment: defaultVerticalAlignment,
|
||||
textBaseline: textBaseline
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
void updateRenderObject(BuildContext context, RenderTable renderObject) {
|
||||
assert(renderObject.columns == (children.length > 0 ? children[0].children.length : 0));
|
||||
assert(renderObject.rows == children.length);
|
||||
renderObject
|
||||
..columnWidths = columnWidths
|
||||
..defaultColumnWidth = defaultColumnWidth
|
||||
..border = border
|
||||
..defaultVerticalAlignment = defaultVerticalAlignment
|
||||
..textBaseline = textBaseline;
|
||||
}
|
||||
}
|
||||
|
||||
class _TableElement extends RenderObjectElement {
|
||||
_TableElement(Table widget) : super(widget);
|
||||
|
||||
@override
|
||||
Table get widget => super.widget;
|
||||
|
||||
@override
|
||||
RenderTable get renderObject => super.renderObject;
|
||||
|
||||
// This class ignores the child's slot entirely.
|
||||
// Instead of doing incremental updates to the child list, it replaces the entire list each frame.
|
||||
|
||||
List<_TableElementRow> _children = const<_TableElementRow>[];
|
||||
|
||||
bool _debugWillReattachChildren = false;
|
||||
|
||||
@override
|
||||
void mount(Element parent, dynamic newSlot) {
|
||||
super.mount(parent, newSlot);
|
||||
assert(!_debugWillReattachChildren);
|
||||
assert(() { _debugWillReattachChildren = true; return true; });
|
||||
_children = widget.children.map((TableRow row) {
|
||||
return new _TableElementRow(
|
||||
key: row.key,
|
||||
children: row.children.map((Widget child) => inflateWidget(child, null)).toList(growable: false)
|
||||
);
|
||||
}).toList(growable: false);
|
||||
assert(() { _debugWillReattachChildren = false; return true; });
|
||||
_updateRenderObjectChildren();
|
||||
}
|
||||
|
||||
@override
|
||||
void insertChildRenderObject(RenderObject child, Element slot) {
|
||||
assert(_debugWillReattachChildren);
|
||||
renderObject.setupParentData(child);
|
||||
}
|
||||
|
||||
@override
|
||||
void moveChildRenderObject(RenderObject child, dynamic slot) {
|
||||
assert(_debugWillReattachChildren);
|
||||
}
|
||||
|
||||
@override
|
||||
void removeChildRenderObject(RenderObject child) {
|
||||
assert(_debugWillReattachChildren);
|
||||
TableCellParentData childParentData = child.parentData;
|
||||
renderObject.setChild(childParentData.x, childParentData.y, null);
|
||||
}
|
||||
|
||||
final Set<Element> _detachedChildren = new HashSet<Element>();
|
||||
|
||||
@override
|
||||
void update(Table newWidget) {
|
||||
assert(!_debugWillReattachChildren);
|
||||
assert(() { _debugWillReattachChildren = true; return true; });
|
||||
Map<LocalKey, List<Element>> oldKeyedRows = new Map<LocalKey, List<Element>>.fromIterable(
|
||||
_children.where((_TableElementRow row) => row.key != null),
|
||||
key: (_TableElementRow row) => row.key,
|
||||
value: (_TableElementRow row) => row.children
|
||||
);
|
||||
Iterator<_TableElementRow> oldUnkeyedRows = _children.where((_TableElementRow row) => row.key == null).iterator;
|
||||
List<_TableElementRow> newChildren = <_TableElementRow>[];
|
||||
Set<List<Element>> taken = new Set<List<Element>>();
|
||||
for (TableRow row in newWidget.children) {
|
||||
List<Element> oldChildren;
|
||||
if (row.key != null && oldKeyedRows.containsKey(row.key)) {
|
||||
oldChildren = oldKeyedRows[row.key];
|
||||
taken.add(oldChildren);
|
||||
} else if (row.key == null && oldUnkeyedRows.moveNext()) {
|
||||
oldChildren = oldUnkeyedRows.current.children;
|
||||
} else {
|
||||
oldChildren = const <Element>[];
|
||||
}
|
||||
newChildren.add(new _TableElementRow(
|
||||
key: row.key,
|
||||
children: updateChildren(oldChildren, row.children, detachedChildren: _detachedChildren)
|
||||
));
|
||||
}
|
||||
while (oldUnkeyedRows.moveNext())
|
||||
updateChildren(oldUnkeyedRows.current.children, const <Widget>[], detachedChildren: _detachedChildren);
|
||||
for (List<Element> oldChildren in oldKeyedRows.values.where((List<Element> list) => !taken.contains(list)))
|
||||
updateChildren(oldChildren, const <Widget>[], detachedChildren: _detachedChildren);
|
||||
assert(() { _debugWillReattachChildren = false; return true; });
|
||||
_children = newChildren;
|
||||
_updateRenderObjectChildren();
|
||||
_detachedChildren.clear();
|
||||
super.update(newWidget);
|
||||
assert(widget == newWidget);
|
||||
}
|
||||
|
||||
void _updateRenderObjectChildren() {
|
||||
assert(renderObject != null);
|
||||
renderObject.setFlatChildren(
|
||||
_children.length > 0 ? _children[0].children.length : 0,
|
||||
_children.expand((_TableElementRow row) => row.children.map((Element child) => child.renderObject)).toList()
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
void visitChildren(ElementVisitor visitor) {
|
||||
for (Element child in _children.expand((_TableElementRow row) => row.children)) {
|
||||
if (!_detachedChildren.contains(child))
|
||||
visitor(child);
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
bool detachChild(Element child) {
|
||||
_detachedChildren.add(child);
|
||||
deactivateChild(child);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
class TableCell extends ParentDataWidget<Table> {
|
||||
TableCell({ Key key, this.verticalAlignment, Widget child })
|
||||
: super(key: key, child: child);
|
||||
|
||||
final TableCellVerticalAlignment verticalAlignment;
|
||||
|
||||
@override
|
||||
void applyParentData(RenderObject renderObject) {
|
||||
final TableCellParentData parentData = renderObject.parentData;
|
||||
if (parentData.verticalAlignment != verticalAlignment) {
|
||||
parentData.verticalAlignment = verticalAlignment;
|
||||
AbstractNode targetParent = renderObject.parent;
|
||||
if (targetParent is RenderObject)
|
||||
targetParent.markNeedsLayout();
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
void debugFillDescription(List<String> description) {
|
||||
super.debugFillDescription(description);
|
||||
description.add('verticalAlignment: $verticalAlignment');
|
||||
}
|
||||
}
|
|
@ -44,6 +44,7 @@ export 'src/widgets/scrollable_list.dart';
|
|||
export 'src/widgets/scrollable.dart';
|
||||
export 'src/widgets/semantics_debugger.dart';
|
||||
export 'src/widgets/status_transitions.dart';
|
||||
export 'src/widgets/table.dart';
|
||||
export 'src/widgets/title.dart';
|
||||
export 'src/widgets/transitions.dart';
|
||||
export 'src/widgets/unique_widget.dart';
|
||||
|
|
|
@ -55,4 +55,20 @@ void main() {
|
|||
|
||||
expect(table.size, equals(new Size(800.0, 230.0)));
|
||||
});
|
||||
|
||||
test('Table test: removing cells', () {
|
||||
RenderTable table;
|
||||
RenderBox child;
|
||||
table = new RenderTable(
|
||||
columns: 5,
|
||||
rows: 5
|
||||
);
|
||||
table.setChild(4, 4, child = sizedBox(10.0, 10.0));
|
||||
|
||||
layout(table);
|
||||
|
||||
expect(child.attached, isTrue);
|
||||
table.rows = 4;
|
||||
expect(child.attached, isFalse);
|
||||
});
|
||||
}
|
||||
|
|
344
packages/flutter/test/widget/table_test.dart
Normal file
344
packages/flutter/test/widget/table_test.dart
Normal file
|
@ -0,0 +1,344 @@
|
|||
// 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_test/flutter_test.dart';
|
||||
import 'package:flutter/rendering.dart';
|
||||
import 'package:flutter/widgets.dart';
|
||||
import 'package:test/test.dart';
|
||||
|
||||
void main() {
|
||||
test('Table widget - control test', () {
|
||||
testWidgets((WidgetTester tester) {
|
||||
tester.pumpWidget(
|
||||
new Table(
|
||||
children: <TableRow>[
|
||||
new TableRow(
|
||||
children: <Widget>[
|
||||
new Text('AAAAAA'), new Text('B'), new Text('C')
|
||||
]
|
||||
),
|
||||
new TableRow(
|
||||
children: <Widget>[
|
||||
new Text('D'), new Text('EEE'), new Text('F')
|
||||
]
|
||||
),
|
||||
new TableRow(
|
||||
children: <Widget>[
|
||||
new Text('G'), new Text('H'), new Text('III')
|
||||
]
|
||||
),
|
||||
]
|
||||
)
|
||||
);
|
||||
RenderBox boxA = tester.findText('AAAAAA').findRenderObject();
|
||||
RenderBox boxD = tester.findText('D').findRenderObject();
|
||||
RenderBox boxG = tester.findText('G').findRenderObject();
|
||||
RenderBox boxB = tester.findText('B').findRenderObject();
|
||||
expect(boxA.size, equals(boxD.size));
|
||||
expect(boxA.size, equals(boxG.size));
|
||||
expect(boxA.size, equals(boxB.size));
|
||||
});
|
||||
});
|
||||
test('Table widget - changing table dimensions', () {
|
||||
testWidgets((WidgetTester tester) {
|
||||
tester.pumpWidget(
|
||||
new Table(
|
||||
children: <TableRow>[
|
||||
new TableRow(
|
||||
children: <Widget>[
|
||||
new Text('A'), new Text('B'), new Text('C')
|
||||
]
|
||||
),
|
||||
new TableRow(
|
||||
children: <Widget>[
|
||||
new Text('D'), new Text('E'), new Text('F')
|
||||
]
|
||||
),
|
||||
new TableRow(
|
||||
children: <Widget>[
|
||||
new Text('G'), new Text('H'), new Text('I')
|
||||
]
|
||||
),
|
||||
]
|
||||
)
|
||||
);
|
||||
RenderBox boxA1 = tester.findText('A').findRenderObject();
|
||||
RenderBox boxG1 = tester.findText('G').findRenderObject();
|
||||
expect(boxA1, isNotNull);
|
||||
expect(boxG1, isNotNull);
|
||||
tester.pumpWidget(
|
||||
new Table(
|
||||
children: <TableRow>[
|
||||
new TableRow(
|
||||
children: <Widget>[
|
||||
new Text('a'), new Text('b'), new Text('c'), new Text('d')
|
||||
]
|
||||
),
|
||||
new TableRow(
|
||||
children: <Widget>[
|
||||
new Text('e'), new Text('f'), new Text('g'), new Text('h')
|
||||
]
|
||||
),
|
||||
]
|
||||
)
|
||||
);
|
||||
RenderBox boxA2 = tester.findText('a').findRenderObject();
|
||||
RenderBox boxG2 = tester.findText('g').findRenderObject();
|
||||
expect(boxA2, isNotNull);
|
||||
expect(boxG2, isNotNull);
|
||||
expect(boxA1, equals(boxA2));
|
||||
expect(boxG1, isNot(equals(boxG2)));
|
||||
});
|
||||
});
|
||||
test('Table widget - repump test', () {
|
||||
testWidgets((WidgetTester tester) {
|
||||
tester.pumpWidget(
|
||||
new Table(
|
||||
children: <TableRow>[
|
||||
new TableRow(
|
||||
children: <Widget>[
|
||||
new Text('AAAAAA'), new Text('B'), new Text('C')
|
||||
]
|
||||
),
|
||||
new TableRow(
|
||||
children: <Widget>[
|
||||
new Text('D'), new Text('EEE'), new Text('F')
|
||||
]
|
||||
),
|
||||
new TableRow(
|
||||
children: <Widget>[
|
||||
new Text('G'), new Text('H'), new Text('III')
|
||||
]
|
||||
),
|
||||
]
|
||||
)
|
||||
);
|
||||
tester.pumpWidget(
|
||||
new Table(
|
||||
children: <TableRow>[
|
||||
new TableRow(
|
||||
children: <Widget>[
|
||||
new Text('AAA'), new Text('B'), new Text('C')
|
||||
]
|
||||
),
|
||||
new TableRow(
|
||||
children: <Widget>[
|
||||
new Text('D'), new Text('E'), new Text('FFFFFF')
|
||||
]
|
||||
),
|
||||
new TableRow(
|
||||
children: <Widget>[
|
||||
new Text('G'), new Text('H'), new Text('III')
|
||||
]
|
||||
),
|
||||
]
|
||||
)
|
||||
);
|
||||
RenderBox boxA = tester.findText('AAA').findRenderObject();
|
||||
RenderBox boxD = tester.findText('D').findRenderObject();
|
||||
RenderBox boxG = tester.findText('G').findRenderObject();
|
||||
RenderBox boxB = tester.findText('B').findRenderObject();
|
||||
expect(boxA.size, equals(boxD.size));
|
||||
expect(boxA.size, equals(boxG.size));
|
||||
expect(boxA.size, equals(boxB.size));
|
||||
});
|
||||
});
|
||||
test('Table widget - intrinsic sizing test', () {
|
||||
testWidgets((WidgetTester tester) {
|
||||
tester.pumpWidget(
|
||||
new Table(
|
||||
defaultColumnWidth: const IntrinsicColumnWidth(),
|
||||
children: <TableRow>[
|
||||
new TableRow(
|
||||
children: <Widget>[
|
||||
new Text('AAA'), new Text('B'), new Text('C')
|
||||
]
|
||||
),
|
||||
new TableRow(
|
||||
children: <Widget>[
|
||||
new Text('D'), new Text('E'), new Text('FFFFFF')
|
||||
]
|
||||
),
|
||||
new TableRow(
|
||||
children: <Widget>[
|
||||
new Text('G'), new Text('H'), new Text('III')
|
||||
]
|
||||
),
|
||||
]
|
||||
)
|
||||
);
|
||||
RenderBox boxA = tester.findText('AAA').findRenderObject();
|
||||
RenderBox boxD = tester.findText('D').findRenderObject();
|
||||
RenderBox boxG = tester.findText('G').findRenderObject();
|
||||
RenderBox boxB = tester.findText('B').findRenderObject();
|
||||
expect(boxA.size, equals(boxD.size));
|
||||
expect(boxA.size, equals(boxG.size));
|
||||
expect(boxA.size.width, greaterThan(boxB.size.width));
|
||||
expect(boxA.size.height, equals(boxB.size.height));
|
||||
});
|
||||
});
|
||||
test('Table widget - intrinsic sizing test, resizing', () {
|
||||
testWidgets((WidgetTester tester) {
|
||||
tester.pumpWidget(
|
||||
new Table(
|
||||
defaultColumnWidth: const IntrinsicColumnWidth(),
|
||||
children: <TableRow>[
|
||||
new TableRow(
|
||||
children: <Widget>[
|
||||
new Text('AAAAAA'), new Text('B'), new Text('C')
|
||||
]
|
||||
),
|
||||
new TableRow(
|
||||
children: <Widget>[
|
||||
new Text('D'), new Text('EEE'), new Text('F')
|
||||
]
|
||||
),
|
||||
new TableRow(
|
||||
children: <Widget>[
|
||||
new Text('G'), new Text('H'), new Text('III')
|
||||
]
|
||||
),
|
||||
]
|
||||
)
|
||||
);
|
||||
tester.pumpWidget(
|
||||
new Table(
|
||||
defaultColumnWidth: const IntrinsicColumnWidth(),
|
||||
children: <TableRow>[
|
||||
new TableRow(
|
||||
children: <Widget>[
|
||||
new Text('A'), new Text('B'), new Text('C')
|
||||
]
|
||||
),
|
||||
new TableRow(
|
||||
children: <Widget>[
|
||||
new Text('D'), new Text('EEE'), new Text('F')
|
||||
]
|
||||
),
|
||||
new TableRow(
|
||||
children: <Widget>[
|
||||
new Text('G'), new Text('H'), new Text('III')
|
||||
]
|
||||
),
|
||||
]
|
||||
)
|
||||
);
|
||||
RenderBox boxA = tester.findText('A').findRenderObject();
|
||||
RenderBox boxD = tester.findText('D').findRenderObject();
|
||||
RenderBox boxG = tester.findText('G').findRenderObject();
|
||||
RenderBox boxB = tester.findText('B').findRenderObject();
|
||||
expect(boxA.size, equals(boxD.size));
|
||||
expect(boxA.size, equals(boxG.size));
|
||||
expect(boxA.size.width, lessThan(boxB.size.width));
|
||||
expect(boxA.size.height, equals(boxB.size.height));
|
||||
});
|
||||
});
|
||||
test('Table widget - intrinsic sizing test, changing column widths', () {
|
||||
testWidgets((WidgetTester tester) {
|
||||
tester.pumpWidget(
|
||||
new Table(
|
||||
children: <TableRow>[
|
||||
new TableRow(
|
||||
children: <Widget>[
|
||||
new Text('AAA'), new Text('B'), new Text('C')
|
||||
]
|
||||
),
|
||||
new TableRow(
|
||||
children: <Widget>[
|
||||
new Text('D'), new Text('E'), new Text('FFFFFF')
|
||||
]
|
||||
),
|
||||
new TableRow(
|
||||
children: <Widget>[
|
||||
new Text('G'), new Text('H'), new Text('III')
|
||||
]
|
||||
),
|
||||
]
|
||||
)
|
||||
);
|
||||
tester.pumpWidget(
|
||||
new Table(
|
||||
defaultColumnWidth: const IntrinsicColumnWidth(),
|
||||
children: <TableRow>[
|
||||
new TableRow(
|
||||
children: <Widget>[
|
||||
new Text('AAA'), new Text('B'), new Text('C')
|
||||
]
|
||||
),
|
||||
new TableRow(
|
||||
children: <Widget>[
|
||||
new Text('D'), new Text('E'), new Text('FFFFFF')
|
||||
]
|
||||
),
|
||||
new TableRow(
|
||||
children: <Widget>[
|
||||
new Text('G'), new Text('H'), new Text('III')
|
||||
]
|
||||
),
|
||||
]
|
||||
)
|
||||
);
|
||||
RenderBox boxA = tester.findText('AAA').findRenderObject();
|
||||
RenderBox boxD = tester.findText('D').findRenderObject();
|
||||
RenderBox boxG = tester.findText('G').findRenderObject();
|
||||
RenderBox boxB = tester.findText('B').findRenderObject();
|
||||
expect(boxA.size, equals(boxD.size));
|
||||
expect(boxA.size, equals(boxG.size));
|
||||
expect(boxA.size.width, greaterThan(boxB.size.width));
|
||||
expect(boxA.size.height, equals(boxB.size.height));
|
||||
});
|
||||
});
|
||||
test('Table widget - moving test', () {
|
||||
testWidgets((WidgetTester tester) {
|
||||
List<BuildContext> contexts = <BuildContext>[];
|
||||
tester.pumpWidget(
|
||||
new Table(
|
||||
children: <TableRow>[
|
||||
new TableRow(
|
||||
key: new ValueKey<int>(1),
|
||||
children: <Widget>[
|
||||
new StatefulBuilder(
|
||||
builder: (BuildContext context, StateSetter setState) {
|
||||
contexts.add(context);
|
||||
return new Text('A');
|
||||
}
|
||||
)
|
||||
]
|
||||
),
|
||||
new TableRow(
|
||||
children: <Widget>[
|
||||
new Text('b')
|
||||
]
|
||||
),
|
||||
]
|
||||
)
|
||||
);
|
||||
tester.pumpWidget(
|
||||
new Table(
|
||||
children: <TableRow>[
|
||||
new TableRow(
|
||||
children: <Widget>[
|
||||
new Text('b')
|
||||
]
|
||||
),
|
||||
new TableRow(
|
||||
key: new ValueKey<int>(1),
|
||||
children: <Widget>[
|
||||
new StatefulBuilder(
|
||||
builder: (BuildContext context, StateSetter setState) {
|
||||
contexts.add(context);
|
||||
return new Text('A');
|
||||
}
|
||||
)
|
||||
]
|
||||
),
|
||||
]
|
||||
)
|
||||
);
|
||||
expect(contexts.length, equals(2));
|
||||
expect(contexts[0], equals(contexts[1]));
|
||||
});
|
||||
});
|
||||
// TODO(ianh): Test handling of TableCell object
|
||||
}
|
Loading…
Reference in a new issue