mirror of
https://github.com/flutter/flutter.git
synced 2026-02-20 02:29:02 +08:00
Merge pull request #2942 from Hixie/layout-asserts
More elaborate exceptions.
This commit is contained in:
commit
d5deea491e
@ -520,8 +520,36 @@ abstract class RenderBox extends RenderObject {
|
||||
bool get hasSize => _size != null;
|
||||
Size _size;
|
||||
void set size(Size value) {
|
||||
assert((sizedByParent && debugDoingThisResize) ||
|
||||
(!sizedByParent && debugDoingThisLayout));
|
||||
assert(!(debugDoingThisResize && debugDoingThisLayout));
|
||||
assert(sizedByParent || !debugDoingThisResize);
|
||||
assert(() {
|
||||
if ((sizedByParent && debugDoingThisResize) ||
|
||||
(!sizedByParent && debugDoingThisLayout))
|
||||
return true;
|
||||
assert(!debugDoingThisResize);
|
||||
String contract, violation, hint;
|
||||
if (debugDoingThisLayout) {
|
||||
assert(sizedByParent);
|
||||
violation = 'It appears that the size setter was called from performLayout().';
|
||||
hint = '';
|
||||
} else {
|
||||
violation = 'The size setter was called from outside layout (neither performResize() nor performLayout() were being run for this object).';
|
||||
if (owner != null && owner.debugDoingLayout)
|
||||
hint = 'Only the object itself can set its size. It is a contract violation for other objects to set it.';
|
||||
}
|
||||
if (sizedByParent)
|
||||
contract = 'Because this RenderBox has sizedByParent set to true, it must set its size in performResize().';
|
||||
else
|
||||
contract = 'Because this RenderBox has sizedByParent set to false, it must set its size in performLayout().';
|
||||
throw new FlutterError(
|
||||
'RenderBox size setter called incorrectly.\n'
|
||||
'$violation\n'
|
||||
'$hint\n'
|
||||
'$contract\n'
|
||||
'The RenderBox in question is:\n'
|
||||
' $this'
|
||||
);
|
||||
});
|
||||
assert(() {
|
||||
if (value is _DebugSize) {
|
||||
if (value._owner != this) {
|
||||
@ -628,89 +656,106 @@ abstract class RenderBox extends RenderObject {
|
||||
@override
|
||||
void debugAssertDoesMeetConstraints() {
|
||||
assert(constraints != null);
|
||||
assert(_size != null);
|
||||
// verify that the size is not infinite
|
||||
if (_size.isInfinite) {
|
||||
StringBuffer information = new StringBuffer();
|
||||
if (!constraints.hasBoundedWidth) {
|
||||
RenderBox node = this;
|
||||
while (!node.constraints.hasBoundedWidth && node.parent is RenderBox)
|
||||
node = node.parent;
|
||||
information.writeln('The nearest ancestor providing an unbounded width constraint is:');
|
||||
information.writeln(' $node');
|
||||
List<String> description = <String>[];
|
||||
node.debugFillDescription(description);
|
||||
for (String line in description)
|
||||
information.writeln(' $line');
|
||||
assert(() {
|
||||
if (!hasSize) {
|
||||
assert(!needsLayout); // this is called in the size= setter during layout, but in that case we have a size
|
||||
String contract;
|
||||
if (sizedByParent)
|
||||
contract = 'Because this RenderBox has sizedByParent set to true, it must set its size in performResize().\n';
|
||||
else
|
||||
contract = 'Because this RenderBox has sizedByParent set to false, it must set its size in performLayout().\n';
|
||||
throw new FlutterError(
|
||||
'RenderBox did not set its size during layout.\n'
|
||||
'$contract'
|
||||
'It appears that this did not happen; layout completed, but the size property is still null.\n'
|
||||
'The RenderBox in question is:\n'
|
||||
' $this'
|
||||
);
|
||||
}
|
||||
if (!constraints.hasBoundedHeight) {
|
||||
RenderBox node = this;
|
||||
while (!node.constraints.hasBoundedHeight && node.parent is RenderBox)
|
||||
node = node.parent;
|
||||
information.writeln('The nearest ancestor providing an unbounded height constraint is:');
|
||||
information.writeln(' $node');
|
||||
List<String> description = <String>[];
|
||||
node.debugFillDescription(description);
|
||||
for (String line in description)
|
||||
information.writeln(' $line');
|
||||
// verify that the size is not infinite
|
||||
if (_size.isInfinite) {
|
||||
StringBuffer information = new StringBuffer();
|
||||
if (!constraints.hasBoundedWidth) {
|
||||
RenderBox node = this;
|
||||
while (!node.constraints.hasBoundedWidth && node.parent is RenderBox)
|
||||
node = node.parent;
|
||||
information.writeln('The nearest ancestor providing an unbounded width constraint is:');
|
||||
information.writeln(' $node');
|
||||
List<String> description = <String>[];
|
||||
node.debugFillDescription(description);
|
||||
for (String line in description)
|
||||
information.writeln(' $line');
|
||||
}
|
||||
if (!constraints.hasBoundedHeight) {
|
||||
RenderBox node = this;
|
||||
while (!node.constraints.hasBoundedHeight && node.parent is RenderBox)
|
||||
node = node.parent;
|
||||
information.writeln('The nearest ancestor providing an unbounded height constraint is:');
|
||||
information.writeln(' $node');
|
||||
List<String> description = <String>[];
|
||||
node.debugFillDescription(description);
|
||||
for (String line in description)
|
||||
information.writeln(' $line');
|
||||
}
|
||||
throw new FlutterError(
|
||||
'$runtimeType object was given an infinite size during layout.\n'
|
||||
'This probably means that it is a render object that tries to be '
|
||||
'as big as possible, but it was put inside another render object '
|
||||
'that allows its children to pick their own size.\n'
|
||||
'$information'
|
||||
'See https://flutter.io/layout/ for more information.'
|
||||
);
|
||||
}
|
||||
throw new FlutterError(
|
||||
'$runtimeType object was given an infinite size during layout.\n'
|
||||
'This probably means that it is a render object that tries to be\n'
|
||||
'as big as possible, but it was put inside another render object\n'
|
||||
'that allows its children to pick their own size.\n'
|
||||
'$information'
|
||||
'See https://flutter.io/layout/ for more information.'
|
||||
);
|
||||
}
|
||||
// verify that the size is within the constraints
|
||||
if (!constraints.isSatisfiedBy(_size)) {
|
||||
throw new FlutterError(
|
||||
'$runtimeType does not meet its constraints.\n'
|
||||
'Constraints: $constraints\n'
|
||||
'Size: $_size\n'
|
||||
'If you are not writing your own RenderBox subclass, then this is not\n'
|
||||
'your fault. Contact support: https://github.com/flutter/flutter/issues/new'
|
||||
);
|
||||
}
|
||||
// verify that the intrinsics are also within the constraints
|
||||
assert(!RenderObject.debugCheckingIntrinsics);
|
||||
RenderObject.debugCheckingIntrinsics = true;
|
||||
double intrinsic;
|
||||
StringBuffer failures = new StringBuffer();
|
||||
int failureCount = 0;
|
||||
intrinsic = getMinIntrinsicWidth(constraints);
|
||||
if (intrinsic != constraints.constrainWidth(intrinsic)) {
|
||||
failures.writeln(' * getMinIntrinsicWidth() -- returned: w=$intrinsic');
|
||||
failureCount += 1;
|
||||
}
|
||||
intrinsic = getMaxIntrinsicWidth(constraints);
|
||||
if (intrinsic != constraints.constrainWidth(intrinsic)) {
|
||||
failures.writeln(' * getMaxIntrinsicWidth() -- returned: w=$intrinsic');
|
||||
failureCount += 1;
|
||||
}
|
||||
intrinsic = getMinIntrinsicHeight(constraints);
|
||||
if (intrinsic != constraints.constrainHeight(intrinsic)) {
|
||||
failures.writeln(' * getMinIntrinsicHeight() -- returned: h=$intrinsic');
|
||||
failureCount += 1;
|
||||
}
|
||||
intrinsic = getMaxIntrinsicHeight(constraints);
|
||||
if (intrinsic != constraints.constrainHeight(intrinsic)) {
|
||||
failures.writeln(' * getMaxIntrinsicHeight() -- returned: h=$intrinsic');
|
||||
failureCount += 1;
|
||||
}
|
||||
RenderObject.debugCheckingIntrinsics = false;
|
||||
if (failures.isNotEmpty) {
|
||||
assert(failureCount > 0);
|
||||
throw new FlutterError(
|
||||
'The intrinsic dimension methods of the $runtimeType class returned values that violate the given constraints.\n'
|
||||
'The constraints were: $constraints\n'
|
||||
'The following method${failureCount > 1 ? "s" : ""} returned values outside of those constraints:\n'
|
||||
'$failures'
|
||||
'If you are not writing your own RenderBox subclass, then this is not\n'
|
||||
'your fault. Contact support: https://github.com/flutter/flutter/issues/new'
|
||||
);
|
||||
}
|
||||
// verify that the size is within the constraints
|
||||
if (!constraints.isSatisfiedBy(_size)) {
|
||||
throw new FlutterError(
|
||||
'$runtimeType does not meet its constraints.\n'
|
||||
'Constraints: $constraints\n'
|
||||
'Size: $_size\n'
|
||||
'If you are not writing your own RenderBox subclass, then this is not '
|
||||
'your fault. Contact support: https://github.com/flutter/flutter/issues/new'
|
||||
);
|
||||
}
|
||||
// verify that the intrinsics are also within the constraints
|
||||
assert(!RenderObject.debugCheckingIntrinsics);
|
||||
RenderObject.debugCheckingIntrinsics = true;
|
||||
double intrinsic;
|
||||
StringBuffer failures = new StringBuffer();
|
||||
int failureCount = 0;
|
||||
intrinsic = getMinIntrinsicWidth(constraints);
|
||||
if (intrinsic != constraints.constrainWidth(intrinsic)) {
|
||||
failures.writeln(' * getMinIntrinsicWidth() -- returned: w=$intrinsic');
|
||||
failureCount += 1;
|
||||
}
|
||||
intrinsic = getMaxIntrinsicWidth(constraints);
|
||||
if (intrinsic != constraints.constrainWidth(intrinsic)) {
|
||||
failures.writeln(' * getMaxIntrinsicWidth() -- returned: w=$intrinsic');
|
||||
failureCount += 1;
|
||||
}
|
||||
intrinsic = getMinIntrinsicHeight(constraints);
|
||||
if (intrinsic != constraints.constrainHeight(intrinsic)) {
|
||||
failures.writeln(' * getMinIntrinsicHeight() -- returned: h=$intrinsic');
|
||||
failureCount += 1;
|
||||
}
|
||||
intrinsic = getMaxIntrinsicHeight(constraints);
|
||||
if (intrinsic != constraints.constrainHeight(intrinsic)) {
|
||||
failures.writeln(' * getMaxIntrinsicHeight() -- returned: h=$intrinsic');
|
||||
failureCount += 1;
|
||||
}
|
||||
RenderObject.debugCheckingIntrinsics = false;
|
||||
if (failures.isNotEmpty) {
|
||||
assert(failureCount > 0);
|
||||
throw new FlutterError(
|
||||
'The intrinsic dimension methods of the $runtimeType class returned values that violate the given constraints.\n'
|
||||
'The constraints were: $constraints\n'
|
||||
'The following method${failureCount > 1 ? "s" : ""} returned values outside of those constraints:\n'
|
||||
'$failures'
|
||||
'If you are not writing your own RenderBox subclass, then this is not\n'
|
||||
'your fault. Contact support: https://github.com/flutter/flutter/issues/new'
|
||||
);
|
||||
}
|
||||
return true;
|
||||
});
|
||||
}
|
||||
|
||||
@override
|
||||
@ -763,8 +808,30 @@ abstract class RenderBox extends RenderObject {
|
||||
/// coordinate space of the callee. The callee is responsible for checking
|
||||
/// whether the given position is within its bounds.
|
||||
bool hitTest(HitTestResult result, { Point position }) {
|
||||
assert(!needsLayout);
|
||||
assert(_size != null && 'Missing size. Did you set a size during layout?' != null);
|
||||
assert(() {
|
||||
if (needsLayout) {
|
||||
throw new FlutterError(
|
||||
'Cannot hit test a dirty render box.\n'
|
||||
'The hitTest() method was invoked on this RenderBox:\n'
|
||||
' $this\n'
|
||||
'Unfortunately, since this object has been marked as needing layout, its geometry is not known at this time. '
|
||||
'This means it cannot be accurately hit-tested. Make sure to only mark nodes as needing layout during a pipeline '
|
||||
'flush, so that it is marked clean before any event handling occurs. If you are trying to perform a hit test '
|
||||
'during the layout phase itself, make sure you only hit test nodes that have completed layout (e.g. the node\'s '
|
||||
'children, after their layout() method has been called).'
|
||||
);
|
||||
}
|
||||
if (!hasSize) {
|
||||
throw new FlutterError(
|
||||
'Cannot hit test a render box with no size.\n'
|
||||
'The hitTest() method was invoked on this RenderBox:\n'
|
||||
' $this\n'
|
||||
'Although this node is not marked as needing layout, its size is not set. A RenderBox object must have an '
|
||||
'explicit size before it can be hit-tested. Make sure that the RenderBox in question sets its size during layout.'
|
||||
);
|
||||
}
|
||||
return true;
|
||||
});
|
||||
if (position.x >= 0.0 && position.x < _size.width &&
|
||||
position.y >= 0.0 && position.y < _size.height) {
|
||||
if (hitTestChildren(result, position: position) || hitTestSelf(position)) {
|
||||
|
||||
@ -353,10 +353,69 @@ class RenderFlex extends RenderBox with ContainerRenderObjectMixin<RenderBox, Fl
|
||||
totalChildren++;
|
||||
int flex = _getFlex(child);
|
||||
if (flex > 0) {
|
||||
// Flexible children can only be used when the RenderFlex box's container has a finite size.
|
||||
// When the container is infinite, for example if you are in a scrollable viewport, then
|
||||
// it wouldn't make any sense to have a flexible child.
|
||||
assert(canFlex && 'See https://flutter.io/layout/#flex' is String);
|
||||
assert(() {
|
||||
final String identity = _direction == FlexDirection.horizontal ? 'row' : 'column';
|
||||
final String axis = _direction == FlexDirection.horizontal ? 'horizontal' : 'vertical';
|
||||
final String dimension = _direction == FlexDirection.horizontal ? 'width' : 'height';
|
||||
String error, message;
|
||||
String addendum = '';
|
||||
if (mainAxisAlignment == MainAxisAlignment.collapse) {
|
||||
error = 'RenderFlex children have non-zero flex but mainAxisAlignment is set to "collapse".';
|
||||
message = 'The MainAxisAlignment.collapse value indicates that the $identity is to shrink-wrap its children '
|
||||
'along the $axis axis. Setting a flex on a child (e.g. using a Flexible) indicates that the '
|
||||
'child is to expand to fill the remaining space in the $axis direction.';
|
||||
} else if (mainSize == double.INFINITY) {
|
||||
error = 'RenderFlex children have non-zero flex but incoming $dimension constraints are unbounded.';
|
||||
message = 'When a $identity is in a parent that does not provide a finite $dimension constraint, for example '
|
||||
'if it is in a $axis scrollable, it will try to shrink-wrap its children along the $axis '
|
||||
'axis. Setting a flex on a child (e.g. using a Flexible) indicates that the child is to '
|
||||
'expand to fill the remaining space in the $axis direction.';
|
||||
StringBuffer information = new StringBuffer();
|
||||
RenderBox node = this;
|
||||
switch (_direction) {
|
||||
case FlexDirection.horizontal:
|
||||
while (!node.constraints.hasBoundedWidth && node.parent is RenderBox)
|
||||
node = node.parent;
|
||||
if (!node.constraints.hasBoundedWidth)
|
||||
node = null;
|
||||
break;
|
||||
case FlexDirection.vertical:
|
||||
while (!node.constraints.hasBoundedHeight && node.parent is RenderBox)
|
||||
node = node.parent;
|
||||
if (!node.constraints.hasBoundedHeight)
|
||||
node = null;
|
||||
break;
|
||||
}
|
||||
if (node != null) {
|
||||
information.writeln('The nearest ancestor providing an unbounded width constraint is:');
|
||||
information.writeln(' $node');
|
||||
List<String> description = <String>[];
|
||||
node.debugFillDescription(description);
|
||||
for (String line in description)
|
||||
information.writeln(' $line');
|
||||
}
|
||||
information.writeln('See also: https://flutter.io/layout/');
|
||||
addendum = information.toString();
|
||||
} else {
|
||||
return true;
|
||||
}
|
||||
throw new FlutterError(
|
||||
'$error\n'
|
||||
'$message\n'
|
||||
'These two directives are mutually exclusive. If a parent is to shrink-wrap its child, the child '
|
||||
'cannot simultaneously expand to fit its parent.\n'
|
||||
'The affected RenderFlex is:\n'
|
||||
' $this\n'
|
||||
'The creator information is set to:\n'
|
||||
' $debugCreator\n'
|
||||
'$addendum'
|
||||
'If this message did not help you determine the problem, consider using debugDumpRenderTree():\n'
|
||||
' https://flutter.io/debugging/#rendering-layer\n'
|
||||
' http://docs.flutter.io/flutter/rendering/debugDumpRenderTree.html\n'
|
||||
'If none of the above helps enough to fix this problem, please don\'t hesitate to file a bug:\n'
|
||||
' https://github.com/flutter/flutter/issues/new'
|
||||
);
|
||||
});
|
||||
totalFlex += childParentData.flex;
|
||||
} else {
|
||||
BoxConstraints innerConstraints;
|
||||
|
||||
@ -959,8 +959,8 @@ abstract class RenderObject extends AbstractNode implements HitTestTarget {
|
||||
/// Verify that the object's constraints are being met. Override
|
||||
/// this function in a subclass to verify that your state matches
|
||||
/// the constraints object. This function is only called in checked
|
||||
/// mode. If the constraints are not met, it should assert or throw
|
||||
/// an exception.
|
||||
/// mode and only when needsLayout is false. If the constraints are
|
||||
/// not met, it should assert or throw an exception.
|
||||
void debugAssertDoesMeetConstraints();
|
||||
|
||||
/// When true, debugAssertDoesMeetConstraints() is currently
|
||||
@ -1111,6 +1111,7 @@ abstract class RenderObject extends AbstractNode implements HitTestTarget {
|
||||
/// implemented here) to return early if the child does not need to do any
|
||||
/// work to update its layout information.
|
||||
void layout(Constraints constraints, { bool parentUsesSize: false }) {
|
||||
assert(constraints != null);
|
||||
assert(constraints.debugAssertIsNormalized);
|
||||
assert(!_debugDoingThisResize);
|
||||
assert(!_debugDoingThisLayout);
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user