Commit 712eb969 authored by Adam Barth's avatar Adam Barth Committed by GitHub

CustomSingleChildLayout should be able to resize (#7528)

It's not correct to set sizedByParent for
RenderCustomSingleChildLayoutBox because the delegate's size function
might depend on information other than the incoming constraints.
parent 23361d5a
......@@ -736,18 +736,54 @@ class RenderFractionallySizedOverflowBox extends RenderAligningShiftedBox {
}
/// A delegate for computing the layout of a render object with a single child.
class SingleChildLayoutDelegate {
/// Returns the size of this object given the incoming constraints.
abstract class SingleChildLayoutDelegate {
// TODO(abarth): This class should take a Listenable to drive relayout.
/// The size of this object given the incoming constraints.
///
/// Defaults to the biggest size that satifies the given constraints.
Size getSize(BoxConstraints constraints) => constraints.biggest;
/// Returns the box constraints for the child given the incoming constraints.
/// The constraints for the child given the incoming constraints.
///
/// During layout, the child is given the layout constraints returned by this
/// function. The child is required to pick a size for itself that satisfies
/// these constraints.
///
/// Defaults to the given constraints.
BoxConstraints getConstraintsForChild(BoxConstraints constraints) => constraints;
/// Returns the position where the child should be placed given the size of this object and the size of the child.
/// The position where the child should be placed.
///
/// The `size` argument is the size of the parent, which might be different
/// from the value returned by [getSize] if that size doesn't satisfy the
/// constraints passed to [getSize]. The `childSize` argument is the size of
/// the child, which will satisfy the constraints returned by
/// [getConstraintsForChild].
///
/// Defaults to positioning the child in the upper left corner of the parent.
Offset getPositionForChild(Size size, Size childSize) => Offset.zero;
/// Override this method to return true when the child needs to be laid out.
bool shouldRelayout(@checked SingleChildLayoutDelegate oldDelegate) => true;
/// Called whenever a new instance of the custom layout delegate class is
/// provided to the [RenderCustomSingleChildLayoutBox] object, or any time
/// that a new [CustomSingleChildLayout] object is created with a new instance
/// of the custom layout delegate class (which amounts to the same thing,
/// because the latter is implemented in terms of the former).
///
/// If the new instance represents different information than the old
/// instance, then the method should return `true`, otherwise it should return
/// `false`.
///
/// If the method returns `false`, then the [getSize],
/// [getConstraintsForChild], and [getPositionForChild] calls might be
/// optimized away.
///
/// It's possible that the layout methods will get called even if
/// [shouldRelayout] returns `false` (e.g. if an ancestor changed its layout).
/// It's also possible that the layout method will get called
/// without [shouldRelayout] being called at all (e.g. if the parent changes
/// size).
bool shouldRelayout(@checked SingleChildLayoutDelegate oldDelegate);
}
/// Defers the layout of its single child to a delegate.
......@@ -819,16 +855,9 @@ class RenderCustomSingleChildLayoutBox extends RenderShiftedBox {
return 0.0;
}
@override
bool get sizedByParent => true;
@override
void performResize() {
size = _getSize(constraints);
}
@override
void performLayout() {
size = _getSize(constraints);
if (child != null) {
BoxConstraints childConstraints = delegate.getConstraintsForChild(constraints);
assert(childConstraints.debugAssertIsValid(isAppliedConstraint: true));
......
......@@ -24,11 +24,7 @@ class TestSingleChildLayoutDelegate extends SingleChildLayoutDelegate {
assert(!RenderObject.debugCheckingIntrinsics);
constraintsFromGetConstraintsForChild = constraints;
return const BoxConstraints(
minWidth: 100.0,
maxWidth: 150.0,
minHeight: 200.0,
maxHeight: 400.0
);
minWidth: 100.0, maxWidth: 150.0, minHeight: 200.0, maxHeight: 400.0);
}
@override
......@@ -50,53 +46,95 @@ class TestSingleChildLayoutDelegate extends SingleChildLayoutDelegate {
}
}
class FixedSizeLayoutDelegate extends SingleChildLayoutDelegate {
FixedSizeLayoutDelegate(this.size);
final Size size;
@override
Size getSize(BoxConstraints constraints) => size;
@override
BoxConstraints getConstraintsForChild(BoxConstraints constraints) {
return new BoxConstraints.tight(size);
}
@override
bool shouldRelayout(FixedSizeLayoutDelegate oldDelegate) {
return size != oldDelegate.size;
}
}
Widget buildFrame(SingleChildLayoutDelegate delegate) {
return new Center(child: new CustomSingleChildLayout(delegate: delegate, child: new Container()));
return new Center(
child: new CustomSingleChildLayout(
delegate: delegate,
child: new Container(),
),
);
}
void main() {
testWidgets('Control test for CustomSingleChildLayout', (WidgetTester tester) async {
TestSingleChildLayoutDelegate delegate = new TestSingleChildLayoutDelegate();
await tester.pumpWidget(buildFrame(delegate));
testWidgets('Control test for CustomSingleChildLayout',
(WidgetTester tester) async {
TestSingleChildLayoutDelegate delegate =
new TestSingleChildLayoutDelegate();
await tester.pumpWidget(buildFrame(delegate));
expect(delegate.constraintsFromGetSize.minWidth, 0.0);
expect(delegate.constraintsFromGetSize.maxWidth, 800.0);
expect(delegate.constraintsFromGetSize.minHeight, 0.0);
expect(delegate.constraintsFromGetSize.maxHeight, 600.0);
expect(delegate.constraintsFromGetConstraintsForChild.minWidth, 0.0);
expect(delegate.constraintsFromGetConstraintsForChild.maxWidth, 800.0);
expect(delegate.constraintsFromGetConstraintsForChild.minHeight, 0.0);
expect(delegate.constraintsFromGetConstraintsForChild.maxHeight, 600.0);
expect(delegate.sizeFromGetPositionForChild.width, 200.0);
expect(delegate.sizeFromGetPositionForChild.height, 300.0);
expect(delegate.childSizeFromGetPositionForChild.width, 150.0);
expect(delegate.childSizeFromGetPositionForChild.height, 400.0);
});
expect(delegate.constraintsFromGetSize.minWidth, 0.0);
expect(delegate.constraintsFromGetSize.maxWidth, 800.0);
expect(delegate.constraintsFromGetSize.minHeight, 0.0);
expect(delegate.constraintsFromGetSize.maxHeight, 600.0);
testWidgets('Test SingleChildDelegate shouldRelayout method',
(WidgetTester tester) async {
TestSingleChildLayoutDelegate delegate =
new TestSingleChildLayoutDelegate();
await tester.pumpWidget(buildFrame(delegate));
// Layout happened because the delegate was set.
expect(delegate.constraintsFromGetConstraintsForChild,
isNotNull); // i.e. layout happened
expect(delegate.shouldRelayoutCalled, isFalse);
// Layout did not happen because shouldRelayout() returned false.
delegate = new TestSingleChildLayoutDelegate();
delegate.shouldRelayoutValue = false;
await tester.pumpWidget(buildFrame(delegate));
expect(delegate.shouldRelayoutCalled, isTrue);
expect(delegate.constraintsFromGetConstraintsForChild, isNull);
// Layout happened because shouldRelayout() returned true.
delegate = new TestSingleChildLayoutDelegate();
delegate.shouldRelayoutValue = true;
await tester.pumpWidget(buildFrame(delegate));
expect(delegate.shouldRelayoutCalled, isTrue);
expect(delegate.constraintsFromGetConstraintsForChild, isNotNull);
});
expect(delegate.constraintsFromGetConstraintsForChild.minWidth, 0.0);
expect(delegate.constraintsFromGetConstraintsForChild.maxWidth, 800.0);
expect(delegate.constraintsFromGetConstraintsForChild.minHeight, 0.0);
expect(delegate.constraintsFromGetConstraintsForChild.maxHeight, 600.0);
testWidgets('Delegate can change size', (WidgetTester tester) async {
await tester.pumpWidget(
buildFrame(new FixedSizeLayoutDelegate(const Size(100.0, 200.0))));
expect(delegate.sizeFromGetPositionForChild.width, 200.0);
expect(delegate.sizeFromGetPositionForChild.height, 300.0);
RenderBox box = tester.renderObject(find.byType(CustomSingleChildLayout));
expect(box.size, equals(const Size(100.0, 200.0)));
expect(delegate.childSizeFromGetPositionForChild.width, 150.0);
expect(delegate.childSizeFromGetPositionForChild.height, 400.0);
});
await tester.pumpWidget(
buildFrame(new FixedSizeLayoutDelegate(const Size(150.0, 240.0))));
testWidgets('Test SingleChildDelegate shouldRelayout method', (WidgetTester tester) async {
TestSingleChildLayoutDelegate delegate = new TestSingleChildLayoutDelegate();
await tester.pumpWidget(buildFrame(delegate));
// Layout happened because the delegate was set.
expect(delegate.constraintsFromGetConstraintsForChild, isNotNull); // i.e. layout happened
expect(delegate.shouldRelayoutCalled, isFalse);
// Layout did not happen because shouldRelayout() returned false.
delegate = new TestSingleChildLayoutDelegate();
delegate.shouldRelayoutValue = false;
await tester.pumpWidget(buildFrame(delegate));
expect(delegate.shouldRelayoutCalled, isTrue);
expect(delegate.constraintsFromGetConstraintsForChild, isNull);
// Layout happened because shouldRelayout() returned true.
delegate = new TestSingleChildLayoutDelegate();
delegate.shouldRelayoutValue = true;
await tester.pumpWidget(buildFrame(delegate));
expect(delegate.shouldRelayoutCalled, isTrue);
expect(delegate.constraintsFromGetConstraintsForChild, isNotNull);
box = tester.renderObject(find.byType(CustomSingleChildLayout));
expect(box.size, equals(const Size(150.0, 240.0)));
});
}
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