bottom_sheet.dart 25 KB
Newer Older
Ian Hickson's avatar
Ian Hickson committed
1
// Copyright 2014 The Flutter Authors. All rights reserved.
2 3 4 5
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

import 'dart:async';
6
import 'dart:ui' show lerpDouble;
7

8
import 'package:flutter/foundation.dart';
9
import 'package:flutter/scheduler.dart';
10 11
import 'package:flutter/widgets.dart';

12
import 'bottom_sheet_theme.dart';
13
import 'colors.dart';
14
import 'curves.dart';
15
import 'debug.dart';
16
import 'material.dart';
17
import 'material_localizations.dart';
18
import 'scaffold.dart';
19
import 'theme.dart';
20

21 22 23
const Duration _bottomSheetEnterDuration = Duration(milliseconds: 250);
const Duration _bottomSheetExitDuration = Duration(milliseconds: 200);
const Curve _modalBottomSheetCurve = decelerateEasing;
24 25
const double _minFlingVelocity = 700.0;
const double _closeProgressThreshold = 0.5;
26

27 28 29 30 31 32
typedef BottomSheetDragStartHandler = void Function(DragStartDetails details);
typedef BottomSheetDragEndHandler = void Function(
  DragEndDetails details, {
  bool isClosing,
});

33 34 35 36 37 38 39 40
/// 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
41 42
///    [ScaffoldState.showBottomSheet] function or by specifying the
///    [Scaffold.bottomSheet] constructor parameter.
43 44 45 46 47 48 49
///
///  * _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
50 51
/// create a persistent bottom sheet with [ScaffoldState.showBottomSheet] or
/// [Scaffold.bottomSheet], and a modal bottom sheet with [showModalBottomSheet].
52 53 54
///
/// See also:
///
55 56 57 58 59
///  * [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>
60
class BottomSheet extends StatefulWidget {
61 62 63
  /// Creates a bottom sheet.
  ///
  /// Typically, bottom sheets are created implicitly by
64
  /// [ScaffoldState.showBottomSheet], for persistent bottom sheets, or by
65
  /// [showModalBottomSheet], for modal bottom sheets.
66
  const BottomSheet({
67
    Key key,
68
    this.animationController,
69
    this.enableDrag = true,
70 71
    this.onDragStart,
    this.onDragEnd,
72
    this.backgroundColor,
73 74
    this.elevation,
    this.shape,
75
    this.clipBehavior,
76
    @required this.onClosing,
77
    @required this.builder,
78 79
  }) : assert(enableDrag != null),
       assert(onClosing != null),
80
       assert(builder != null),
81
       assert(elevation == null || elevation >= 0.0),
82
       super(key: key);
83

84 85
  /// The animation controller that controls the bottom sheet's entrance and
  /// exit animations.
86 87 88
  ///
  /// The BottomSheet widget will manipulate the position of this animation, it
  /// is not just a passive observer.
89
  final AnimationController animationController;
90 91 92

  /// Called when the bottom sheet begins to close.
  ///
93
  /// A bottom sheet might be prevented from closing (e.g., by user
94 95
  /// interaction) even after this callback is called. For this reason, this
  /// callback might be call multiple times for a given bottom sheet.
96
  final VoidCallback onClosing;
97 98 99 100 101

  /// A builder for the contents of the sheet.
  ///
  /// The bottom sheet will wrap the widget produced by this builder in a
  /// [Material] widget.
102 103
  final WidgetBuilder builder;

104
  /// If true, the bottom sheet can be dragged up and down and dismissed by
105
  /// swiping downwards.
106 107 108 109
  ///
  /// Default is true.
  final bool enableDrag;

110 111 112 113 114 115 116 117 118 119 120 121 122 123 124
  /// 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;

125 126 127 128 129 130 131
  /// 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;

132
  /// The z-coordinate at which to place this material relative to its parent.
133
  ///
134 135 136
  /// This controls the size of the shadow below the material.
  ///
  /// Defaults to 0. The value is non-negative.
137 138
  final double elevation;

139
  /// The shape of the bottom sheet.
140
  ///
141 142 143 144
  /// Defines the bottom sheet's [Material.shape].
  ///
  /// Defaults to null and falls back to [Material]'s default.
  final ShapeBorder shape;
145

146 147 148 149 150 151 152 153 154 155 156 157 158
  /// {@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;

159
  @override
160
  _BottomSheetState createState() => _BottomSheetState();
161

162 163 164 165 166 167
  /// 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.
168
  static AnimationController createAnimationController(TickerProvider vsync) {
169
    return AnimationController(
170 171
      duration: _bottomSheetEnterDuration,
      reverseDuration: _bottomSheetExitDuration,
172 173
      debugLabel: 'BottomSheet',
      vsync: vsync,
174 175
    );
  }
176 177 178 179
}

class _BottomSheetState extends State<BottomSheet> {

180
  final GlobalKey _childKey = GlobalKey(debugLabel: 'BottomSheet child');
181 182

  double get _childHeight {
183
    final RenderBox renderBox = _childKey.currentContext.findRenderObject() as RenderBox;
184 185
    return renderBox.size.height;
  }
186

187
  bool get _dismissUnderway => widget.animationController.status == AnimationStatus.reverse;
188

189 190 191 192 193 194
  void _handleDragStart(DragStartDetails details) {
    if (widget.onDragStart != null) {
      widget.onDragStart(details);
    }
  }

195
  void _handleDragUpdate(DragUpdateDetails details) {
196
    assert(widget.enableDrag);
197 198
    if (_dismissUnderway)
      return;
199
    widget.animationController.value -= details.primaryDelta / (_childHeight ?? details.primaryDelta);
200 201
  }

202
  void _handleDragEnd(DragEndDetails details) {
203
    assert(widget.enableDrag);
204 205
    if (_dismissUnderway)
      return;
206
    bool isClosing = false;
207
    if (details.velocity.pixelsPerSecond.dy > _minFlingVelocity) {
208
      final double flingVelocity = -details.velocity.pixelsPerSecond.dy / _childHeight;
209
      if (widget.animationController.value > 0.0) {
210
        widget.animationController.fling(velocity: flingVelocity);
211 212
      }
      if (flingVelocity < 0.0) {
213
        isClosing = true;
214 215
      }
    } else if (widget.animationController.value < _closeProgressThreshold) {
216 217
      if (widget.animationController.value > 0.0)
        widget.animationController.fling(velocity: -1.0);
218
      isClosing = true;
219
    } else {
220
      widget.animationController.forward();
221 222 223 224 225 226 227 228 229 230 231 232
    }

    if (widget.onDragEnd != null) {
      widget.onDragEnd(
        details,
        isClosing: isClosing,
      );
    }

    if (isClosing) {
      widget.onClosing();
    }
233 234 235 236 237
  }

  bool extentChanged(DraggableScrollableNotification notification) {
    if (notification.extent == notification.minExtent) {
      widget.onClosing();
238
    }
239
    return false;
240 241
  }

242
  @override
243
  Widget build(BuildContext context) {
244 245 246 247
    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;
248
    final Clip clipBehavior = widget.clipBehavior ?? bottomSheetTheme.clipBehavior ?? Clip.none;
249

250
    final Widget bottomSheet = Material(
251
      key: _childKey,
252 253 254
      color: color,
      elevation: elevation,
      shape: shape,
255
      clipBehavior: clipBehavior,
256 257 258 259
      child: NotificationListener<DraggableScrollableNotification>(
        onNotification: extentChanged,
        child: widget.builder(context),
      ),
260
    );
261
    return !widget.enableDrag ? bottomSheet : GestureDetector(
262
      onVerticalDragStart: _handleDragStart,
263
      onVerticalDragUpdate: _handleDragUpdate,
264
      onVerticalDragEnd: _handleDragEnd,
265
      child: bottomSheet,
266
      excludeFromSemantics: true,
267 268 269 270
    );
  }
}

271
// PERSISTENT BOTTOM SHEETS
272

273
// See scaffold.dart
274 275


276
// MODAL BOTTOM SHEETS
277
class _ModalBottomSheetLayout extends SingleChildLayoutDelegate {
278
  _ModalBottomSheetLayout(this.progress, this.isScrollControlled);
279 280

  final double progress;
281
  final bool isScrollControlled;
282

283
  @override
284
  BoxConstraints getConstraintsForChild(BoxConstraints constraints) {
285
    return BoxConstraints(
286 287 288
      minWidth: constraints.maxWidth,
      maxWidth: constraints.maxWidth,
      minHeight: 0.0,
289 290 291
      maxHeight: isScrollControlled
        ? constraints.maxHeight
        : constraints.maxHeight * 9.0 / 16.0,
292 293 294
    );
  }

295
  @override
296
  Offset getPositionForChild(Size size, Size childSize) {
297
    return Offset(0.0, size.height - childSize.height * progress);
298 299
  }

300
  @override
301 302
  bool shouldRelayout(_ModalBottomSheetLayout oldDelegate) {
    return progress != oldDelegate.progress;
303 304 305
  }
}

306
class _ModalBottomSheet<T> extends StatefulWidget {
307 308 309
  const _ModalBottomSheet({
    Key key,
    this.route,
310 311 312
    this.backgroundColor,
    this.elevation,
    this.shape,
313
    this.clipBehavior,
314
    this.isScrollControlled = false,
315
    this.enableDrag = true,
316
  }) : assert(isScrollControlled != null),
317
       assert(enableDrag != null),
318
       super(key: key);
319

320
  final _ModalBottomSheetRoute<T> route;
321
  final bool isScrollControlled;
322 323 324
  final Color backgroundColor;
  final double elevation;
  final ShapeBorder shape;
325
  final Clip clipBehavior;
326
  final bool enableDrag;
327

328
  @override
329
  _ModalBottomSheetState<T> createState() => _ModalBottomSheetState<T>();
330 331
}

332
class _ModalBottomSheetState<T> extends State<_ModalBottomSheet<T>> {
333 334
  ParametricCurve<double> animationCurve = _modalBottomSheetCurve;

335
  String _getRouteLabel(MaterialLocalizations localizations) {
336
    switch (Theme.of(context).platform) {
337
      case TargetPlatform.iOS:
338
      case TargetPlatform.macOS:
339
        return '';
340 341
      case TargetPlatform.android:
      case TargetPlatform.fuchsia:
342 343
      case TargetPlatform.linux:
      case TargetPlatform.windows:
344
        return localizations.dialogLabel;
345
    }
346 347 348
    return null;
  }

349 350 351 352 353 354 355 356 357 358 359 360 361
  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,
    );
  }

362 363 364 365 366 367 368
  @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);
369

370 371 372 373 374
    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.
375 376 377
        final double animationValue = animationCurve.transform(
            mediaQuery.accessibleNavigation ? 1.0 : widget.route.animation.value
        );
378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396
        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,
397
                clipBehavior: widget.clipBehavior,
398
                enableDrag: widget.enableDrag,
399 400
                onDragStart: handleDragStart,
                onDragEnd: handleDragEnd,
401 402
              ),
            ),
403 404 405
          ),
        );
      },
406 407 408 409
    );
  }
}

Hixie's avatar
Hixie committed
410 411
class _ModalBottomSheetRoute<T> extends PopupRoute<T> {
  _ModalBottomSheetRoute({
412 413
    this.builder,
    this.theme,
414
    this.barrierLabel,
415
    this.backgroundColor,
416 417
    this.elevation,
    this.shape,
418
    this.clipBehavior,
419
    this.modalBarrierColor,
420
    this.isDismissible = true,
421
    this.enableDrag = true,
422
    @required this.isScrollControlled,
423
    RouteSettings settings,
424
  }) : assert(isScrollControlled != null),
425
       assert(isDismissible != null),
426
       assert(enableDrag != null),
427
       super(settings: settings);
428 429

  final WidgetBuilder builder;
430
  final ThemeData theme;
431 432
  final bool isScrollControlled;
  final Color backgroundColor;
433 434
  final double elevation;
  final ShapeBorder shape;
435
  final Clip clipBehavior;
436
  final Color modalBarrierColor;
437
  final bool isDismissible;
438
  final bool enableDrag;
439

440
  @override
441 442 443 444
  Duration get transitionDuration => _bottomSheetEnterDuration;

  @override
  Duration get reverseTransitionDuration => _bottomSheetExitDuration;
445 446

  @override
447
  bool get barrierDismissible => isDismissible;
448

449 450 451
  @override
  final String barrierLabel;

452
  @override
453
  Color get barrierColor => modalBarrierColor ?? Colors.black54;
454

455 456
  AnimationController _animationController;

457
  @override
458
  AnimationController createAnimationController() {
459
    assert(_animationController == null);
460
    _animationController = BottomSheet.createAnimationController(navigator.overlay);
461
    return _animationController;
462 463
  }

464
  @override
465
  Widget buildPage(BuildContext context, Animation<double> animation, Animation<double> secondaryAnimation) {
466
    final BottomSheetThemeData sheetTheme = theme?.bottomSheetTheme ?? Theme.of(context).bottomSheetTheme;
467 468
    // By definition, the bottom sheet is aligned to the bottom of the page
    // and isn't exposed to the top padding of the MediaQuery.
469
    Widget bottomSheet = MediaQuery.removePadding(
470 471
      context: context,
      removeTop: true,
472 473
      child: _ModalBottomSheet<T>(
        route: this,
474 475
        backgroundColor: backgroundColor ?? sheetTheme?.modalBackgroundColor ?? sheetTheme?.backgroundColor,
        elevation: elevation ?? sheetTheme?.modalElevation ?? sheetTheme?.elevation,
476
        shape: shape,
477
        clipBehavior: clipBehavior,
478
        isScrollControlled: isScrollControlled,
479
        enableDrag: enableDrag,
480
      ),
481
    );
482
    if (theme != null)
483
      bottomSheet = Theme(data: theme, child: bottomSheet);
484
    return bottomSheet;
485 486 487
  }
}

488 489 490 491 492 493 494 495 496 497 498 499 500 501 502 503 504 505 506 507 508 509 510 511 512 513 514 515 516 517 518 519 520 521 522 523 524 525 526 527 528 529 530 531 532 533 534 535 536 537 538 539 540 541 542 543 544
// 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)';
  }
}

545 546 547 548 549 550 551 552
/// 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
553 554
/// can be created and displayed with the [showBottomSheet] function or the
/// [ScaffoldState.showBottomSheet] method.
555
///
Ian Hickson's avatar
Ian Hickson committed
556 557 558 559 560
/// 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.
///
561 562 563 564 565 566
/// 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.
///
567 568 569 570 571
/// 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].
///
572 573 574
/// The [isDismissible] parameter specifies whether the bottom sheet will be
/// dismissed when user taps on the scrim.
///
575
/// The [enableDrag] parameter specifies whether the bottom sheet can be
576
/// dragged up and down and dismissed by swiping downwards.
577
///
578 579 580 581
/// The optional [backgroundColor], [elevation], [shape], and [clipBehavior]
/// parameters can be passed in to customize the appearance and behavior of
/// modal bottom sheets.
///
582
/// Returns a `Future` that resolves to the value (if any) that was passed to
583
/// [Navigator.pop] when the modal bottom sheet was closed.
584
///
585
/// {@tool dartpad --template=stateless_widget_scaffold}
586 587 588 589 590 591 592 593 594 595 596 597 598 599 600 601 602 603 604 605 606 607 608 609 610 611 612 613 614 615 616 617 618 619 620 621 622 623 624 625
///
/// 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}
626 627
/// See also:
///
628 629
///  * [BottomSheet], which becomes the parent of the widget returned by the
///    function passed as the `builder` argument to [showModalBottomSheet].
630 631
///  * [showBottomSheet] and [ScaffoldState.showBottomSheet], for showing
///    non-modal bottom sheets.
632 633
///  * [DraggableScrollableSheet], which allows you to create a bottom sheet
///    that grows and then becomes scrollable once it reaches its maximum size.
634
///  * <https://material.io/design/components/sheets-bottom.html#modal-bottom-sheet>
635 636 637
Future<T> showModalBottomSheet<T>({
  @required BuildContext context,
  @required WidgetBuilder builder,
638
  Color backgroundColor,
639 640
  double elevation,
  ShapeBorder shape,
641
  Clip clipBehavior,
642
  Color barrierColor,
643
  bool isScrollControlled = false,
644
  bool useRootNavigator = false,
645
  bool isDismissible = true,
646
  bool enableDrag = true,
647
}) {
648 649
  assert(context != null);
  assert(builder != null);
650
  assert(isScrollControlled != null);
651
  assert(useRootNavigator != null);
652
  assert(isDismissible != null);
653
  assert(enableDrag != null);
654
  assert(debugCheckHasMediaQuery(context));
655
  assert(debugCheckHasMaterialLocalizations(context));
656

657
  return Navigator.of(context, rootNavigator: useRootNavigator).push(_ModalBottomSheetRoute<T>(
658 659
    builder: builder,
    theme: Theme.of(context, shadowThemeOnly: true),
660
    isScrollControlled: isScrollControlled,
661
    barrierLabel: MaterialLocalizations.of(context).modalBarrierDismissLabel,
662 663
    backgroundColor: backgroundColor,
    elevation: elevation,
664
    shape: shape,
665
    clipBehavior: clipBehavior,
666
    isDismissible: isDismissible,
667 668
    modalBarrierColor: barrierColor,
    enableDrag: enableDrag,
669 670
  ));
}
671

672 673
/// Shows a material design bottom sheet in the nearest [Scaffold] ancestor. If
/// you wish to show a persistent bottom sheet, use [Scaffold.bottomSheet].
674
///
675 676 677
/// Returns a controller that can be used to close and otherwise manipulate the
/// bottom sheet.
///
678 679 680 681
/// The optional [backgroundColor], [elevation], [shape], and [clipBehavior]
/// parameters can be passed in to customize the appearance and behavior of
/// persistent bottom sheets.
///
682 683 684 685 686
/// 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
687
/// [ModalRoute] and a back button is added to the app bar of the [Scaffold]
688 689 690
/// that closes the bottom sheet.
///
/// To create a persistent bottom sheet that is not a [LocalHistoryEntry] and
691
/// does not add a back button to the enclosing Scaffold's app bar, use the
692 693
/// [Scaffold.bottomSheet] constructor parameter.
///
694 695 696 697 698 699 700 701 702 703 704
/// 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:
///
705 706
///  * [BottomSheet], which becomes the parent of the widget returned by the
///    `builder`.
707 708 709
///  * [showModalBottomSheet], which can be used to display a modal bottom
///    sheet.
///  * [Scaffold.of], for information about how to obtain the [BuildContext].
710
///  * <https://material.io/design/components/sheets-bottom.html#standard-bottom-sheet>
711 712 713
PersistentBottomSheetController<T> showBottomSheet<T>({
  @required BuildContext context,
  @required WidgetBuilder builder,
714
  Color backgroundColor,
715 716
  double elevation,
  ShapeBorder shape,
717
  Clip clipBehavior,
718 719 720
}) {
  assert(context != null);
  assert(builder != null);
721 722 723 724 725
  assert(debugCheckHasScaffold(context));

  return Scaffold.of(context).showBottomSheet<T>(
    builder,
    backgroundColor: backgroundColor,
726 727
    elevation: elevation,
    shape: shape,
728
    clipBehavior: clipBehavior,
729
  );
730
}