Expose AnimationController property to showBottomSheet/showModalBottomSheet (#72541)
* Expose AnimationController property to showBottomSheet/showModalBottomSheet (#72541)
ong <shihaohong@google.com>
Co-authored-by: Kate Lovett <katelovett@google.com>
Co-authored-by: Shi-Hao Hong <shihaohong@google.com>
diff --git a/packages/flutter/lib/src/material/bottom_sheet.dart b/packages/flutter/lib/src/material/bottom_sheet.dart
index 9211c13..6703b83 100644
--- a/packages/flutter/lib/src/material/bottom_sheet.dart
+++ b/packages/flutter/lib/src/material/bottom_sheet.dart
@@ -431,6 +431,7 @@
this.enableDrag = true,
required this.isScrollControlled,
RouteSettings? settings,
+ this.transitionAnimationController,
}) : assert(isScrollControlled != null),
assert(isDismissible != null),
assert(enableDrag != null),
@@ -446,6 +447,7 @@
final Color? modalBarrierColor;
final bool isDismissible;
final bool enableDrag;
+ final AnimationController? transitionAnimationController;
@override
Duration get transitionDuration => _bottomSheetEnterDuration;
@@ -467,7 +469,7 @@
@override
AnimationController createAnimationController() {
assert(_animationController == null);
- _animationController = BottomSheet.createAnimationController(navigator!.overlay!);
+ _animationController = transitionAnimationController ?? BottomSheet.createAnimationController(navigator!.overlay!);
return _animationController!;
}
@@ -588,10 +590,13 @@
/// The [enableDrag] parameter specifies whether the bottom sheet can be
/// dragged up and down and dismissed by swiping downwards.
///
-/// The optional [backgroundColor], [elevation], [shape], and [clipBehavior]
+/// The optional [backgroundColor], [elevation], [shape], [clipBehavior] and [transitionAnimationController]
/// parameters can be passed in to customize the appearance and behavior of
/// modal bottom sheets.
///
+/// The [transitionAnimationController] controls the bottom sheet's entrance and
+/// exit animations if provided.
+///
/// The optional `routeSettings` parameter sets the [RouteSettings] of the modal bottom sheet
/// sheet. This is particularly useful in the case that a user wants to observe
/// [PopupRoute]s within a [NavigatorObserver].
@@ -662,6 +667,7 @@
bool isDismissible = true,
bool enableDrag = true,
RouteSettings? routeSettings,
+ AnimationController? transitionAnimationController,
}) {
assert(context != null);
assert(builder != null);
@@ -686,6 +692,7 @@
modalBarrierColor: barrierColor,
enableDrag: enableDrag,
settings: routeSettings,
+ transitionAnimationController: transitionAnimationController,
));
}
@@ -695,7 +702,7 @@
/// Returns a controller that can be used to close and otherwise manipulate the
/// bottom sheet.
///
-/// The optional [backgroundColor], [elevation], [shape], and [clipBehavior]
+/// The optional [backgroundColor], [elevation], [shape], [clipBehavior] and [transitionAnimationController]
/// parameters can be passed in to customize the appearance and behavior of
/// persistent bottom sheets.
///
@@ -735,6 +742,7 @@
double? elevation,
ShapeBorder? shape,
Clip? clipBehavior,
+ AnimationController? transitionAnimationController,
}) {
assert(context != null);
assert(builder != null);
@@ -746,5 +754,6 @@
elevation: elevation,
shape: shape,
clipBehavior: clipBehavior,
+ transitionAnimationController: transitionAnimationController,
);
}
diff --git a/packages/flutter/lib/src/material/scaffold.dart b/packages/flutter/lib/src/material/scaffold.dart
index a2a8902..5a731a8 100644
--- a/packages/flutter/lib/src/material/scaffold.dart
+++ b/packages/flutter/lib/src/material/scaffold.dart
@@ -2634,6 +2634,7 @@
double? elevation,
ShapeBorder? shape,
Clip? clipBehavior,
+ AnimationController? transitionAnimationController,
}) {
assert(() {
if (widget.bottomSheet != null) {
@@ -2648,7 +2649,7 @@
assert(debugCheckHasMediaQuery(context));
_closeCurrentBottomSheet();
- final AnimationController controller = BottomSheet.createAnimationController(this)..forward();
+ final AnimationController controller = (transitionAnimationController ?? BottomSheet.createAnimationController(this))..forward();
setState(() {
_currentBottomSheet = _buildBottomSheet<T>(
builder,
diff --git a/packages/flutter/test/material/bottom_sheet_test.dart b/packages/flutter/test/material/bottom_sheet_test.dart
index 78b8643..819feb1 100644
--- a/packages/flutter/test/material/bottom_sheet_test.dart
+++ b/packages/flutter/test/material/bottom_sheet_test.dart
@@ -727,6 +727,127 @@
expect(retrievedRouteSettings, routeSettings);
});
+ testWidgets('Verify showModalBottomSheet use AnimationController if provided.', (WidgetTester tester) async {
+ const Key tapTarget = Key('tap-target');
+ await tester.pumpWidget(MaterialApp(
+ home: Scaffold(
+ body: Builder(
+ builder: (BuildContext context) {
+ return GestureDetector(
+ onTap: () {
+ showModalBottomSheet<void>(
+ context: context,
+ // The default duration and reverseDuration is 1 second
+ transitionAnimationController: AnimationController(
+ vsync: const TestVSync(),
+ duration: const Duration(seconds: 2),
+ reverseDuration: const Duration(seconds: 2),
+ ),
+ builder: (BuildContext context) {
+ return Container(
+ child: const Text('BottomSheet'),
+ );
+ },
+ );
+ },
+ behavior: HitTestBehavior.opaque,
+ child: Container(
+ height: 100.0,
+ width: 100.0,
+ key: tapTarget,
+ ),
+ );
+ },
+ ),
+ ),
+ ));
+
+ expect(find.text('BottomSheet'), findsNothing);
+
+ await tester.tap(find.byKey(tapTarget)); // Opening animation will start after tapping
+ await tester.pump();
+
+ expect(find.text('BottomSheet'), findsOneWidget);
+ await tester.pump(const Duration(milliseconds: 2000));
+ expect(find.text('BottomSheet'), findsOneWidget);
+
+ // Tapping above the bottom sheet to dismiss it.
+ await tester.tapAt(const Offset(20.0, 20.0)); // Closing animation will start after tapping
+ await tester.pump();
+
+ expect(find.text('BottomSheet'), findsOneWidget);
+ await tester.pump(const Duration(milliseconds: 2000));
+ // The bottom sheet should still be present at the very end of the animation.
+ expect(find.text('BottomSheet'), findsOneWidget);
+
+ await tester.pump(const Duration(milliseconds: 1));
+ // The bottom sheet should not be showing any longer.
+ expect(find.text('BottomSheet'), findsNothing);
+ });
+
+ testWidgets('Verify persistence BottomSheet use AnimationController if provided.', (WidgetTester tester) async {
+ const Key tapTarget = Key('tap-target');
+ const Key tapTargetToClose = Key('tap-target-to-close');
+ await tester.pumpWidget(MaterialApp(
+ home: Scaffold(
+ body: Builder(
+ builder: (BuildContext context) {
+ return GestureDetector(
+ onTap: () {
+ showBottomSheet<void>(
+ context: context,
+ // The default duration and reverseDuration is 1 second
+ transitionAnimationController: AnimationController(
+ vsync: const TestVSync(),
+ duration: const Duration(seconds: 2),
+ reverseDuration: const Duration(seconds: 2),
+ ),
+ builder: (BuildContext context) {
+ return Container(
+ child: MaterialButton(
+ child: const Text('BottomSheet'),
+ onPressed: () => Navigator.pop(context),
+ key: tapTargetToClose,
+ ),
+ );
+ },
+ );
+ },
+ behavior: HitTestBehavior.opaque,
+ child: Container(
+ height: 100.0,
+ width: 100.0,
+ key: tapTarget,
+ ),
+ );
+ },
+ ),
+ ),
+ ));
+
+ expect(find.text('BottomSheet'), findsNothing);
+
+ await tester.tap(find.byKey(tapTarget)); // Opening animation will start after tapping
+ await tester.pump();
+
+ expect(find.text('BottomSheet'), findsOneWidget);
+ await tester.pump(const Duration(milliseconds: 2000));
+ expect(find.text('BottomSheet'), findsOneWidget);
+
+ // Tapping button on the bottom sheet to dismiss it.
+ await tester.tap(find.byKey(tapTargetToClose)); // Closing animation will start after tapping
+ await tester.pump();
+
+ expect(find.text('BottomSheet'), findsOneWidget);
+ await tester.pump(const Duration(milliseconds: 2000));
+ // The bottom sheet should still be present at the very end of the animation.
+ expect(find.text('BottomSheet'), findsOneWidget);
+
+ await tester.pump(const Duration(milliseconds: 1));
+ // The bottom sheet should not be showing any longer.
+ expect(find.text('BottomSheet'), findsNothing);
+ });
+
testWidgets('showModalBottomSheet should move along on-screen keyboard',
(WidgetTester tester) async {
late BuildContext savedContext;