| // Copyright 2014 The Flutter Authors. All rights reserved. |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| import 'dart:ui' show lerpDouble; |
| |
| import 'package:flutter/foundation.dart'; |
| import 'package:flutter/widgets.dart'; |
| |
| import 'bottom_sheet_theme.dart'; |
| import 'colors.dart'; |
| import 'curves.dart'; |
| import 'debug.dart'; |
| import 'material.dart'; |
| import 'material_localizations.dart'; |
| import 'scaffold.dart'; |
| import 'theme.dart'; |
| |
| const Duration _bottomSheetEnterDuration = Duration(milliseconds: 250); |
| const Duration _bottomSheetExitDuration = Duration(milliseconds: 200); |
| const Curve _modalBottomSheetCurve = decelerateEasing; |
| const double _minFlingVelocity = 700.0; |
| const double _closeProgressThreshold = 0.5; |
| |
| /// A callback for when the user begins dragging the bottom sheet. |
| /// |
| /// Used by [BottomSheet.onDragStart]. |
| typedef BottomSheetDragStartHandler = void Function(DragStartDetails details); |
| |
| /// A callback for when the user stops dragging the bottom sheet. |
| /// |
| /// Used by [BottomSheet.onDragEnd]. |
| typedef BottomSheetDragEndHandler = void Function( |
| DragEndDetails details, { |
| required bool isClosing, |
| }); |
| |
| /// A material design bottom sheet. |
| /// |
| /// There are two kinds of bottom sheets in material design: |
| /// |
| /// * _Persistent_. A persistent bottom sheet shows information that |
| /// supplements the primary content of the app. A persistent bottom sheet |
| /// remains visible even when the user interacts with other parts of the app. |
| /// Persistent bottom sheets can be created and displayed with the |
| /// [ScaffoldState.showBottomSheet] function or by specifying the |
| /// [Scaffold.bottomSheet] constructor parameter. |
| /// |
| /// * _Modal_. A modal bottom sheet is an alternative to a menu or a dialog and |
| /// prevents the user from interacting with the rest of the app. Modal bottom |
| /// sheets can be created and displayed with the [showModalBottomSheet] |
| /// function. |
| /// |
| /// The [BottomSheet] widget itself is rarely used directly. Instead, prefer to |
| /// create a persistent bottom sheet with [ScaffoldState.showBottomSheet] or |
| /// [Scaffold.bottomSheet], and a modal bottom sheet with [showModalBottomSheet]. |
| /// |
| /// See also: |
| /// |
| /// * [showBottomSheet] and [ScaffoldState.showBottomSheet], for showing |
| /// non-modal "persistent" bottom sheets. |
| /// * [showModalBottomSheet], which can be used to display a modal bottom |
| /// sheet. |
| /// * <https://material.io/design/components/sheets-bottom.html> |
| class BottomSheet extends StatefulWidget { |
| /// Creates a bottom sheet. |
| /// |
| /// Typically, bottom sheets are created implicitly by |
| /// [ScaffoldState.showBottomSheet], for persistent bottom sheets, or by |
| /// [showModalBottomSheet], for modal bottom sheets. |
| const BottomSheet({ |
| Key? key, |
| this.animationController, |
| this.enableDrag = true, |
| this.onDragStart, |
| this.onDragEnd, |
| this.backgroundColor, |
| this.elevation, |
| this.shape, |
| this.clipBehavior, |
| this.constraints, |
| required this.onClosing, |
| required this.builder, |
| }) : assert(enableDrag != null), |
| assert(onClosing != null), |
| assert(builder != null), |
| assert(elevation == null || elevation >= 0.0), |
| super(key: key); |
| |
| /// The animation controller that controls the bottom sheet's entrance and |
| /// exit animations. |
| /// |
| /// The BottomSheet widget will manipulate the position of this animation, it |
| /// is not just a passive observer. |
| final AnimationController? animationController; |
| |
| /// Called when the bottom sheet begins to close. |
| /// |
| /// A bottom sheet might be prevented from closing (e.g., by user |
| /// interaction) even after this callback is called. For this reason, this |
| /// callback might be call multiple times for a given bottom sheet. |
| final VoidCallback onClosing; |
| |
| /// A builder for the contents of the sheet. |
| /// |
| /// The bottom sheet will wrap the widget produced by this builder in a |
| /// [Material] widget. |
| final WidgetBuilder builder; |
| |
| /// If true, the bottom sheet can be dragged up and down and dismissed by |
| /// swiping downwards. |
| /// |
| /// Default is true. |
| final bool enableDrag; |
| |
| /// Called when the user begins dragging the bottom sheet vertically, if |
| /// [enableDrag] is true. |
| /// |
| /// Would typically be used to change the bottom sheet animation curve so |
| /// that it tracks the user's finger accurately. |
| final BottomSheetDragStartHandler? onDragStart; |
| |
| /// Called when the user stops dragging the bottom sheet, if [enableDrag] |
| /// is true. |
| /// |
| /// Would typically be used to reset the bottom sheet animation curve, so |
| /// that it animates non-linearly. Called before [onClosing] if the bottom |
| /// sheet is closing. |
| final BottomSheetDragEndHandler? onDragEnd; |
| |
| /// The bottom sheet's background color. |
| /// |
| /// Defines the bottom sheet's [Material.color]. |
| /// |
| /// Defaults to null and falls back to [Material]'s default. |
| final Color? backgroundColor; |
| |
| /// The z-coordinate at which to place this material relative to its parent. |
| /// |
| /// This controls the size of the shadow below the material. |
| /// |
| /// Defaults to 0. The value is non-negative. |
| final double? elevation; |
| |
| /// The shape of the bottom sheet. |
| /// |
| /// Defines the bottom sheet's [Material.shape]. |
| /// |
| /// Defaults to null and falls back to [Material]'s default. |
| final ShapeBorder? shape; |
| |
| /// {@macro flutter.material.Material.clipBehavior} |
| /// |
| /// Defines the bottom sheet's [Material.clipBehavior]. |
| /// |
| /// Use this property to enable clipping of content when the bottom sheet has |
| /// a custom [shape] and the content can extend past this shape. For example, |
| /// a bottom sheet with rounded corners and an edge-to-edge [Image] at the |
| /// top. |
| /// |
| /// If this property is null then [BottomSheetThemeData.clipBehavior] of |
| /// [ThemeData.bottomSheetTheme] is used. If that's null then the behavior |
| /// will be [Clip.none]. |
| final Clip? clipBehavior; |
| |
| /// Defines minimum and maximum sizes for a [BottomSheet]. |
| /// |
| /// Typically a bottom sheet will cover the entire width of its |
| /// parent. However for large screens you may want to limit the width |
| /// to something smaller and this property provides a way to specify |
| /// a maximum width. |
| /// |
| /// If null, then the ambient [ThemeData.bottomSheetTheme]'s |
| /// [BottomSheetThemeData.constraints] will be used. If that |
| /// is null then the bottom sheet's size will be constrained |
| /// by its parent (usually a [Scaffold]). |
| /// |
| /// If constraints are specified (either in this property or in the |
| /// theme), the bottom sheet will be aligned to the bottom-center of |
| /// the available space. Otherwise, no alignment is applied. |
| final BoxConstraints? constraints; |
| |
| @override |
| State<BottomSheet> createState() => _BottomSheetState(); |
| |
| /// Creates an [AnimationController] suitable for a |
| /// [BottomSheet.animationController]. |
| /// |
| /// This API available as a convenience for a Material compliant bottom sheet |
| /// animation. If alternative animation durations are required, a different |
| /// animation controller could be provided. |
| static AnimationController createAnimationController(TickerProvider vsync) { |
| return AnimationController( |
| duration: _bottomSheetEnterDuration, |
| reverseDuration: _bottomSheetExitDuration, |
| debugLabel: 'BottomSheet', |
| vsync: vsync, |
| ); |
| } |
| } |
| |
| class _BottomSheetState extends State<BottomSheet> { |
| |
| final GlobalKey _childKey = GlobalKey(debugLabel: 'BottomSheet child'); |
| |
| double get _childHeight { |
| final RenderBox renderBox = _childKey.currentContext!.findRenderObject()! as RenderBox; |
| return renderBox.size.height; |
| } |
| |
| bool get _dismissUnderway => widget.animationController!.status == AnimationStatus.reverse; |
| |
| void _handleDragStart(DragStartDetails details) { |
| widget.onDragStart?.call(details); |
| } |
| |
| void _handleDragUpdate(DragUpdateDetails details) { |
| assert( |
| widget.enableDrag && widget.animationController != null, |
| "'BottomSheet.animationController' can not be null when 'BottomSheet.enableDrag' is true. " |
| "Use 'BottomSheet.createAnimationController' to create one, or provide another AnimationController.", |
| ); |
| if (_dismissUnderway) |
| return; |
| widget.animationController!.value -= details.primaryDelta! / _childHeight; |
| } |
| |
| void _handleDragEnd(DragEndDetails details) { |
| assert( |
| widget.enableDrag && widget.animationController != null, |
| "'BottomSheet.animationController' can not be null when 'BottomSheet.enableDrag' is true. " |
| "Use 'BottomSheet.createAnimationController' to create one, or provide another AnimationController.", |
| ); |
| if (_dismissUnderway) |
| return; |
| bool isClosing = false; |
| if (details.velocity.pixelsPerSecond.dy > _minFlingVelocity) { |
| final double flingVelocity = -details.velocity.pixelsPerSecond.dy / _childHeight; |
| if (widget.animationController!.value > 0.0) { |
| widget.animationController!.fling(velocity: flingVelocity); |
| } |
| if (flingVelocity < 0.0) { |
| isClosing = true; |
| } |
| } else if (widget.animationController!.value < _closeProgressThreshold) { |
| if (widget.animationController!.value > 0.0) |
| widget.animationController!.fling(velocity: -1.0); |
| isClosing = true; |
| } else { |
| widget.animationController!.forward(); |
| } |
| |
| widget.onDragEnd?.call( |
| details, |
| isClosing: isClosing, |
| ); |
| |
| if (isClosing) { |
| widget.onClosing(); |
| } |
| } |
| |
| bool extentChanged(DraggableScrollableNotification notification) { |
| if (notification.extent == notification.minExtent) { |
| widget.onClosing(); |
| } |
| return false; |
| } |
| |
| @override |
| Widget build(BuildContext context) { |
| final BottomSheetThemeData bottomSheetTheme = Theme.of(context).bottomSheetTheme; |
| final BoxConstraints? constraints = widget.constraints ?? bottomSheetTheme.constraints; |
| final Color? color = widget.backgroundColor ?? bottomSheetTheme.backgroundColor; |
| final double elevation = widget.elevation ?? bottomSheetTheme.elevation ?? 0; |
| final ShapeBorder? shape = widget.shape ?? bottomSheetTheme.shape; |
| final Clip clipBehavior = widget.clipBehavior ?? bottomSheetTheme.clipBehavior ?? Clip.none; |
| |
| Widget bottomSheet = Material( |
| key: _childKey, |
| color: color, |
| elevation: elevation, |
| shape: shape, |
| clipBehavior: clipBehavior, |
| child: NotificationListener<DraggableScrollableNotification>( |
| onNotification: extentChanged, |
| child: widget.builder(context), |
| ), |
| ); |
| |
| if (constraints != null) { |
| bottomSheet = Align( |
| alignment: Alignment.bottomCenter, |
| heightFactor: 1.0, |
| child: ConstrainedBox( |
| constraints: constraints, |
| child: bottomSheet, |
| ), |
| ); |
| } |
| |
| return !widget.enableDrag ? bottomSheet : GestureDetector( |
| onVerticalDragStart: _handleDragStart, |
| onVerticalDragUpdate: _handleDragUpdate, |
| onVerticalDragEnd: _handleDragEnd, |
| excludeFromSemantics: true, |
| child: bottomSheet, |
| ); |
| } |
| } |
| |
| // PERSISTENT BOTTOM SHEETS |
| |
| // See scaffold.dart |
| |
| |
| // MODAL BOTTOM SHEETS |
| class _ModalBottomSheetLayout extends SingleChildLayoutDelegate { |
| _ModalBottomSheetLayout(this.progress, this.isScrollControlled); |
| |
| final double progress; |
| final bool isScrollControlled; |
| |
| @override |
| BoxConstraints getConstraintsForChild(BoxConstraints constraints) { |
| return BoxConstraints( |
| minWidth: constraints.maxWidth, |
| maxWidth: constraints.maxWidth, |
| maxHeight: isScrollControlled |
| ? constraints.maxHeight |
| : constraints.maxHeight * 9.0 / 16.0, |
| ); |
| } |
| |
| @override |
| Offset getPositionForChild(Size size, Size childSize) { |
| return Offset(0.0, size.height - childSize.height * progress); |
| } |
| |
| @override |
| bool shouldRelayout(_ModalBottomSheetLayout oldDelegate) { |
| return progress != oldDelegate.progress; |
| } |
| } |
| |
| class _ModalBottomSheet<T> extends StatefulWidget { |
| const _ModalBottomSheet({ |
| Key? key, |
| this.route, |
| this.backgroundColor, |
| this.elevation, |
| this.shape, |
| this.clipBehavior, |
| this.constraints, |
| this.isScrollControlled = false, |
| this.enableDrag = true, |
| }) : assert(isScrollControlled != null), |
| assert(enableDrag != null), |
| super(key: key); |
| |
| final _ModalBottomSheetRoute<T>? route; |
| final bool isScrollControlled; |
| final Color? backgroundColor; |
| final double? elevation; |
| final ShapeBorder? shape; |
| final Clip? clipBehavior; |
| final BoxConstraints? constraints; |
| final bool enableDrag; |
| |
| @override |
| _ModalBottomSheetState<T> createState() => _ModalBottomSheetState<T>(); |
| } |
| |
| class _ModalBottomSheetState<T> extends State<_ModalBottomSheet<T>> { |
| ParametricCurve<double> animationCurve = _modalBottomSheetCurve; |
| |
| String _getRouteLabel(MaterialLocalizations localizations) { |
| switch (Theme.of(context).platform) { |
| case TargetPlatform.iOS: |
| case TargetPlatform.macOS: |
| return ''; |
| case TargetPlatform.android: |
| case TargetPlatform.fuchsia: |
| case TargetPlatform.linux: |
| case TargetPlatform.windows: |
| return localizations.dialogLabel; |
| } |
| } |
| |
| void handleDragStart(DragStartDetails details) { |
| // Allow the bottom sheet to track the user's finger accurately. |
| animationCurve = Curves.linear; |
| } |
| |
| void handleDragEnd(DragEndDetails details, {bool? isClosing}) { |
| // Allow the bottom sheet to animate smoothly from its current position. |
| animationCurve = _BottomSheetSuspendedCurve( |
| widget.route!.animation!.value, |
| curve: _modalBottomSheetCurve, |
| ); |
| } |
| |
| @override |
| Widget build(BuildContext context) { |
| assert(debugCheckHasMediaQuery(context)); |
| assert(debugCheckHasMaterialLocalizations(context)); |
| final MediaQueryData mediaQuery = MediaQuery.of(context); |
| final MaterialLocalizations localizations = MaterialLocalizations.of(context); |
| final String routeLabel = _getRouteLabel(localizations); |
| |
| return AnimatedBuilder( |
| animation: widget.route!.animation!, |
| child: BottomSheet( |
| animationController: widget.route!._animationController, |
| onClosing: () { |
| if (widget.route!.isCurrent) { |
| Navigator.pop(context); |
| } |
| }, |
| builder: widget.route!.builder!, |
| backgroundColor: widget.backgroundColor, |
| elevation: widget.elevation, |
| shape: widget.shape, |
| clipBehavior: widget.clipBehavior, |
| constraints: widget.constraints, |
| enableDrag: widget.enableDrag, |
| onDragStart: handleDragStart, |
| onDragEnd: handleDragEnd, |
| ), |
| builder: (BuildContext context, Widget? child) { |
| // Disable the initial animation when accessible navigation is on so |
| // that the semantics are added to the tree at the correct time. |
| final double animationValue = animationCurve.transform( |
| mediaQuery.accessibleNavigation ? 1.0 : widget.route!.animation!.value, |
| ); |
| return Semantics( |
| scopesRoute: true, |
| namesRoute: true, |
| label: routeLabel, |
| explicitChildNodes: true, |
| child: ClipRect( |
| child: CustomSingleChildLayout( |
| delegate: _ModalBottomSheetLayout(animationValue, widget.isScrollControlled), |
| child: child, |
| ), |
| ), |
| ); |
| }, |
| ); |
| } |
| } |
| |
| class _ModalBottomSheetRoute<T> extends PopupRoute<T> { |
| _ModalBottomSheetRoute({ |
| this.builder, |
| required this.capturedThemes, |
| this.barrierLabel, |
| this.backgroundColor, |
| this.elevation, |
| this.shape, |
| this.clipBehavior, |
| this.constraints, |
| this.modalBarrierColor, |
| this.isDismissible = true, |
| this.enableDrag = true, |
| required this.isScrollControlled, |
| RouteSettings? settings, |
| this.transitionAnimationController, |
| }) : assert(isScrollControlled != null), |
| assert(isDismissible != null), |
| assert(enableDrag != null), |
| super(settings: settings); |
| |
| final WidgetBuilder? builder; |
| final CapturedThemes capturedThemes; |
| final bool isScrollControlled; |
| final Color? backgroundColor; |
| final double? elevation; |
| final ShapeBorder? shape; |
| final Clip? clipBehavior; |
| final BoxConstraints? constraints; |
| final Color? modalBarrierColor; |
| final bool isDismissible; |
| final bool enableDrag; |
| final AnimationController? transitionAnimationController; |
| |
| @override |
| Duration get transitionDuration => _bottomSheetEnterDuration; |
| |
| @override |
| Duration get reverseTransitionDuration => _bottomSheetExitDuration; |
| |
| @override |
| bool get barrierDismissible => isDismissible; |
| |
| @override |
| final String? barrierLabel; |
| |
| @override |
| Color get barrierColor => modalBarrierColor ?? Colors.black54; |
| |
| AnimationController? _animationController; |
| |
| @override |
| AnimationController createAnimationController() { |
| assert(_animationController == null); |
| if (transitionAnimationController != null) { |
| _animationController = transitionAnimationController; |
| willDisposeAnimationController = false; |
| } else { |
| _animationController = BottomSheet.createAnimationController(navigator!.overlay!); |
| } |
| return _animationController!; |
| } |
| |
| @override |
| Widget buildPage(BuildContext context, Animation<double> animation, Animation<double> secondaryAnimation) { |
| // By definition, the bottom sheet is aligned to the bottom of the page |
| // and isn't exposed to the top padding of the MediaQuery. |
| final Widget bottomSheet = MediaQuery.removePadding( |
| context: context, |
| removeTop: true, |
| child: Builder( |
| builder: (BuildContext context) { |
| final BottomSheetThemeData sheetTheme = Theme.of(context).bottomSheetTheme; |
| return _ModalBottomSheet<T>( |
| route: this, |
| backgroundColor: backgroundColor ?? sheetTheme.modalBackgroundColor ?? sheetTheme.backgroundColor, |
| elevation: elevation ?? sheetTheme.modalElevation ?? sheetTheme.elevation, |
| shape: shape, |
| clipBehavior: clipBehavior, |
| constraints: constraints, |
| isScrollControlled: isScrollControlled, |
| enableDrag: enableDrag, |
| ); |
| }, |
| ), |
| ); |
| return capturedThemes.wrap(bottomSheet); |
| } |
| } |
| |
| // TODO(guidezpl): Look into making this public. A copy of this class is in |
| // scaffold.dart, for now, https://github.com/flutter/flutter/issues/51627 |
| /// A curve that progresses linearly until a specified [startingPoint], at which |
| /// point [curve] will begin. Unlike [Interval], [curve] will not start at zero, |
| /// but will use [startingPoint] as the Y position. |
| /// |
| /// For example, if [startingPoint] is set to `0.5`, and [curve] is set to |
| /// [Curves.easeOut], then the bottom-left quarter of the curve will be a |
| /// straight line, and the top-right quarter will contain the entire contents of |
| /// [Curves.easeOut]. |
| /// |
| /// This is useful in situations where a widget must track the user's finger |
| /// (which requires a linear animation), and afterwards can be flung using a |
| /// curve specified with the [curve] argument, after the finger is released. In |
| /// such a case, the value of [startingPoint] would be the progress of the |
| /// animation at the time when the finger was released. |
| /// |
| /// The [startingPoint] and [curve] arguments must not be null. |
| class _BottomSheetSuspendedCurve extends ParametricCurve<double> { |
| /// Creates a suspended curve. |
| const _BottomSheetSuspendedCurve( |
| this.startingPoint, { |
| this.curve = Curves.easeOutCubic, |
| }) : assert(startingPoint != null), |
| assert(curve != null); |
| |
| /// The progress value at which [curve] should begin. |
| /// |
| /// This defaults to [Curves.easeOutCubic]. |
| final double startingPoint; |
| |
| /// The curve to use when [startingPoint] is reached. |
| final Curve curve; |
| |
| @override |
| double transform(double t) { |
| assert(t >= 0.0 && t <= 1.0); |
| assert(startingPoint >= 0.0 && startingPoint <= 1.0); |
| |
| if (t < startingPoint) { |
| return t; |
| } |
| |
| if (t == 1.0) { |
| return t; |
| } |
| |
| final double curveProgress = (t - startingPoint) / (1 - startingPoint); |
| final double transformed = curve.transform(curveProgress); |
| return lerpDouble(startingPoint, 1, transformed)!; |
| } |
| |
| @override |
| String toString() { |
| return '${describeIdentity(this)}($startingPoint, $curve)'; |
| } |
| } |
| |
| /// Shows a modal material design bottom sheet. |
| /// |
| /// A modal bottom sheet is an alternative to a menu or a dialog and prevents |
| /// the user from interacting with the rest of the app. |
| /// |
| /// A closely related widget is a persistent bottom sheet, which shows |
| /// information that supplements the primary content of the app without |
| /// preventing the user from interacting with the app. Persistent bottom sheets |
| /// can be created and displayed with the [showBottomSheet] function or the |
| /// [ScaffoldState.showBottomSheet] method. |
| /// |
| /// The `context` argument is used to look up the [Navigator] and [Theme] for |
| /// the bottom sheet. It is only used when the method is called. Its |
| /// corresponding widget can be safely removed from the tree before the bottom |
| /// sheet is closed. |
| /// |
| /// The `isScrollControlled` parameter specifies whether this is a route for |
| /// a bottom sheet that will utilize [DraggableScrollableSheet]. If you wish |
| /// to have a bottom sheet that has a scrollable child such as a [ListView] or |
| /// a [GridView] and have the bottom sheet be draggable, you should set this |
| /// parameter to true. |
| /// |
| /// The `useRootNavigator` parameter ensures that the root navigator is used to |
| /// display the [BottomSheet] when set to `true`. This is useful in the case |
| /// that a modal [BottomSheet] needs to be displayed above all other content |
| /// but the caller is inside another [Navigator]. |
| /// |
| /// The [isDismissible] parameter specifies whether the bottom sheet will be |
| /// dismissed when user taps on the scrim. |
| /// |
| /// The [enableDrag] parameter specifies whether the bottom sheet can be |
| /// dragged up and down and dismissed by swiping downwards. |
| /// |
| /// The optional [backgroundColor], [elevation], [shape], [clipBehavior], |
| /// [constraints] and [transitionAnimationController] |
| /// parameters can be passed in to customize the appearance and behavior of |
| /// modal bottom sheets (see the documentation for these on [BottomSheet] |
| /// for more details). |
| /// |
| /// The [transitionAnimationController] controls the bottom sheet's entrance and |
| /// exit animations. It's up to the owner of the controller to call |
| /// [AnimationController.dispose] when the controller is no longer needed. |
| /// |
| /// 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]. |
| /// |
| /// Returns a `Future` that resolves to the value (if any) that was passed to |
| /// [Navigator.pop] when the modal bottom sheet was closed. |
| /// |
| /// {@tool dartpad} |
| /// This example demonstrates how to use `showModalBottomSheet` to display a |
| /// bottom sheet that obscures the content behind it when a user taps a button. |
| /// It also demonstrates how to close the bottom sheet using the [Navigator] |
| /// when a user taps on a button inside the bottom sheet. |
| /// |
| /// ** See code in examples/api/lib/material/bottom_sheet/show_modal_bottom_sheet.0.dart ** |
| /// {@end-tool} |
| /// |
| /// See also: |
| /// |
| /// * [BottomSheet], which becomes the parent of the widget returned by the |
| /// function passed as the `builder` argument to [showModalBottomSheet]. |
| /// * [showBottomSheet] and [ScaffoldState.showBottomSheet], for showing |
| /// non-modal bottom sheets. |
| /// * [DraggableScrollableSheet], which allows you to create a bottom sheet |
| /// that grows and then becomes scrollable once it reaches its maximum size. |
| /// * <https://material.io/design/components/sheets-bottom.html#modal-bottom-sheet> |
| Future<T?> showModalBottomSheet<T>({ |
| required BuildContext context, |
| required WidgetBuilder builder, |
| Color? backgroundColor, |
| double? elevation, |
| ShapeBorder? shape, |
| Clip? clipBehavior, |
| BoxConstraints? constraints, |
| Color? barrierColor, |
| bool isScrollControlled = false, |
| bool useRootNavigator = false, |
| bool isDismissible = true, |
| bool enableDrag = true, |
| RouteSettings? routeSettings, |
| AnimationController? transitionAnimationController, |
| }) { |
| assert(context != null); |
| assert(builder != null); |
| assert(isScrollControlled != null); |
| assert(useRootNavigator != null); |
| assert(isDismissible != null); |
| assert(enableDrag != null); |
| assert(debugCheckHasMediaQuery(context)); |
| assert(debugCheckHasMaterialLocalizations(context)); |
| |
| final NavigatorState navigator = Navigator.of(context, rootNavigator: useRootNavigator); |
| return navigator.push(_ModalBottomSheetRoute<T>( |
| builder: builder, |
| capturedThemes: InheritedTheme.capture(from: context, to: navigator.context), |
| isScrollControlled: isScrollControlled, |
| barrierLabel: MaterialLocalizations.of(context).modalBarrierDismissLabel, |
| backgroundColor: backgroundColor, |
| elevation: elevation, |
| shape: shape, |
| clipBehavior: clipBehavior, |
| constraints: constraints, |
| isDismissible: isDismissible, |
| modalBarrierColor: barrierColor, |
| enableDrag: enableDrag, |
| settings: routeSettings, |
| transitionAnimationController: transitionAnimationController, |
| )); |
| } |
| |
| /// Shows a material design bottom sheet in the nearest [Scaffold] ancestor. If |
| /// you wish to show a persistent bottom sheet, use [Scaffold.bottomSheet]. |
| /// |
| /// Returns a controller that can be used to close and otherwise manipulate the |
| /// bottom sheet. |
| /// |
| /// The optional [backgroundColor], [elevation], [shape], [clipBehavior], |
| /// [constraints] and [transitionAnimationController] |
| /// parameters can be passed in to customize the appearance and behavior of |
| /// persistent bottom sheets (see the documentation for these on [BottomSheet] |
| /// for more details). |
| /// |
| /// To rebuild the bottom sheet (e.g. if it is stateful), call |
| /// [PersistentBottomSheetController.setState] on the controller returned by |
| /// this method. |
| /// |
| /// The new bottom sheet becomes a [LocalHistoryEntry] for the enclosing |
| /// [ModalRoute] and a back button is added to the app bar of the [Scaffold] |
| /// that closes the bottom sheet. |
| /// |
| /// To create a persistent bottom sheet that is not a [LocalHistoryEntry] and |
| /// does not add a back button to the enclosing Scaffold's app bar, use the |
| /// [Scaffold.bottomSheet] constructor parameter. |
| /// |
| /// A closely related widget is a modal bottom sheet, which is an alternative |
| /// to a menu or a dialog and prevents the user from interacting with the rest |
| /// of the app. Modal bottom sheets can be created and displayed with the |
| /// [showModalBottomSheet] function. |
| /// |
| /// The `context` argument is used to look up the [Scaffold] for the bottom |
| /// sheet. It is only used when the method is called. Its corresponding widget |
| /// can be safely removed from the tree before the bottom sheet is closed. |
| /// |
| /// See also: |
| /// |
| /// * [BottomSheet], which becomes the parent of the widget returned by the |
| /// `builder`. |
| /// * [showModalBottomSheet], which can be used to display a modal bottom |
| /// sheet. |
| /// * [Scaffold.of], for information about how to obtain the [BuildContext]. |
| /// * <https://material.io/design/components/sheets-bottom.html#standard-bottom-sheet> |
| PersistentBottomSheetController<T> showBottomSheet<T>({ |
| required BuildContext context, |
| required WidgetBuilder builder, |
| Color? backgroundColor, |
| double? elevation, |
| ShapeBorder? shape, |
| Clip? clipBehavior, |
| BoxConstraints? constraints, |
| AnimationController? transitionAnimationController, |
| }) { |
| assert(context != null); |
| assert(builder != null); |
| assert(debugCheckHasScaffold(context)); |
| |
| return Scaffold.of(context).showBottomSheet<T>( |
| builder, |
| backgroundColor: backgroundColor, |
| elevation: elevation, |
| shape: shape, |
| clipBehavior: clipBehavior, |
| constraints: constraints, |
| transitionAnimationController: transitionAnimationController, |
| ); |
| } |