diff --git a/packages/flutter/lib/src/widgets/transitions.dart b/packages/flutter/lib/src/widgets/transitions.dart index 24d90161c93..76ef816cc93 100644 --- a/packages/flutter/lib/src/widgets/transitions.dart +++ b/packages/flutter/lib/src/widgets/transitions.dart @@ -441,8 +441,10 @@ class SizeTransition extends AnimatedWidget { this.axis = Axis.vertical, required Animation sizeFactor, this.axisAlignment = 0.0, + this.fixedCrossAxisSizeFactor, this.child, - }) : super(listenable: sizeFactor); + }) : assert(fixedCrossAxisSizeFactor == null || fixedCrossAxisSizeFactor >= 0.0), + super(listenable: sizeFactor); /// [Axis.horizontal] if [sizeFactor] modifies the width, otherwise /// [Axis.vertical]. @@ -471,6 +473,14 @@ class SizeTransition extends AnimatedWidget { /// A value of 0.0 (the default) indicates the center for either [axis] value. final double axisAlignment; + /// The factor by which to multiply the cross axis size of the child. + /// + /// If the value of [fixedCrossAxisSizeFactor] is less than one, the child + /// will be clipped along the appropriate axis. + /// + /// If `null` (the default), the cross axis size is as large as the parent. + final double? fixedCrossAxisSizeFactor; + /// The widget below this widget in the tree. /// /// {@macro flutter.widgets.ProxyWidget.child} @@ -487,8 +497,8 @@ class SizeTransition extends AnimatedWidget { return ClipRect( child: Align( alignment: alignment, - heightFactor: axis == Axis.vertical ? math.max(sizeFactor.value, 0.0) : null, - widthFactor: axis == Axis.horizontal ? math.max(sizeFactor.value, 0.0) : null, + heightFactor: axis == Axis.vertical ? math.max(sizeFactor.value, 0.0) : fixedCrossAxisSizeFactor, + widthFactor: axis == Axis.horizontal ? math.max(sizeFactor.value, 0.0) : fixedCrossAxisSizeFactor, child: child, ), ); diff --git a/packages/flutter/test/widgets/transitions_test.dart b/packages/flutter/test/widgets/transitions_test.dart index 92f74baf604..f7c8eb17267 100644 --- a/packages/flutter/test/widgets/transitions_test.dart +++ b/packages/flutter/test/widgets/transitions_test.dart @@ -244,6 +244,7 @@ void main() { textDirection: TextDirection.ltr, child: SizeTransition( sizeFactor: animation, + fixedCrossAxisSizeFactor: 2.0, child: const Text('Ready'), ), ); @@ -252,18 +253,22 @@ void main() { final RenderPositionedBox actualPositionedBox = tester.renderObject(find.byType(Align)); expect(actualPositionedBox.heightFactor, 0.0); + expect(actualPositionedBox.widthFactor, 2.0); controller.value = 0.0; await tester.pump(); expect(actualPositionedBox.heightFactor, 0.0); + expect(actualPositionedBox.widthFactor, 2.0); controller.value = 0.75; await tester.pump(); expect(actualPositionedBox.heightFactor, 0.5); + expect(actualPositionedBox.widthFactor, 2.0); controller.value = 1.0; await tester.pump(); expect(actualPositionedBox.heightFactor, 1.0); + expect(actualPositionedBox.widthFactor, 2.0); }); testWidgetsWithLeakTracking('SizeTransition clamps negative size factors - horizontal axis', (WidgetTester tester) async { @@ -275,6 +280,7 @@ void main() { child: SizeTransition( axis: Axis.horizontal, sizeFactor: animation, + fixedCrossAxisSizeFactor: 1.0, child: const Text('Ready'), ), ); @@ -283,18 +289,139 @@ void main() { final RenderPositionedBox actualPositionedBox = tester.renderObject(find.byType(Align)); expect(actualPositionedBox.widthFactor, 0.0); + expect(actualPositionedBox.heightFactor, 1.0); controller.value = 0.0; await tester.pump(); expect(actualPositionedBox.widthFactor, 0.0); + expect(actualPositionedBox.heightFactor, 1.0); controller.value = 0.75; await tester.pump(); expect(actualPositionedBox.widthFactor, 0.5); + expect(actualPositionedBox.heightFactor, 1.0); controller.value = 1.0; await tester.pump(); expect(actualPositionedBox.widthFactor, 1.0); + expect(actualPositionedBox.heightFactor, 1.0); + }); + + testWidgetsWithLeakTracking('SizeTransition with fixedCrossAxisSizeFactor should size its cross axis from its children - vertical axis', (WidgetTester tester) async { + final AnimationController controller = AnimationController(vsync: const TestVSync()); + final Animation animation = Tween(begin: 0, end: 1.0).animate(controller); + + const Key key = Key('key'); + + final Widget widget = Directionality( + textDirection: TextDirection.ltr, + child: Center( + child: SizedBox( + key: key, + child: SizeTransition( + sizeFactor: animation, + fixedCrossAxisSizeFactor: 1.0, + child: const SizedBox.square(dimension: 100), + ), + ), + ), + ); + + await tester.pumpWidget(widget); + + final RenderPositionedBox actualPositionedBox = tester.renderObject(find.byType(Align)); + expect(actualPositionedBox.heightFactor, 0.0); + expect(actualPositionedBox.widthFactor, 1.0); + expect(tester.getSize(find.byKey(key)), const Size(100, 0)); + + controller.value = 0.0; + await tester.pump(); + expect(actualPositionedBox.heightFactor, 0.0); + expect(actualPositionedBox.widthFactor, 1.0); + expect(tester.getSize(find.byKey(key)), const Size(100, 0)); + + controller.value = 0.5; + await tester.pump(); + expect(actualPositionedBox.heightFactor, 0.5); + expect(actualPositionedBox.widthFactor, 1.0); + expect(tester.getSize(find.byKey(key)), const Size(100, 50)); + + controller.value = 1.0; + await tester.pump(); + expect(actualPositionedBox.heightFactor, 1.0); + expect(actualPositionedBox.widthFactor, 1.0); + expect(tester.getSize(find.byKey(key)), const Size.square(100)); + + controller.value = 0.5; + await tester.pump(); + expect(actualPositionedBox.heightFactor, 0.5); + expect(actualPositionedBox.widthFactor, 1.0); + expect(tester.getSize(find.byKey(key)), const Size(100, 50)); + + controller.value = 0.0; + await tester.pump(); + expect(actualPositionedBox.heightFactor, 0.0); + expect(actualPositionedBox.widthFactor, 1.0); + expect(tester.getSize(find.byKey(key)), const Size(100, 0)); + }); + + testWidgetsWithLeakTracking('SizeTransition with fixedCrossAxisSizeFactor should size its cross axis from its children - horizontal axis', (WidgetTester tester) async { + final AnimationController controller = AnimationController(vsync: const TestVSync()); + final Animation animation = Tween(begin: 0.0, end: 1.0).animate(controller); + + const Key key = Key('key'); + + final Widget widget = Directionality( + textDirection: TextDirection.ltr, + child: Center( + child: SizedBox( + key: key, + child: SizeTransition( + axis: Axis.horizontal, + sizeFactor: animation, + fixedCrossAxisSizeFactor: 1.0, + child: const SizedBox.square(dimension: 100), + ), + ), + ), + ); + + await tester.pumpWidget(widget); + + final RenderPositionedBox actualPositionedBox = tester.renderObject(find.byType(Align)); + expect(actualPositionedBox.heightFactor, 1.0); + expect(actualPositionedBox.widthFactor, 0.0); + expect(tester.getSize(find.byKey(key)), const Size(0, 100)); + + controller.value = 0.0; + await tester.pump(); + expect(actualPositionedBox.heightFactor, 1.0); + expect(actualPositionedBox.widthFactor, 0.0); + expect(tester.getSize(find.byKey(key)), const Size(0, 100)); + + controller.value = 0.5; + await tester.pump(); + expect(actualPositionedBox.heightFactor, 1.0); + expect(actualPositionedBox.widthFactor, 0.5); + expect(tester.getSize(find.byKey(key)), const Size(50, 100)); + + controller.value = 1.0; + await tester.pump(); + expect(actualPositionedBox.heightFactor, 1.0); + expect(actualPositionedBox.widthFactor, 1.0); + expect(tester.getSize(find.byKey(key)), const Size.square(100)); + + controller.value = 0.5; + await tester.pump(); + expect(actualPositionedBox.heightFactor, 1.0); + expect(actualPositionedBox.widthFactor, 0.5); + expect(tester.getSize(find.byKey(key)), const Size(50, 100)); + + controller.value = 0.0; + await tester.pump(); + expect(actualPositionedBox.heightFactor, 1.0); + expect(actualPositionedBox.widthFactor, 0.0); + expect(tester.getSize(find.byKey(key)), const Size(0, 100)); }); testWidgetsWithLeakTracking('MatrixTransition animates', (WidgetTester tester) async {