Fix bugs in RenderSizedOverflowBox and RenderFractionallySizedOverflowBox (#23492)
Bug #1: These didn't work with directional alignments, due to an error
in the types of the constructor arguments.
Bug #2: Pretty sure RenderSizedOverflowBox never worked at all. As
soon as I wrote a test for it, the test showed that there was a
fundamental bug in its performLayout method: it didn't set
parentUsesSize, but it immediately tried to use the child's size.
diff --git a/packages/flutter/lib/src/rendering/shifted_box.dart b/packages/flutter/lib/src/rendering/shifted_box.dart
index f9a181a..c9b74ac 100644
--- a/packages/flutter/lib/src/rendering/shifted_box.dart
+++ b/packages/flutter/lib/src/rendering/shifted_box.dart
@@ -229,6 +229,9 @@
/// Initializes member variables for subclasses.
///
/// The [alignment] argument must not be null.
+ ///
+ /// The [textDirection] must be non-null if the [alignment] is
+ /// direction-sensitive.
RenderAligningShiftedBox({
AlignmentGeometry alignment = Alignment.center,
@required TextDirection textDirection,
@@ -303,6 +306,7 @@
///
/// This method must be called after the child has been laid out and
/// this object's own size has been set.
+ @protected
void alignChild() {
_resolve();
assert(child != null);
@@ -707,6 +711,9 @@
/// A render object that is a specific size but passes its original constraints
/// through to its child, which it allows to overflow.
///
+/// If the child's resulting size differs from this render object's size, then
+/// the child is aligned according to the [alignment] property.
+///
/// See also:
/// * [RenderUnconstrainedBox] for a render object that allows its children
/// to render themselves unconstrained, expands to fit them, and considers
@@ -717,11 +724,14 @@
class RenderSizedOverflowBox extends RenderAligningShiftedBox {
/// Creates a render box of a given size that lets its child overflow.
///
- /// The [requestedSize] argument must not be null.
+ /// The [requestedSize] and [alignment] arguments must not be null.
+ ///
+ /// The [textDirection] argument must not be null if the [alignment] is
+ /// direction-sensitive.
RenderSizedOverflowBox({
RenderBox child,
@required Size requestedSize,
- Alignment alignment = Alignment.center,
+ AlignmentGeometry alignment = Alignment.center,
TextDirection textDirection,
}) : assert(requestedSize != null),
_requestedSize = requestedSize,
@@ -769,7 +779,7 @@
void performLayout() {
size = constraints.constrain(_requestedSize);
if (child != null) {
- child.layout(constraints);
+ child.layout(constraints, parentUsesSize: true);
alignChild();
}
}
@@ -783,17 +793,24 @@
/// for a given axis is null, then the constraints from the parent are just
/// passed through instead.
///
-/// It then tries to size itself to the size of its child.
+/// It then tries to size itself to the size of its child. Where this is not
+/// possible (e.g. if the constraints from the parent are themselves tight), the
+/// child is aligned according to [alignment].
class RenderFractionallySizedOverflowBox extends RenderAligningShiftedBox {
/// Creates a render box that sizes its child to a fraction of the total available space.
///
/// If non-null, the [widthFactor] and [heightFactor] arguments must be
/// non-negative.
+ ///
+ /// The [alignment] must not be null.
+ ///
+ /// The [textDirection] must be non-null if the [alignment] is
+ /// direction-sensitive.
RenderFractionallySizedOverflowBox({
RenderBox child,
double widthFactor,
double heightFactor,
- Alignment alignment = Alignment.center,
+ AlignmentGeometry alignment = Alignment.center,
TextDirection textDirection,
}) : _widthFactor = widthFactor,
_heightFactor = heightFactor,
diff --git a/packages/flutter/test/widgets/fractionally_sized_box_test.dart b/packages/flutter/test/widgets/fractionally_sized_box_test.dart
index 2feb6b5..006572e 100644
--- a/packages/flutter/test/widgets/fractionally_sized_box_test.dart
+++ b/packages/flutter/test/widgets/fractionally_sized_box_test.dart
@@ -29,4 +29,36 @@
expect(box.size, equals(const Size(50.0, 25.0)));
expect(box.localToGlobal(Offset.zero), equals(const Offset(25.0, 37.5)));
});
+
+ testWidgets('FractionallySizedBox alignment', (WidgetTester tester) async {
+ final GlobalKey inner = GlobalKey();
+ await tester.pumpWidget(Directionality(
+ textDirection: TextDirection.rtl,
+ child: FractionallySizedBox(
+ widthFactor: 0.5,
+ heightFactor: 0.5,
+ alignment: Alignment.topRight,
+ child: Placeholder(key: inner),
+ ),
+ ));
+ final RenderBox box = inner.currentContext.findRenderObject();
+ expect(box.size, equals(const Size(400.0, 300.0)));
+ expect(box.localToGlobal(box.size.center(Offset.zero)), equals(const Offset(800.0 - 400.0 / 2.0, 0.0 + 300.0 / 2.0)));
+ });
+
+ testWidgets('FractionallySizedBox alignment (direction-sensitive)', (WidgetTester tester) async {
+ final GlobalKey inner = GlobalKey();
+ await tester.pumpWidget(Directionality(
+ textDirection: TextDirection.rtl,
+ child: FractionallySizedBox(
+ widthFactor: 0.5,
+ heightFactor: 0.5,
+ alignment: AlignmentDirectional.topEnd,
+ child: Placeholder(key: inner),
+ ),
+ ));
+ final RenderBox box = inner.currentContext.findRenderObject();
+ expect(box.size, equals(const Size(400.0, 300.0)));
+ expect(box.localToGlobal(box.size.center(Offset.zero)), equals(const Offset(0.0 + 400.0 / 2.0, 0.0 + 300.0 / 2.0)));
+ });
}
diff --git a/packages/flutter/test/widgets/overflow_box_test.dart b/packages/flutter/test/widgets/overflow_box_test.dart
index fbfb0fa..e6c0517 100644
--- a/packages/flutter/test/widgets/overflow_box_test.dart
+++ b/packages/flutter/test/widgets/overflow_box_test.dart
@@ -49,4 +49,50 @@
'maxHeight: 4.0',
]);
});
+
+ testWidgets('SizedOverflowBox alignment', (WidgetTester tester) async {
+ final GlobalKey inner = GlobalKey();
+ await tester.pumpWidget(Directionality(
+ textDirection: TextDirection.rtl,
+ child: Center(
+ child: SizedOverflowBox(
+ size: const Size(100.0, 100.0),
+ alignment: Alignment.topRight,
+ child: Container(height: 50.0, width: 50.0, key: inner),
+ ),
+ ),
+ ));
+ final RenderBox box = inner.currentContext.findRenderObject();
+ expect(box.size, equals(const Size(50.0, 50.0)));
+ expect(
+ box.localToGlobal(box.size.center(Offset.zero)),
+ equals(const Offset(
+ (800.0 - 100.0) / 2.0 + 100.0 - 50.0 / 2.0,
+ (600.0 - 100.0) / 2.0 + 0.0 + 50.0 / 2.0,
+ )),
+ );
+ });
+
+ testWidgets('SizedOverflowBox alignment (direction-sensitive)', (WidgetTester tester) async {
+ final GlobalKey inner = GlobalKey();
+ await tester.pumpWidget(Directionality(
+ textDirection: TextDirection.rtl,
+ child: Center(
+ child: SizedOverflowBox(
+ size: const Size(100.0, 100.0),
+ alignment: AlignmentDirectional.bottomStart,
+ child: Container(height: 50.0, width: 50.0, key: inner),
+ ),
+ ),
+ ));
+ final RenderBox box = inner.currentContext.findRenderObject();
+ expect(box.size, equals(const Size(50.0, 50.0)));
+ expect(
+ box.localToGlobal(box.size.center(Offset.zero)),
+ equals(const Offset(
+ (800.0 - 100.0) / 2.0 + 100.0 - 50.0 / 2.0,
+ (600.0 - 100.0) / 2.0 + 100.0 - 50.0 / 2.0,
+ )),
+ );
+ });
}