From 9f374f12ea890d31f81b3373face5f99cf88e8bc Mon Sep 17 00:00:00 2001 From: Alex Li Date: Wed, 26 Jul 2023 02:24:43 +0800 Subject: [PATCH] =?UTF-8?q?=F0=9F=9A=80=20Expose=20`scrollControlDisabledM?= =?UTF-8?q?axHeightRatio`=20to=20the=20modal=20bottom=20sheet=20(#129688)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Adding the `scrollControlDisabledMaxHeightRatio` parameter for modal bottom sheet widgets, and using the default value `9.0 / 16.0` to avoid breaking. Resolves #129690. --- .../lib/src/material/bottom_sheet.dart | 49 +++++++++++++---- .../test/material/bottom_sheet_test.dart | 54 ++++++++++++++++++- 2 files changed, 93 insertions(+), 10 deletions(-) diff --git a/packages/flutter/lib/src/material/bottom_sheet.dart b/packages/flutter/lib/src/material/bottom_sheet.dart index c59afd7889..f84cfe9ae1 100644 --- a/packages/flutter/lib/src/material/bottom_sheet.dart +++ b/packages/flutter/lib/src/material/bottom_sheet.dart @@ -26,6 +26,7 @@ const Duration _bottomSheetExitDuration = Duration(milliseconds: 200); const Curve _modalBottomSheetCurve = decelerateEasing; const double _minFlingVelocity = 700.0; const double _closeProgressThreshold = 0.5; +const double _defaultScrollControlDisabledMaxHeightRatio = 9.0 / 16.0; /// A callback for when the user begins dragging the bottom sheet. /// @@ -471,24 +472,26 @@ class _DragHandle extends StatelessWidget { } class _BottomSheetLayoutWithSizeListener extends SingleChildRenderObjectWidget { - const _BottomSheetLayoutWithSizeListener({ + required this.onChildSizeChanged, required this.animationValue, required this.isScrollControlled, - required this.onChildSizeChanged, + required this.scrollControlDisabledMaxHeightRatio, super.child, }); + final _SizeChangeCallback onChildSizeChanged; final double animationValue; final bool isScrollControlled; - final _SizeChangeCallback onChildSizeChanged; + final double scrollControlDisabledMaxHeightRatio; @override _RenderBottomSheetLayoutWithSizeListener createRenderObject(BuildContext context) { return _RenderBottomSheetLayoutWithSizeListener( + onChildSizeChanged: onChildSizeChanged, animationValue: animationValue, isScrollControlled: isScrollControlled, - onChildSizeChanged: onChildSizeChanged, + scrollControlDisabledMaxHeightRatio: scrollControlDisabledMaxHeightRatio, ); } @@ -497,6 +500,7 @@ class _BottomSheetLayoutWithSizeListener extends SingleChildRenderObjectWidget { renderObject.onChildSizeChanged = onChildSizeChanged; renderObject.animationValue = animationValue; renderObject.isScrollControlled = isScrollControlled; + renderObject.scrollControlDisabledMaxHeightRatio = scrollControlDisabledMaxHeightRatio; } } @@ -506,9 +510,11 @@ class _RenderBottomSheetLayoutWithSizeListener extends RenderShiftedBox { required _SizeChangeCallback onChildSizeChanged, required double animationValue, required bool isScrollControlled, - }) : _animationValue = animationValue, + required double scrollControlDisabledMaxHeightRatio, + }) : _onChildSizeChanged = onChildSizeChanged, + _animationValue = animationValue, _isScrollControlled = isScrollControlled, - _onChildSizeChanged = onChildSizeChanged, + _scrollControlDisabledMaxHeightRatio = scrollControlDisabledMaxHeightRatio, super(child); Size _lastSize = Size.zero; @@ -546,6 +552,17 @@ class _RenderBottomSheetLayoutWithSizeListener extends RenderShiftedBox { markNeedsLayout(); } + double get scrollControlDisabledMaxHeightRatio => _scrollControlDisabledMaxHeightRatio; + double _scrollControlDisabledMaxHeightRatio; + set scrollControlDisabledMaxHeightRatio(double newValue) { + if (_scrollControlDisabledMaxHeightRatio == newValue) { + return; + } + + _scrollControlDisabledMaxHeightRatio = newValue; + markNeedsLayout(); + } + Size _getSize(BoxConstraints constraints) { return constraints.constrain(constraints.biggest); } @@ -591,13 +608,13 @@ class _RenderBottomSheetLayoutWithSizeListener extends RenderShiftedBox { return _getSize(constraints); } - BoxConstraints _getConstraintsForChild(BoxConstraints constraints) { + BoxConstraints _getConstraintsForChild(BoxConstraints constraints) { return BoxConstraints( minWidth: constraints.maxWidth, maxWidth: constraints.maxWidth, maxHeight: isScrollControlled - ? constraints.maxHeight - : constraints.maxHeight * 9.0 / 16.0, + ? constraints.maxHeight + : constraints.maxHeight * scrollControlDisabledMaxHeightRatio, ); } @@ -634,12 +651,14 @@ class _ModalBottomSheet extends StatefulWidget { this.clipBehavior, this.constraints, this.isScrollControlled = false, + this.scrollControlDisabledMaxHeightRatio = _defaultScrollControlDisabledMaxHeightRatio, this.enableDrag = true, this.showDragHandle = false, }); final ModalBottomSheetRoute route; final bool isScrollControlled; + final double scrollControlDisabledMaxHeightRatio; final Color? backgroundColor; final double? elevation; final ShapeBorder? shape; @@ -730,6 +749,7 @@ class _ModalBottomSheetState extends State<_ModalBottomSheet> { }, animationValue: animationValue, isScrollControlled: widget.isScrollControlled, + scrollControlDisabledMaxHeightRatio: widget.scrollControlDisabledMaxHeightRatio, child: child, ), ), @@ -815,6 +835,7 @@ class ModalBottomSheetRoute extends PopupRoute { this.enableDrag = true, this.showDragHandle, required this.isScrollControlled, + this.scrollControlDisabledMaxHeightRatio = _defaultScrollControlDisabledMaxHeightRatio, super.settings, this.transitionAnimationController, this.anchorPoint, @@ -842,6 +863,13 @@ class ModalBottomSheetRoute extends PopupRoute { /// to have the bottom sheet be draggable. final bool isScrollControlled; + /// The max height constraint ratio for the bottom sheet + /// when [isScrollControlled] set to false, + /// no ratio will be applied when [isScrollControlled] set to true. + /// + /// Defaults to 9 / 16. + final double scrollControlDisabledMaxHeightRatio; + /// The bottom sheet's background color. /// /// Defines the bottom sheet's [Material.color]. @@ -1026,6 +1054,7 @@ class ModalBottomSheetRoute extends PopupRoute { clipBehavior: clipBehavior, constraints: constraints, isScrollControlled: isScrollControlled, + scrollControlDisabledMaxHeightRatio: scrollControlDisabledMaxHeightRatio, enableDrag: enableDrag, showDragHandle: showDragHandle ?? (enableDrag && (sheetTheme.showDragHandle ?? false)), ); @@ -1192,6 +1221,7 @@ Future showModalBottomSheet({ BoxConstraints? constraints, Color? barrierColor, bool isScrollControlled = false, + double scrollControlDisabledMaxHeightRatio = _defaultScrollControlDisabledMaxHeightRatio, bool useRootNavigator = false, bool isDismissible = true, bool enableDrag = true, @@ -1210,6 +1240,7 @@ Future showModalBottomSheet({ builder: builder, capturedThemes: InheritedTheme.capture(from: context, to: navigator.context), isScrollControlled: isScrollControlled, + scrollControlDisabledMaxHeightRatio: scrollControlDisabledMaxHeightRatio, barrierLabel: barrierLabel ?? localizations.scrimLabel, barrierOnTapHint: localizations.scrimOnTapHint(localizations.bottomSheetLabel), backgroundColor: backgroundColor, diff --git a/packages/flutter/test/material/bottom_sheet_test.dart b/packages/flutter/test/material/bottom_sheet_test.dart index b6e5db8401..49695c859f 100644 --- a/packages/flutter/test/material/bottom_sheet_test.dart +++ b/packages/flutter/test/material/bottom_sheet_test.dart @@ -1789,7 +1789,7 @@ void main() { }); group('constraints', () { - testWidgets('default constraints are max width 640 in material 3', (WidgetTester tester) async { + testWidgets('default constraints are max width 640 in material 3', (WidgetTester tester) async { await tester.pumpWidget( MaterialApp( theme: ThemeData.light(useMaterial3: true), @@ -2045,6 +2045,58 @@ void main() { ); }); + group('scrollControlDisabledMaxHeightRatio', () { + Future test( + WidgetTester tester, + bool isScrollControlled, + double scrollControlDisabledMaxHeightRatio, + ) async { + await tester.pumpWidget( + MaterialApp( + home: Scaffold( + body: Builder(builder: (BuildContext context) { + return Center( + child: ElevatedButton( + child: const Text('Press me'), + onPressed: () { + showModalBottomSheet( + context: context, + isScrollControlled: isScrollControlled, + scrollControlDisabledMaxHeightRatio: scrollControlDisabledMaxHeightRatio, + builder: (BuildContext context) => const SizedBox.expand( + child: Text('BottomSheet'), + ), + ); + }, + ), + ); + }), + ), + ), + ); + await tester.tap(find.text('Press me')); + await tester.pumpAndSettle(); + expect( + tester.getRect(find.text('BottomSheet')), + Rect.fromLTRB( + 80, + 600 * (isScrollControlled ? 0 : (1 - scrollControlDisabledMaxHeightRatio)), + 720, + 600, + ), + ); + } + + testWidgets('works at 9 / 16', (WidgetTester tester) { + return test(tester, false, 9.0 / 16.0); + }); + testWidgets('works at 8 / 16', (WidgetTester tester) { + return test(tester, false, 8.0 / 16.0); + }); + testWidgets('works at isScrollControlled', (WidgetTester tester) { + return test(tester, true, 8.0 / 16.0); + }); + }); }); group('showModalBottomSheet modalBarrierDismissLabel', () {