Skip to content

Commit 46b0797

Browse files
ZainUrRehmanKhanPiinksShi-Hao Hong
authored
Expose AnimationController property to showBottomSheet/showModalBottomSheet (flutter#72541)
* Expose AnimationController property to showBottomSheet/showModalBottomSheet (flutter#72541) ong <[email protected]> Co-authored-by: Kate Lovett <[email protected]> Co-authored-by: Shi-Hao Hong <[email protected]>
1 parent 32db0fe commit 46b0797

File tree

3 files changed

+135
-4
lines changed

3 files changed

+135
-4
lines changed

packages/flutter/lib/src/material/bottom_sheet.dart

Lines changed: 12 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -431,6 +431,7 @@ class _ModalBottomSheetRoute<T> extends PopupRoute<T> {
431431
this.enableDrag = true,
432432
required this.isScrollControlled,
433433
RouteSettings? settings,
434+
this.transitionAnimationController,
434435
}) : assert(isScrollControlled != null),
435436
assert(isDismissible != null),
436437
assert(enableDrag != null),
@@ -446,6 +447,7 @@ class _ModalBottomSheetRoute<T> extends PopupRoute<T> {
446447
final Color? modalBarrierColor;
447448
final bool isDismissible;
448449
final bool enableDrag;
450+
final AnimationController? transitionAnimationController;
449451

450452
@override
451453
Duration get transitionDuration => _bottomSheetEnterDuration;
@@ -467,7 +469,7 @@ class _ModalBottomSheetRoute<T> extends PopupRoute<T> {
467469
@override
468470
AnimationController createAnimationController() {
469471
assert(_animationController == null);
470-
_animationController = BottomSheet.createAnimationController(navigator!.overlay!);
472+
_animationController = transitionAnimationController ?? BottomSheet.createAnimationController(navigator!.overlay!);
471473
return _animationController!;
472474
}
473475

@@ -588,10 +590,13 @@ class _BottomSheetSuspendedCurve extends ParametricCurve<double> {
588590
/// The [enableDrag] parameter specifies whether the bottom sheet can be
589591
/// dragged up and down and dismissed by swiping downwards.
590592
///
591-
/// The optional [backgroundColor], [elevation], [shape], and [clipBehavior]
593+
/// The optional [backgroundColor], [elevation], [shape], [clipBehavior] and [transitionAnimationController]
592594
/// parameters can be passed in to customize the appearance and behavior of
593595
/// modal bottom sheets.
594596
///
597+
/// The [transitionAnimationController] controls the bottom sheet's entrance and
598+
/// exit animations if provided.
599+
///
595600
/// The optional `routeSettings` parameter sets the [RouteSettings] of the modal bottom sheet
596601
/// sheet. This is particularly useful in the case that a user wants to observe
597602
/// [PopupRoute]s within a [NavigatorObserver].
@@ -662,6 +667,7 @@ Future<T?> showModalBottomSheet<T>({
662667
bool isDismissible = true,
663668
bool enableDrag = true,
664669
RouteSettings? routeSettings,
670+
AnimationController? transitionAnimationController,
665671
}) {
666672
assert(context != null);
667673
assert(builder != null);
@@ -686,6 +692,7 @@ Future<T?> showModalBottomSheet<T>({
686692
modalBarrierColor: barrierColor,
687693
enableDrag: enableDrag,
688694
settings: routeSettings,
695+
transitionAnimationController: transitionAnimationController,
689696
));
690697
}
691698

@@ -695,7 +702,7 @@ Future<T?> showModalBottomSheet<T>({
695702
/// Returns a controller that can be used to close and otherwise manipulate the
696703
/// bottom sheet.
697704
///
698-
/// The optional [backgroundColor], [elevation], [shape], and [clipBehavior]
705+
/// The optional [backgroundColor], [elevation], [shape], [clipBehavior] and [transitionAnimationController]
699706
/// parameters can be passed in to customize the appearance and behavior of
700707
/// persistent bottom sheets.
701708
///
@@ -735,6 +742,7 @@ PersistentBottomSheetController<T> showBottomSheet<T>({
735742
double? elevation,
736743
ShapeBorder? shape,
737744
Clip? clipBehavior,
745+
AnimationController? transitionAnimationController,
738746
}) {
739747
assert(context != null);
740748
assert(builder != null);
@@ -746,5 +754,6 @@ PersistentBottomSheetController<T> showBottomSheet<T>({
746754
elevation: elevation,
747755
shape: shape,
748756
clipBehavior: clipBehavior,
757+
transitionAnimationController: transitionAnimationController,
749758
);
750759
}

packages/flutter/lib/src/material/scaffold.dart

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2634,6 +2634,7 @@ class ScaffoldState extends State<Scaffold> with TickerProviderStateMixin, Resto
26342634
double? elevation,
26352635
ShapeBorder? shape,
26362636
Clip? clipBehavior,
2637+
AnimationController? transitionAnimationController,
26372638
}) {
26382639
assert(() {
26392640
if (widget.bottomSheet != null) {
@@ -2648,7 +2649,7 @@ class ScaffoldState extends State<Scaffold> with TickerProviderStateMixin, Resto
26482649
assert(debugCheckHasMediaQuery(context));
26492650

26502651
_closeCurrentBottomSheet();
2651-
final AnimationController controller = BottomSheet.createAnimationController(this)..forward();
2652+
final AnimationController controller = (transitionAnimationController ?? BottomSheet.createAnimationController(this))..forward();
26522653
setState(() {
26532654
_currentBottomSheet = _buildBottomSheet<T>(
26542655
builder,

packages/flutter/test/material/bottom_sheet_test.dart

Lines changed: 121 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -727,6 +727,127 @@ void main() {
727727
expect(retrievedRouteSettings, routeSettings);
728728
});
729729

730+
testWidgets('Verify showModalBottomSheet use AnimationController if provided.', (WidgetTester tester) async {
731+
const Key tapTarget = Key('tap-target');
732+
await tester.pumpWidget(MaterialApp(
733+
home: Scaffold(
734+
body: Builder(
735+
builder: (BuildContext context) {
736+
return GestureDetector(
737+
onTap: () {
738+
showModalBottomSheet<void>(
739+
context: context,
740+
// The default duration and reverseDuration is 1 second
741+
transitionAnimationController: AnimationController(
742+
vsync: const TestVSync(),
743+
duration: const Duration(seconds: 2),
744+
reverseDuration: const Duration(seconds: 2),
745+
),
746+
builder: (BuildContext context) {
747+
return Container(
748+
child: const Text('BottomSheet'),
749+
);
750+
},
751+
);
752+
},
753+
behavior: HitTestBehavior.opaque,
754+
child: Container(
755+
height: 100.0,
756+
width: 100.0,
757+
key: tapTarget,
758+
),
759+
);
760+
},
761+
),
762+
),
763+
));
764+
765+
expect(find.text('BottomSheet'), findsNothing);
766+
767+
await tester.tap(find.byKey(tapTarget)); // Opening animation will start after tapping
768+
await tester.pump();
769+
770+
expect(find.text('BottomSheet'), findsOneWidget);
771+
await tester.pump(const Duration(milliseconds: 2000));
772+
expect(find.text('BottomSheet'), findsOneWidget);
773+
774+
// Tapping above the bottom sheet to dismiss it.
775+
await tester.tapAt(const Offset(20.0, 20.0)); // Closing animation will start after tapping
776+
await tester.pump();
777+
778+
expect(find.text('BottomSheet'), findsOneWidget);
779+
await tester.pump(const Duration(milliseconds: 2000));
780+
// The bottom sheet should still be present at the very end of the animation.
781+
expect(find.text('BottomSheet'), findsOneWidget);
782+
783+
await tester.pump(const Duration(milliseconds: 1));
784+
// The bottom sheet should not be showing any longer.
785+
expect(find.text('BottomSheet'), findsNothing);
786+
});
787+
788+
testWidgets('Verify persistence BottomSheet use AnimationController if provided.', (WidgetTester tester) async {
789+
const Key tapTarget = Key('tap-target');
790+
const Key tapTargetToClose = Key('tap-target-to-close');
791+
await tester.pumpWidget(MaterialApp(
792+
home: Scaffold(
793+
body: Builder(
794+
builder: (BuildContext context) {
795+
return GestureDetector(
796+
onTap: () {
797+
showBottomSheet<void>(
798+
context: context,
799+
// The default duration and reverseDuration is 1 second
800+
transitionAnimationController: AnimationController(
801+
vsync: const TestVSync(),
802+
duration: const Duration(seconds: 2),
803+
reverseDuration: const Duration(seconds: 2),
804+
),
805+
builder: (BuildContext context) {
806+
return Container(
807+
child: MaterialButton(
808+
child: const Text('BottomSheet'),
809+
onPressed: () => Navigator.pop(context),
810+
key: tapTargetToClose,
811+
),
812+
);
813+
},
814+
);
815+
},
816+
behavior: HitTestBehavior.opaque,
817+
child: Container(
818+
height: 100.0,
819+
width: 100.0,
820+
key: tapTarget,
821+
),
822+
);
823+
},
824+
),
825+
),
826+
));
827+
828+
expect(find.text('BottomSheet'), findsNothing);
829+
830+
await tester.tap(find.byKey(tapTarget)); // Opening animation will start after tapping
831+
await tester.pump();
832+
833+
expect(find.text('BottomSheet'), findsOneWidget);
834+
await tester.pump(const Duration(milliseconds: 2000));
835+
expect(find.text('BottomSheet'), findsOneWidget);
836+
837+
// Tapping button on the bottom sheet to dismiss it.
838+
await tester.tap(find.byKey(tapTargetToClose)); // Closing animation will start after tapping
839+
await tester.pump();
840+
841+
expect(find.text('BottomSheet'), findsOneWidget);
842+
await tester.pump(const Duration(milliseconds: 2000));
843+
// The bottom sheet should still be present at the very end of the animation.
844+
expect(find.text('BottomSheet'), findsOneWidget);
845+
846+
await tester.pump(const Duration(milliseconds: 1));
847+
// The bottom sheet should not be showing any longer.
848+
expect(find.text('BottomSheet'), findsNothing);
849+
});
850+
730851
testWidgets('showModalBottomSheet should move along on-screen keyboard',
731852
(WidgetTester tester) async {
732853
late BuildContext savedContext;

0 commit comments

Comments
 (0)