| // 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:async'; |
| import 'dart:ui' show lerpDouble; |
| |
| import 'package:flutter/foundation.dart'; |
| import 'package:flutter/scheduler.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; |
| |
| typedef BottomSheetDragStartHandler = void Function(DragStartDetails details); |
| typedef BottomSheetDragEndHandler = void Function( |
| DragEndDetails details, { |
| 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, |
| @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.widgets.Clip} |
| /// |
| /// 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 [ThemeData.bottomSheetTheme.clipBehavior] is |
| /// used. If that's null then the behavior will be [Clip.none]. |
| final Clip clipBehavior; |
| |
| @override |
| _BottomSheetState 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) { |
| if (widget.onDragStart != null) { |
| widget.onDragStart(details); |
| } |
| } |
| |
| void _handleDragUpdate(DragUpdateDetails details) { |
| assert(widget.enableDrag); |
| if (_dismissUnderway) |
| return; |
| widget.animationController.value -= details.primaryDelta / (_childHeight ?? details.primaryDelta); |
| } |
| |
| void _handleDragEnd(DragEndDetails details) { |
| assert(widget.enableDrag); |
| 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(); |
| } |
| |
| if (widget.onDragEnd != null) { |
| widget.onDragEnd( |
| 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 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; |
| |
| final Widget bottomSheet = Material( |
| key: _childKey, |
| color: color, |
| elevation: elevation, |
| shape: shape, |
| clipBehavior: clipBehavior, |
| child: NotificationListener<DraggableScrollableNotification>( |
| onNotification: extentChanged, |
| child: widget.builder(context), |
| ), |
| ); |
| return !widget.enableDrag ? bottomSheet : GestureDetector( |
| onVerticalDragStart: _handleDragStart, |
| onVerticalDragUpdate: _handleDragUpdate, |
| onVerticalDragEnd: _handleDragEnd, |
| child: bottomSheet, |
| excludeFromSemantics: true, |
| ); |
| } |
| } |
| |
| // 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, |
| minHeight: 0.0, |
| 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.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 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; |
| } |
| return null; |
| } |
| |
| 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, |
| 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: 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, |
| enableDrag: widget.enableDrag, |
| onDragStart: handleDragStart, |
| onDragEnd: handleDragEnd, |
| ), |
| ), |
| ), |
| ); |
| }, |
| ); |
| } |
| } |
| |
| class _ModalBottomSheetRoute<T> extends PopupRoute<T> { |
| _ModalBottomSheetRoute({ |
| this.builder, |
| this.theme, |
| this.barrierLabel, |
| this.backgroundColor, |
| this.elevation, |
| this.shape, |
| this.clipBehavior, |
| this.modalBarrierColor, |
| this.isDismissible = true, |
| this.enableDrag = true, |
| @required this.isScrollControlled, |
| RouteSettings settings, |
| }) : assert(isScrollControlled != null), |
| assert(isDismissible != null), |
| assert(enableDrag != null), |
| super(settings: settings); |
| |
| final WidgetBuilder builder; |
| final ThemeData theme; |
| final bool isScrollControlled; |
| final Color backgroundColor; |
| final double elevation; |
| final ShapeBorder shape; |
| final Clip clipBehavior; |
| final Color modalBarrierColor; |
| final bool isDismissible; |
| final bool enableDrag; |
| |
| @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); |
| _animationController = BottomSheet.createAnimationController(navigator.overlay); |
| return _animationController; |
| } |
| |
| @override |
| Widget buildPage(BuildContext context, Animation<double> animation, Animation<double> secondaryAnimation) { |
| final BottomSheetThemeData sheetTheme = theme?.bottomSheetTheme ?? Theme.of(context).bottomSheetTheme; |
| // By definition, the bottom sheet is aligned to the bottom of the page |
| // and isn't exposed to the top padding of the MediaQuery. |
| Widget bottomSheet = MediaQuery.removePadding( |
| context: context, |
| removeTop: true, |
| child: _ModalBottomSheet<T>( |
| route: this, |
| backgroundColor: backgroundColor ?? sheetTheme?.modalBackgroundColor ?? sheetTheme?.backgroundColor, |
| elevation: elevation ?? sheetTheme?.modalElevation ?? sheetTheme?.elevation, |
| shape: shape, |
| clipBehavior: clipBehavior, |
| isScrollControlled: isScrollControlled, |
| enableDrag: enableDrag, |
| ), |
| ); |
| if (theme != null) |
| bottomSheet = Theme(data: theme, child: bottomSheet); |
| return bottomSheet; |
| } |
| } |
| |
| // TODO(guidezpl): Look into making this public. A copy of this class is in scaffold.dart, for now. |
| /// 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 use 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], and [clipBehavior] |
| /// parameters can be passed in to customize the appearance and behavior of |
| /// modal bottom sheets. |
| /// |
| /// 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 --template=stateless_widget_scaffold} |
| /// |
| /// 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. |
| /// |
| /// ```dart |
| /// Widget build(BuildContext context) { |
| /// return Center( |
| /// child: RaisedButton( |
| /// child: const Text('showModalBottomSheet'), |
| /// onPressed: () { |
| /// showModalBottomSheet<void>( |
| /// context: context, |
| /// builder: (BuildContext context) { |
| /// return Container( |
| /// height: 200, |
| /// color: Colors.amber, |
| /// child: Center( |
| /// child: Column( |
| /// mainAxisAlignment: MainAxisAlignment.center, |
| /// mainAxisSize: MainAxisSize.min, |
| /// children: <Widget>[ |
| /// const Text('Modal BottomSheet'), |
| /// RaisedButton( |
| /// child: const Text('Close BottomSheet'), |
| /// onPressed: () => Navigator.pop(context), |
| /// ) |
| /// ], |
| /// ), |
| /// ), |
| /// ); |
| /// }, |
| /// ); |
| /// }, |
| /// ), |
| /// ); |
| /// } |
| /// ``` |
| /// {@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, |
| Color barrierColor, |
| bool isScrollControlled = false, |
| bool useRootNavigator = false, |
| bool isDismissible = true, |
| bool enableDrag = true, |
| }) { |
| assert(context != null); |
| assert(builder != null); |
| assert(isScrollControlled != null); |
| assert(useRootNavigator != null); |
| assert(isDismissible != null); |
| assert(enableDrag != null); |
| assert(debugCheckHasMediaQuery(context)); |
| assert(debugCheckHasMaterialLocalizations(context)); |
| |
| return Navigator.of(context, rootNavigator: useRootNavigator).push(_ModalBottomSheetRoute<T>( |
| builder: builder, |
| theme: Theme.of(context, shadowThemeOnly: true), |
| isScrollControlled: isScrollControlled, |
| barrierLabel: MaterialLocalizations.of(context).modalBarrierDismissLabel, |
| backgroundColor: backgroundColor, |
| elevation: elevation, |
| shape: shape, |
| clipBehavior: clipBehavior, |
| isDismissible: isDismissible, |
| modalBarrierColor: barrierColor, |
| enableDrag: enableDrag, |
| )); |
| } |
| |
| /// 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], and [clipBehavior] |
| /// parameters can be passed in to customize the appearance and behavior of |
| /// persistent bottom sheets. |
| /// |
| /// 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, |
| }) { |
| assert(context != null); |
| assert(builder != null); |
| assert(debugCheckHasScaffold(context)); |
| |
| return Scaffold.of(context).showBottomSheet<T>( |
| builder, |
| backgroundColor: backgroundColor, |
| elevation: elevation, |
| shape: shape, |
| clipBehavior: clipBehavior, |
| ); |
| } |