1// Copyright 2014 The Flutter Authors. All rights reserved.
2// Use of this source code is governed by a BSD-style license that can be
3// found in the LICENSE file.
4
5/// @docImport 'dart:ui';
6library;
7
8import 'dart:math' as math;
9
10import 'package:flutter/gestures.dart';
11import 'package:flutter/rendering.dart';
12import 'package:flutter/widgets.dart';
13
14import 'bottom_sheet_theme.dart';
15import 'color_scheme.dart';
16import 'colors.dart';
17import 'constants.dart';
18import 'debug.dart';
19import 'material.dart';
20import 'material_localizations.dart';
21import 'motion.dart';
22import 'scaffold.dart';
23import 'theme.dart';
24
25const Duration _bottomSheetEnterDuration = Duration(milliseconds: 250);
26const Duration _bottomSheetExitDuration = Duration(milliseconds: 200);
27const Curve _modalBottomSheetCurve = Easing.legacyDecelerate;
28const double _minFlingVelocity = 700.0;
29const double _closeProgressThreshold = 0.5;
30const double _defaultScrollControlDisabledMaxHeightRatio = 9.0 / 16.0;
31
32/// A callback for when the user begins dragging the bottom sheet.
33///
34/// Used by [BottomSheet.onDragStart].
35typedef BottomSheetDragStartHandler = void Function(DragStartDetails details);
36
37/// A callback for when the user stops dragging the bottom sheet.
38///
39/// Used by [BottomSheet.onDragEnd].
40typedef BottomSheetDragEndHandler =
41 void Function(DragEndDetails details, {required bool isClosing});
42
43/// A Material Design bottom sheet.
44///
45/// There are two kinds of bottom sheets in Material Design:
46///
47/// * _Persistent_. A persistent bottom sheet shows information that
48/// supplements the primary content of the app. A persistent bottom sheet
49/// remains visible even when the user interacts with other parts of the app.
50/// Persistent bottom sheets can be created and displayed with the
51/// [ScaffoldState.showBottomSheet] function or by specifying the
52/// [Scaffold.bottomSheet] constructor parameter.
53///
54/// * _Modal_. A modal bottom sheet is an alternative to a menu or a dialog and
55/// prevents the user from interacting with the rest of the app. Modal bottom
56/// sheets can be created and displayed with the [showModalBottomSheet]
57/// function.
58///
59/// The [BottomSheet] widget itself is rarely used directly. Instead, prefer to
60/// create a persistent bottom sheet with [ScaffoldState.showBottomSheet] or
61/// [Scaffold.bottomSheet], and a modal bottom sheet with [showModalBottomSheet].
62///
63/// See also:
64///
65/// * [showBottomSheet] and [ScaffoldState.showBottomSheet], for showing
66/// non-modal "persistent" bottom sheets.
67/// * [showModalBottomSheet], which can be used to display a modal bottom
68/// sheet.
69/// * [BottomSheetThemeData], which can be used to customize the default
70/// bottom sheet property values.
71/// * The Material 2 spec at <https://m2.material.io/components/sheets-bottom>.
72/// * The Material 3 spec at <https://m3.material.io/components/bottom-sheets/overview>.
73class BottomSheet extends StatefulWidget {
74 /// Creates a bottom sheet.
75 ///
76 /// Typically, bottom sheets are created implicitly by
77 /// [ScaffoldState.showBottomSheet], for persistent bottom sheets, or by
78 /// [showModalBottomSheet], for modal bottom sheets.
79 const BottomSheet({
80 super.key,
81 this.animationController,
82 this.enableDrag = true,
83 this.showDragHandle,
84 this.dragHandleColor,
85 this.dragHandleSize,
86 this.onDragStart,
87 this.onDragEnd,
88 this.backgroundColor,
89 this.shadowColor,
90 this.elevation,
91 this.shape,
92 this.clipBehavior,
93 this.constraints,
94 required this.onClosing,
95 required this.builder,
96 }) : assert(elevation == null || elevation >= 0.0);
97
98 /// The animation controller that controls the bottom sheet's entrance and
99 /// exit animations.
100 ///
101 /// The BottomSheet widget will manipulate the position of this animation, it
102 /// is not just a passive observer.
103 final AnimationController? animationController;
104
105 /// Called when the bottom sheet begins to close.
106 ///
107 /// A bottom sheet might be prevented from closing (e.g., by user
108 /// interaction) even after this callback is called. For this reason, this
109 /// callback might be call multiple times for a given bottom sheet.
110 final VoidCallback onClosing;
111
112 /// A builder for the contents of the sheet.
113 ///
114 /// The bottom sheet will wrap the widget produced by this builder in a
115 /// [Material] widget.
116 final WidgetBuilder builder;
117
118 /// If true, the bottom sheet can be dragged up and down and dismissed by
119 /// swiping downwards.
120 ///
121 /// If [showDragHandle] is true, this only applies to the content below the drag handle,
122 /// because the drag handle is always draggable.
123 ///
124 /// Default is true.
125 ///
126 /// If this is true, the [animationController] must not be null.
127 /// Use [BottomSheet.createAnimationController] to create one, or provide
128 /// another AnimationController.
129 final bool enableDrag;
130
131 /// Specifies whether a drag handle is shown.
132 ///
133 /// The drag handle appears at the top of the bottom sheet. The default color is
134 /// [ColorScheme.onSurfaceVariant] with an opacity of 0.4 and can be customized
135 /// using [dragHandleColor]. The default size is `Size(32,4)` and can be customized
136 /// with [dragHandleSize].
137 ///
138 /// If null, then the value of [BottomSheetThemeData.showDragHandle] is used. If
139 /// that is also null, defaults to false.
140 ///
141 /// If this is true, the [animationController] must not be null.
142 /// Use [BottomSheet.createAnimationController] to create one, or provide
143 /// another AnimationController.
144 final bool? showDragHandle;
145
146 /// The bottom sheet drag handle's color.
147 ///
148 /// Defaults to [BottomSheetThemeData.dragHandleColor].
149 /// If that is also null, defaults to [ColorScheme.onSurfaceVariant].
150 final Color? dragHandleColor;
151
152 /// Defaults to [BottomSheetThemeData.dragHandleSize].
153 /// If that is also null, defaults to Size(32, 4).
154 final Size? dragHandleSize;
155
156 /// Called when the user begins dragging the bottom sheet vertically, if
157 /// [enableDrag] is true.
158 ///
159 /// Would typically be used to change the bottom sheet animation curve so
160 /// that it tracks the user's finger accurately.
161 final BottomSheetDragStartHandler? onDragStart;
162
163 /// Called when the user stops dragging the bottom sheet, if [enableDrag]
164 /// is true.
165 ///
166 /// Would typically be used to reset the bottom sheet animation curve, so
167 /// that it animates non-linearly. Called before [onClosing] if the bottom
168 /// sheet is closing.
169 final BottomSheetDragEndHandler? onDragEnd;
170
171 /// The bottom sheet's background color.
172 ///
173 /// Defines the bottom sheet's [Material.color].
174 ///
175 /// Defaults to null and falls back to [Material]'s default.
176 final Color? backgroundColor;
177
178 /// The color of the shadow below the sheet.
179 ///
180 /// If this property is null, then [BottomSheetThemeData.shadowColor] of
181 /// [ThemeData.bottomSheetTheme] is used. If that is also null, the default value
182 /// is transparent.
183 ///
184 /// See also:
185 ///
186 /// * [elevation], which defines the size of the shadow below the sheet.
187 /// * [shape], which defines the shape of the sheet and its shadow.
188 final Color? shadowColor;
189
190 /// The z-coordinate at which to place this material relative to its parent.
191 ///
192 /// This controls the size of the shadow below the material.
193 ///
194 /// Defaults to 0. The value is non-negative.
195 final double? elevation;
196
197 /// The shape of the bottom sheet.
198 ///
199 /// Defines the bottom sheet's [Material.shape].
200 ///
201 /// Defaults to null and falls back to [Material]'s default.
202 final ShapeBorder? shape;
203
204 /// {@macro flutter.material.Material.clipBehavior}
205 ///
206 /// Defines the bottom sheet's [Material.clipBehavior].
207 ///
208 /// Use this property to enable clipping of content when the bottom sheet has
209 /// a custom [shape] and the content can extend past this shape. For example,
210 /// a bottom sheet with rounded corners and an edge-to-edge [Image] at the
211 /// top.
212 ///
213 /// If this property is null then [BottomSheetThemeData.clipBehavior] of
214 /// [ThemeData.bottomSheetTheme] is used. If that's null then the behavior
215 /// will be [Clip.none].
216 final Clip? clipBehavior;
217
218 /// Defines minimum and maximum sizes for a [BottomSheet].
219 ///
220 /// If null, then the ambient [ThemeData.bottomSheetTheme]'s
221 /// [BottomSheetThemeData.constraints] will be used. If that
222 /// is null and [ThemeData.useMaterial3] is true, then the bottom sheet
223 /// will have a max width of 640dp. If [ThemeData.useMaterial3] is false, then
224 /// the bottom sheet's size will be constrained by its parent
225 /// (usually a [Scaffold]). In this case, consider limiting the width by
226 /// setting smaller constraints for large screens.
227 ///
228 /// If constraints are specified (either in this property or in the
229 /// theme), the bottom sheet will be aligned to the bottom-center of
230 /// the available space. Otherwise, no alignment is applied.
231 final BoxConstraints? constraints;
232
233 @override
234 State<BottomSheet> createState() => _BottomSheetState();
235
236 /// Creates an [AnimationController] suitable for a
237 /// [BottomSheet.animationController].
238 ///
239 /// This API is available as a convenience for a Material compliant bottom sheet
240 /// animation. If alternative animation durations are required, a different
241 /// animation controller could be provided.
242 static AnimationController createAnimationController(
243 TickerProvider vsync, {
244 AnimationStyle? sheetAnimationStyle,
245 }) {
246 return AnimationController(
247 duration: sheetAnimationStyle?.duration ?? _bottomSheetEnterDuration,
248 reverseDuration: sheetAnimationStyle?.reverseDuration ?? _bottomSheetExitDuration,
249 debugLabel: 'BottomSheet',
250 vsync: vsync,
251 );
252 }
253}
254
255class _BottomSheetState extends State<BottomSheet> {
256 final GlobalKey _childKey = GlobalKey(debugLabel: 'BottomSheet child');
257
258 double get _childHeight {
259 final RenderBox renderBox = _childKey.currentContext!.findRenderObject()! as RenderBox;
260 return renderBox.size.height;
261 }
262
263 bool get _dismissUnderway => widget.animationController!.status == AnimationStatus.reverse;
264
265 Set<WidgetState> dragHandleStates = <WidgetState>{};
266
267 void _handleDragStart(DragStartDetails details) {
268 setState(() {
269 dragHandleStates.add(WidgetState.dragged);
270 });
271 widget.onDragStart?.call(details);
272 }
273
274 void _handleDragUpdate(DragUpdateDetails details) {
275 assert(
276 (widget.enableDrag || (widget.showDragHandle ?? false)) && widget.animationController != null,
277 "'BottomSheet.animationController' cannot be null when 'BottomSheet.enableDrag' or 'BottomSheet.showDragHandle' is true. "
278 "Use 'BottomSheet.createAnimationController' to create one, or provide another AnimationController.",
279 );
280 if (_dismissUnderway) {
281 return;
282 }
283 widget.animationController!.value -= details.primaryDelta! / _childHeight;
284 }
285
286 void _handleDragEnd(DragEndDetails details) {
287 assert(
288 (widget.enableDrag || (widget.showDragHandle ?? false)) && widget.animationController != null,
289 "'BottomSheet.animationController' cannot be null when 'BottomSheet.enableDrag' or 'BottomSheet.showDragHandle' is true. "
290 "Use 'BottomSheet.createAnimationController' to create one, or provide another AnimationController.",
291 );
292 if (_dismissUnderway) {
293 return;
294 }
295 setState(() {
296 dragHandleStates.remove(WidgetState.dragged);
297 });
298 bool isClosing = false;
299 if (details.velocity.pixelsPerSecond.dy > _minFlingVelocity) {
300 final double flingVelocity = -details.velocity.pixelsPerSecond.dy / _childHeight;
301 if (widget.animationController!.value > 0.0) {
302 widget.animationController!.fling(velocity: flingVelocity);
303 }
304 if (flingVelocity < 0.0) {
305 isClosing = true;
306 }
307 } else if (widget.animationController!.value < _closeProgressThreshold) {
308 if (widget.animationController!.value > 0.0) {
309 widget.animationController!.fling(velocity: -1.0);
310 }
311 isClosing = true;
312 } else {
313 widget.animationController!.forward();
314 }
315
316 widget.onDragEnd?.call(details, isClosing: isClosing);
317
318 if (isClosing) {
319 widget.onClosing();
320 }
321 }
322
323 bool extentChanged(DraggableScrollableNotification notification) {
324 if (notification.extent == notification.minExtent && notification.shouldCloseOnMinExtent) {
325 widget.onClosing();
326 }
327 return false;
328 }
329
330 void _handleDragHandleHover(bool hovering) {
331 if (hovering != dragHandleStates.contains(WidgetState.hovered)) {
332 setState(() {
333 if (hovering) {
334 dragHandleStates.add(WidgetState.hovered);
335 } else {
336 dragHandleStates.remove(WidgetState.hovered);
337 }
338 });
339 }
340 }
341
342 @override
343 Widget build(BuildContext context) {
344 final BottomSheetThemeData bottomSheetTheme = Theme.of(context).bottomSheetTheme;
345 final bool useMaterial3 = Theme.of(context).useMaterial3;
346 final BottomSheetThemeData defaults =
347 useMaterial3 ? _BottomSheetDefaultsM3(context) : const BottomSheetThemeData();
348 final BoxConstraints? constraints =
349 widget.constraints ?? bottomSheetTheme.constraints ?? defaults.constraints;
350 final Color? color =
351 widget.backgroundColor ?? bottomSheetTheme.backgroundColor ?? defaults.backgroundColor;
352 final Color? surfaceTintColor = bottomSheetTheme.surfaceTintColor ?? defaults.surfaceTintColor;
353 final Color? shadowColor =
354 widget.shadowColor ?? bottomSheetTheme.shadowColor ?? defaults.shadowColor;
355 final double elevation =
356 widget.elevation ?? bottomSheetTheme.elevation ?? defaults.elevation ?? 0;
357 final ShapeBorder? shape = widget.shape ?? bottomSheetTheme.shape ?? defaults.shape;
358 final Clip clipBehavior = widget.clipBehavior ?? bottomSheetTheme.clipBehavior ?? Clip.none;
359 final bool showDragHandle =
360 widget.showDragHandle ?? (widget.enableDrag && (bottomSheetTheme.showDragHandle ?? false));
361
362 Widget? dragHandle;
363 if (showDragHandle) {
364 dragHandle = _DragHandle(
365 onSemanticsTap: widget.onClosing,
366 handleHover: _handleDragHandleHover,
367 states: dragHandleStates,
368 dragHandleColor: widget.dragHandleColor,
369 dragHandleSize: widget.dragHandleSize,
370 );
371 // Only add [_BottomSheetGestureDetector] to the drag handle when the rest of the
372 // bottom sheet is not draggable. If the whole bottom sheet is draggable,
373 // no need to add it.
374 if (!widget.enableDrag) {
375 dragHandle = _BottomSheetGestureDetector(
376 onVerticalDragStart: _handleDragStart,
377 onVerticalDragUpdate: _handleDragUpdate,
378 onVerticalDragEnd: _handleDragEnd,
379 child: dragHandle,
380 );
381 }
382 }
383
384 Widget bottomSheet = Material(
385 key: _childKey,
386 color: color,
387 elevation: elevation,
388 surfaceTintColor: surfaceTintColor,
389 shadowColor: shadowColor,
390 shape: shape,
391 clipBehavior: clipBehavior,
392 child: NotificationListener<DraggableScrollableNotification>(
393 onNotification: extentChanged,
394 child:
395 !showDragHandle
396 ? widget.builder(context)
397 : Stack(
398 alignment: Alignment.topCenter,
399 children: <Widget>[
400 dragHandle!,
401 Padding(
402 padding: const EdgeInsets.only(top: kMinInteractiveDimension),
403 child: widget.builder(context),
404 ),
405 ],
406 ),
407 ),
408 );
409
410 if (constraints != null) {
411 bottomSheet = Align(
412 alignment: Alignment.bottomCenter,
413 heightFactor: 1.0,
414 child: ConstrainedBox(constraints: constraints, child: bottomSheet),
415 );
416 }
417
418 return !widget.enableDrag
419 ? bottomSheet
420 : _BottomSheetGestureDetector(
421 onVerticalDragStart: _handleDragStart,
422 onVerticalDragUpdate: _handleDragUpdate,
423 onVerticalDragEnd: _handleDragEnd,
424 child: bottomSheet,
425 );
426 }
427}
428
429// PERSISTENT BOTTOM SHEETS
430
431// See scaffold.dart
432
433class _DragHandle extends StatelessWidget {
434 const _DragHandle({
435 required this.onSemanticsTap,
436 required this.handleHover,
437 required this.states,
438 this.dragHandleColor,
439 this.dragHandleSize,
440 });
441
442 final VoidCallback? onSemanticsTap;
443 final ValueChanged<bool> handleHover;
444 final Set<WidgetState> states;
445 final Color? dragHandleColor;
446 final Size? dragHandleSize;
447
448 @override
449 Widget build(BuildContext context) {
450 final BottomSheetThemeData bottomSheetTheme = Theme.of(context).bottomSheetTheme;
451 final BottomSheetThemeData m3Defaults = _BottomSheetDefaultsM3(context);
452 final Size handleSize =
453 dragHandleSize ?? bottomSheetTheme.dragHandleSize ?? m3Defaults.dragHandleSize!;
454
455 return MouseRegion(
456 onEnter: (PointerEnterEvent event) => handleHover(true),
457 onExit: (PointerExitEvent event) => handleHover(false),
458 child: Semantics(
459 label: MaterialLocalizations.of(context).modalBarrierDismissLabel,
460 container: true,
461 button: true,
462 onTap: onSemanticsTap,
463 child: SizedBox(
464 width: math.max(handleSize.width, kMinInteractiveDimension),
465 height: math.max(handleSize.height, kMinInteractiveDimension),
466 child: Center(
467 child: Container(
468 height: handleSize.height,
469 width: handleSize.width,
470 decoration: BoxDecoration(
471 borderRadius: BorderRadius.circular(handleSize.height / 2),
472 color:
473 WidgetStateProperty.resolveAs<Color?>(dragHandleColor, states) ??
474 WidgetStateProperty.resolveAs<Color?>(
475 bottomSheetTheme.dragHandleColor,
476 states,
477 ) ??
478 m3Defaults.dragHandleColor,
479 ),
480 ),
481 ),
482 ),
483 ),
484 );
485 }
486}
487
488class _BottomSheetLayoutWithSizeListener extends SingleChildRenderObjectWidget {
489 const _BottomSheetLayoutWithSizeListener({
490 required this.onChildSizeChanged,
491 required this.animationValue,
492 required this.isScrollControlled,
493 required this.scrollControlDisabledMaxHeightRatio,
494 super.child,
495 });
496
497 final ValueChanged<Size> onChildSizeChanged;
498 final double animationValue;
499 final bool isScrollControlled;
500 final double scrollControlDisabledMaxHeightRatio;
501
502 @override
503 _RenderBottomSheetLayoutWithSizeListener createRenderObject(BuildContext context) {
504 return _RenderBottomSheetLayoutWithSizeListener(
505 onChildSizeChanged: onChildSizeChanged,
506 animationValue: animationValue,
507 isScrollControlled: isScrollControlled,
508 scrollControlDisabledMaxHeightRatio: scrollControlDisabledMaxHeightRatio,
509 );
510 }
511
512 @override
513 void updateRenderObject(
514 BuildContext context,
515 _RenderBottomSheetLayoutWithSizeListener renderObject,
516 ) {
517 renderObject.onChildSizeChanged = onChildSizeChanged;
518 renderObject.animationValue = animationValue;
519 renderObject.isScrollControlled = isScrollControlled;
520 renderObject.scrollControlDisabledMaxHeightRatio = scrollControlDisabledMaxHeightRatio;
521 }
522}
523
524class _RenderBottomSheetLayoutWithSizeListener extends RenderShiftedBox {
525 _RenderBottomSheetLayoutWithSizeListener({
526 RenderBox? child,
527 required ValueChanged<Size> onChildSizeChanged,
528 required double animationValue,
529 required bool isScrollControlled,
530 required double scrollControlDisabledMaxHeightRatio,
531 }) : _onChildSizeChanged = onChildSizeChanged,
532 _animationValue = animationValue,
533 _isScrollControlled = isScrollControlled,
534 _scrollControlDisabledMaxHeightRatio = scrollControlDisabledMaxHeightRatio,
535 super(child);
536
537 Size _lastSize = Size.zero;
538
539 ValueChanged<Size> get onChildSizeChanged => _onChildSizeChanged;
540 ValueChanged<Size> _onChildSizeChanged;
541 set onChildSizeChanged(ValueChanged<Size> newCallback) {
542 if (_onChildSizeChanged == newCallback) {
543 return;
544 }
545
546 _onChildSizeChanged = newCallback;
547 markNeedsLayout();
548 }
549
550 double get animationValue => _animationValue;
551 double _animationValue;
552 set animationValue(double newValue) {
553 if (_animationValue == newValue) {
554 return;
555 }
556
557 _animationValue = newValue;
558 markNeedsLayout();
559 }
560
561 bool get isScrollControlled => _isScrollControlled;
562 bool _isScrollControlled;
563 set isScrollControlled(bool newValue) {
564 if (_isScrollControlled == newValue) {
565 return;
566 }
567
568 _isScrollControlled = newValue;
569 markNeedsLayout();
570 }
571
572 double get scrollControlDisabledMaxHeightRatio => _scrollControlDisabledMaxHeightRatio;
573 double _scrollControlDisabledMaxHeightRatio;
574 set scrollControlDisabledMaxHeightRatio(double newValue) {
575 if (_scrollControlDisabledMaxHeightRatio == newValue) {
576 return;
577 }
578
579 _scrollControlDisabledMaxHeightRatio = newValue;
580 markNeedsLayout();
581 }
582
583 @override
584 double computeMinIntrinsicWidth(double height) => 0.0;
585
586 @override
587 double computeMaxIntrinsicWidth(double height) => 0.0;
588
589 @override
590 double computeMinIntrinsicHeight(double width) => 0.0;
591
592 @override
593 double computeMaxIntrinsicHeight(double width) => 0.0;
594
595 @override
596 Size computeDryLayout(BoxConstraints constraints) => constraints.biggest;
597
598 @override
599 double? computeDryBaseline(covariant BoxConstraints constraints, TextBaseline baseline) {
600 final RenderBox? child = this.child;
601 if (child == null) {
602 return null;
603 }
604 final BoxConstraints childConstraints = _getConstraintsForChild(constraints);
605 final double? result = child.getDryBaseline(childConstraints, baseline);
606 if (result == null) {
607 return null;
608 }
609 final Size childSize =
610 childConstraints.isTight ? childConstraints.smallest : child.getDryLayout(childConstraints);
611 return result + _getPositionForChild(constraints.biggest, childSize).dy;
612 }
613
614 BoxConstraints _getConstraintsForChild(BoxConstraints constraints) {
615 return BoxConstraints(
616 minWidth: constraints.maxWidth,
617 maxWidth: constraints.maxWidth,
618 maxHeight:
619 isScrollControlled
620 ? constraints.maxHeight
621 : constraints.maxHeight * scrollControlDisabledMaxHeightRatio,
622 );
623 }
624
625 Offset _getPositionForChild(Size size, Size childSize) {
626 return Offset(0.0, size.height - childSize.height * animationValue);
627 }
628
629 @override
630 void performLayout() {
631 size = constraints.biggest;
632 final RenderBox? child = this.child;
633 if (child == null) {
634 return;
635 }
636
637 final BoxConstraints childConstraints = _getConstraintsForChild(constraints);
638 assert(childConstraints.debugAssertIsValid(isAppliedConstraint: true));
639 child.layout(childConstraints, parentUsesSize: !childConstraints.isTight);
640 final BoxParentData childParentData = child.parentData! as BoxParentData;
641 final Size childSize = childConstraints.isTight ? childConstraints.smallest : child.size;
642 childParentData.offset = _getPositionForChild(size, childSize);
643
644 if (_lastSize != childSize) {
645 _lastSize = childSize;
646 _onChildSizeChanged.call(_lastSize);
647 }
648 }
649}
650
651class _ModalBottomSheet<T> extends StatefulWidget {
652 const _ModalBottomSheet({
653 super.key,
654 required this.route,
655 this.backgroundColor,
656 this.elevation,
657 this.shape,
658 this.clipBehavior,
659 this.constraints,
660 this.isScrollControlled = false,
661 this.scrollControlDisabledMaxHeightRatio = _defaultScrollControlDisabledMaxHeightRatio,
662 this.enableDrag = true,
663 this.showDragHandle = false,
664 });
665
666 final ModalBottomSheetRoute<T> route;
667 final bool isScrollControlled;
668 final double scrollControlDisabledMaxHeightRatio;
669 final Color? backgroundColor;
670 final double? elevation;
671 final ShapeBorder? shape;
672 final Clip? clipBehavior;
673 final BoxConstraints? constraints;
674 final bool enableDrag;
675 final bool showDragHandle;
676
677 @override
678 _ModalBottomSheetState<T> createState() => _ModalBottomSheetState<T>();
679}
680
681class _ModalBottomSheetState<T> extends State<_ModalBottomSheet<T>> {
682 ParametricCurve<double> animationCurve = _modalBottomSheetCurve;
683
684 String _getRouteLabel(MaterialLocalizations localizations) {
685 switch (Theme.of(context).platform) {
686 case TargetPlatform.iOS:
687 case TargetPlatform.macOS:
688 return '';
689 case TargetPlatform.android:
690 case TargetPlatform.fuchsia:
691 case TargetPlatform.linux:
692 case TargetPlatform.windows:
693 return localizations.dialogLabel;
694 }
695 }
696
697 EdgeInsets _getNewClipDetails(Size topLayerSize) {
698 return EdgeInsets.fromLTRB(0, 0, 0, topLayerSize.height);
699 }
700
701 void handleDragStart(DragStartDetails details) {
702 // Allow the bottom sheet to track the user's finger accurately.
703 animationCurve = Curves.linear;
704 }
705
706 void handleDragEnd(DragEndDetails details, {bool? isClosing}) {
707 // Allow the bottom sheet to animate smoothly from its current position.
708 animationCurve = Split(widget.route.animation!.value, endCurve: _modalBottomSheetCurve);
709 }
710
711 @override
712 Widget build(BuildContext context) {
713 assert(debugCheckHasMediaQuery(context));
714 assert(debugCheckHasMaterialLocalizations(context));
715 final MaterialLocalizations localizations = MaterialLocalizations.of(context);
716 final String routeLabel = _getRouteLabel(localizations);
717
718 return AnimatedBuilder(
719 animation: widget.route.animation!,
720 child: BottomSheet(
721 animationController: widget.route._animationController,
722 onClosing: () {
723 if (widget.route.isCurrent) {
724 Navigator.pop(context);
725 }
726 },
727 builder: widget.route.builder,
728 backgroundColor: widget.backgroundColor,
729 elevation: widget.elevation,
730 shape: widget.shape,
731 clipBehavior: widget.clipBehavior,
732 constraints: widget.constraints,
733 enableDrag: widget.enableDrag,
734 showDragHandle: widget.showDragHandle,
735 onDragStart: handleDragStart,
736 onDragEnd: handleDragEnd,
737 ),
738 builder: (BuildContext context, Widget? child) {
739 final double animationValue = animationCurve.transform(widget.route.animation!.value);
740 return Semantics(
741 scopesRoute: true,
742 namesRoute: true,
743 label: routeLabel,
744 explicitChildNodes: true,
745 child: ClipRect(
746 child: _BottomSheetLayoutWithSizeListener(
747 onChildSizeChanged: (Size size) {
748 widget.route._didChangeBarrierSemanticsClip(_getNewClipDetails(size));
749 },
750 animationValue: animationValue,
751 isScrollControlled: widget.isScrollControlled,
752 scrollControlDisabledMaxHeightRatio: widget.scrollControlDisabledMaxHeightRatio,
753 child: child,
754 ),
755 ),
756 );
757 },
758 );
759 }
760}
761
762/// A route that represents a Material Design modal bottom sheet.
763///
764/// {@template flutter.material.ModalBottomSheetRoute}
765/// A modal bottom sheet is an alternative to a menu or a dialog and prevents
766/// the user from interacting with the rest of the app.
767///
768/// A closely related widget is a persistent bottom sheet, which shows
769/// information that supplements the primary content of the app without
770/// preventing the user from interacting with the app. Persistent bottom sheets
771/// can be created and displayed with the [showBottomSheet] function or the
772/// [ScaffoldState.showBottomSheet] method.
773///
774/// The [isScrollControlled] parameter specifies whether this is a route for
775/// a bottom sheet that will utilize [DraggableScrollableSheet]. Consider
776/// setting this parameter to true if this bottom sheet has
777/// a scrollable child, such as a [ListView] or a [GridView],
778/// to have the bottom sheet be draggable.
779///
780/// The [isDismissible] parameter specifies whether the bottom sheet will be
781/// dismissed when user taps on the scrim.
782///
783/// The [enableDrag] parameter specifies whether the bottom sheet can be
784/// dragged up and down and dismissed by swiping downwards.
785///
786/// The [useSafeArea] parameter specifies whether the sheet will avoid system
787/// intrusions on the top, left, and right. If false, no [SafeArea] is added;
788/// and [MediaQuery.removePadding] is applied to the top,
789/// so that system intrusions at the top will not be avoided by a [SafeArea]
790/// inside the bottom sheet either.
791/// Defaults to false.
792///
793/// The optional [backgroundColor], [elevation], [shape], [clipBehavior],
794/// [constraints] and [transitionAnimationController]
795/// parameters can be passed in to customize the appearance and behavior of
796/// modal bottom sheets (see the documentation for these on [BottomSheet]
797/// for more details).
798///
799/// The [transitionAnimationController] controls the bottom sheet's entrance and
800/// exit animations. It's up to the owner of the controller to call
801/// [AnimationController.dispose] when the controller is no longer needed.
802///
803/// The optional `settings` parameter sets the [RouteSettings] of the modal bottom sheet
804/// sheet. This is particularly useful in the case that a user wants to observe
805/// [PopupRoute]s within a [NavigatorObserver].
806/// {@endtemplate}
807///
808/// {@macro flutter.widgets.RawDialogRoute}
809///
810/// See also:
811///
812/// * [showModalBottomSheet], which is a way to display a ModalBottomSheetRoute.
813/// * [BottomSheet], which becomes the parent of the widget returned by the
814/// function passed as the `builder` argument to [showModalBottomSheet].
815/// * [showBottomSheet] and [ScaffoldState.showBottomSheet], for showing
816/// non-modal bottom sheets.
817/// * [DraggableScrollableSheet], creates a bottom sheet that grows
818/// and then becomes scrollable once it reaches its maximum size.
819/// * [DisplayFeatureSubScreen], which documents the specifics of how
820/// [DisplayFeature]s can split the screen into sub-screens.
821/// * The Material 2 spec at <https://m2.material.io/components/sheets-bottom>.
822/// * The Material 3 spec at <https://m3.material.io/components/bottom-sheets/overview>.
823class ModalBottomSheetRoute<T> extends PopupRoute<T> {
824 /// A modal bottom sheet route.
825 ModalBottomSheetRoute({
826 required this.builder,
827 this.capturedThemes,
828 this.barrierLabel,
829 this.barrierOnTapHint,
830 this.backgroundColor,
831 this.elevation,
832 this.shape,
833 this.clipBehavior,
834 this.constraints,
835 this.modalBarrierColor,
836 this.isDismissible = true,
837 this.enableDrag = true,
838 this.showDragHandle,
839 required this.isScrollControlled,
840 this.scrollControlDisabledMaxHeightRatio = _defaultScrollControlDisabledMaxHeightRatio,
841 super.settings,
842 super.requestFocus,
843 this.transitionAnimationController,
844 this.anchorPoint,
845 this.useSafeArea = false,
846 this.sheetAnimationStyle,
847 });
848
849 /// A builder for the contents of the sheet.
850 ///
851 /// The bottom sheet will wrap the widget produced by this builder in a
852 /// [Material] widget.
853 final WidgetBuilder builder;
854
855 /// Stores a list of captured [InheritedTheme]s that are wrapped around the
856 /// bottom sheet.
857 ///
858 /// Consider setting this attribute when the [ModalBottomSheetRoute]
859 /// is created through [Navigator.push] and its friends.
860 final CapturedThemes? capturedThemes;
861
862 /// Specifies whether this is a route for a bottom sheet that will utilize
863 /// [DraggableScrollableSheet].
864 ///
865 /// Consider setting this parameter to true if this bottom sheet has
866 /// a scrollable child, such as a [ListView] or a [GridView],
867 /// to have the bottom sheet be draggable.
868 final bool isScrollControlled;
869
870 /// The max height constraint ratio for the bottom sheet
871 /// when [isScrollControlled] is set to false,
872 /// no ratio will be applied when [isScrollControlled] is set to true.
873 ///
874 /// Defaults to 9 / 16.
875 final double scrollControlDisabledMaxHeightRatio;
876
877 /// The bottom sheet's background color.
878 ///
879 /// Defines the bottom sheet's [Material.color].
880 ///
881 /// If this property is not provided, it falls back to [Material]'s default.
882 final Color? backgroundColor;
883
884 /// The z-coordinate at which to place this material relative to its parent.
885 ///
886 /// This controls the size of the shadow below the material.
887 ///
888 /// Defaults to 0, must not be negative.
889 final double? elevation;
890
891 /// The shape of the bottom sheet.
892 ///
893 /// Defines the bottom sheet's [Material.shape].
894 ///
895 /// If this property is not provided, it falls back to [Material]'s default.
896 final ShapeBorder? shape;
897
898 /// {@macro flutter.material.Material.clipBehavior}
899 ///
900 /// Defines the bottom sheet's [Material.clipBehavior].
901 ///
902 /// Use this property to enable clipping of content when the bottom sheet has
903 /// a custom [shape] and the content can extend past this shape. For example,
904 /// a bottom sheet with rounded corners and an edge-to-edge [Image] at the
905 /// top.
906 ///
907 /// If this property is null, the [BottomSheetThemeData.clipBehavior] of
908 /// [ThemeData.bottomSheetTheme] is used. If that's null, the behavior defaults to [Clip.none]
909 /// will be [Clip.none].
910 final Clip? clipBehavior;
911
912 /// Defines minimum and maximum sizes for a [BottomSheet].
913 ///
914 /// If null, the ambient [ThemeData.bottomSheetTheme]'s
915 /// [BottomSheetThemeData.constraints] will be used. If that
916 /// is null and [ThemeData.useMaterial3] is true, then the bottom sheet
917 /// will have a max width of 640dp. If [ThemeData.useMaterial3] is false, then
918 /// the bottom sheet's size will be constrained by its parent
919 /// (usually a [Scaffold]). In this case, consider limiting the width by
920 /// setting smaller constraints for large screens.
921 ///
922 /// If constraints are specified (either in this property or in the
923 /// theme), the bottom sheet will be aligned to the bottom-center of
924 /// the available space. Otherwise, no alignment is applied.
925 final BoxConstraints? constraints;
926
927 /// Specifies the color of the modal barrier that darkens everything below the
928 /// bottom sheet.
929 ///
930 /// Defaults to `Colors.black54` if not provided.
931 final Color? modalBarrierColor;
932
933 /// Specifies whether the bottom sheet will be dismissed
934 /// when user taps on the scrim.
935 ///
936 /// If true, the bottom sheet will be dismissed when user taps on the scrim.
937 ///
938 /// Defaults to true.
939 final bool isDismissible;
940
941 /// Specifies whether the bottom sheet can be dragged up and down
942 /// and dismissed by swiping downwards.
943 ///
944 /// If true, the bottom sheet can be dragged up and down and dismissed by
945 /// swiping downwards.
946 ///
947 /// This applies to the content below the drag handle, if showDragHandle is true.
948 ///
949 /// Defaults is true.
950 final bool enableDrag;
951
952 /// Specifies whether a drag handle is shown.
953 ///
954 /// The drag handle appears at the top of the bottom sheet. The default color is
955 /// [ColorScheme.onSurfaceVariant] with an opacity of 0.4 and can be customized
956 /// using dragHandleColor. The default size is `Size(32,4)` and can be customized
957 /// with dragHandleSize.
958 ///
959 /// If null, then the value of [BottomSheetThemeData.showDragHandle] is used. If
960 /// that is also null, defaults to false.
961 final bool? showDragHandle;
962
963 /// The animation controller that controls the bottom sheet's entrance and
964 /// exit animations.
965 ///
966 /// The BottomSheet widget will manipulate the position of this animation, it
967 /// is not just a passive observer.
968 final AnimationController? transitionAnimationController;
969
970 /// {@macro flutter.widgets.DisplayFeatureSubScreen.anchorPoint}
971 final Offset? anchorPoint;
972
973 /// Whether to avoid system intrusions on the top, left, and right.
974 ///
975 /// If true, a [SafeArea] is inserted to keep the bottom sheet away from
976 /// system intrusions at the top, left, and right sides of the screen.
977 ///
978 /// If false, the bottom sheet will extend through any system intrusions
979 /// at the top, left, and right.
980 ///
981 /// If false, then moreover [MediaQuery.removePadding] will be used
982 /// to remove top padding, so that a [SafeArea] widget inside the bottom
983 /// sheet will have no effect at the top edge. If this is undesired, consider
984 /// setting [useSafeArea] to true. Alternatively, wrap the [SafeArea] in a
985 /// [MediaQuery] that restates an ambient [MediaQueryData] from outside [builder].
986 ///
987 /// In either case, the bottom sheet extends all the way to the bottom of
988 /// the screen, including any system intrusions.
989 ///
990 /// The default is false.
991 final bool useSafeArea;
992
993 /// Used to override the modal bottom sheet animation duration and reverse
994 /// animation duration.
995 ///
996 /// If [AnimationStyle.duration] is provided, it will be used to override
997 /// the modal bottom sheet animation duration in the underlying
998 /// [BottomSheet.createAnimationController].
999 ///
1000 /// If [AnimationStyle.reverseDuration] is provided, it will be used to
1001 /// override the modal bottom sheet reverse animation duration in the
1002 /// underlying [BottomSheet.createAnimationController].
1003 ///
1004 /// To disable the modal bottom sheet animation, use [AnimationStyle.noAnimation].
1005 final AnimationStyle? sheetAnimationStyle;
1006
1007 /// {@template flutter.material.ModalBottomSheetRoute.barrierOnTapHint}
1008 /// The semantic hint text that informs users what will happen if they
1009 /// tap on the widget. Announced in the format of 'Double tap to ...'.
1010 ///
1011 /// If the field is null, the default hint will be used, which results in
1012 /// announcement of 'Double tap to activate'.
1013 /// {@endtemplate}
1014 ///
1015 /// See also:
1016 ///
1017 /// * [barrierDismissible], which controls the behavior of the barrier when
1018 /// tapped.
1019 /// * [ModalBarrier], which uses this field as onTapHint when it has an onTap action.
1020 final String? barrierOnTapHint;
1021
1022 final ValueNotifier<EdgeInsets> _clipDetailsNotifier = ValueNotifier<EdgeInsets>(EdgeInsets.zero);
1023
1024 @override
1025 void dispose() {
1026 _clipDetailsNotifier.dispose();
1027 super.dispose();
1028 }
1029
1030 /// Updates the details regarding how the [SemanticsNode.rect] (focus) of
1031 /// the barrier for this [ModalBottomSheetRoute] should be clipped.
1032 ///
1033 /// Returns true if the clipDetails did change and false otherwise.
1034 bool _didChangeBarrierSemanticsClip(EdgeInsets newClipDetails) {
1035 if (_clipDetailsNotifier.value == newClipDetails) {
1036 return false;
1037 }
1038 _clipDetailsNotifier.value = newClipDetails;
1039 return true;
1040 }
1041
1042 @override
1043 Duration get transitionDuration =>
1044 transitionAnimationController?.duration ??
1045 sheetAnimationStyle?.duration ??
1046 _bottomSheetEnterDuration;
1047
1048 @override
1049 Duration get reverseTransitionDuration =>
1050 transitionAnimationController?.reverseDuration ??
1051 transitionAnimationController?.duration ??
1052 sheetAnimationStyle?.reverseDuration ??
1053 _bottomSheetExitDuration;
1054
1055 @override
1056 bool get barrierDismissible => isDismissible;
1057
1058 @override
1059 final String? barrierLabel;
1060
1061 @override
1062 Color get barrierColor => modalBarrierColor ?? Colors.black54;
1063
1064 AnimationController? _animationController;
1065
1066 @override
1067 AnimationController createAnimationController() {
1068 assert(_animationController == null);
1069 if (transitionAnimationController != null) {
1070 _animationController = transitionAnimationController;
1071 willDisposeAnimationController = false;
1072 } else {
1073 _animationController = BottomSheet.createAnimationController(
1074 navigator!,
1075 sheetAnimationStyle: sheetAnimationStyle,
1076 );
1077 }
1078 return _animationController!;
1079 }
1080
1081 @override
1082 Widget buildPage(
1083 BuildContext context,
1084 Animation<double> animation,
1085 Animation<double> secondaryAnimation,
1086 ) {
1087 final Widget content = DisplayFeatureSubScreen(
1088 anchorPoint: anchorPoint,
1089 child: Builder(
1090 builder: (BuildContext context) {
1091 final BottomSheetThemeData sheetTheme = Theme.of(context).bottomSheetTheme;
1092 final BottomSheetThemeData defaults =
1093 Theme.of(context).useMaterial3
1094 ? _BottomSheetDefaultsM3(context)
1095 : const BottomSheetThemeData();
1096 return _ModalBottomSheet<T>(
1097 route: this,
1098 backgroundColor:
1099 backgroundColor ??
1100 sheetTheme.modalBackgroundColor ??
1101 sheetTheme.backgroundColor ??
1102 defaults.backgroundColor,
1103 elevation:
1104 elevation ??
1105 sheetTheme.modalElevation ??
1106 sheetTheme.elevation ??
1107 defaults.modalElevation,
1108 shape: shape,
1109 clipBehavior: clipBehavior,
1110 constraints: constraints,
1111 isScrollControlled: isScrollControlled,
1112 scrollControlDisabledMaxHeightRatio: scrollControlDisabledMaxHeightRatio,
1113 enableDrag: enableDrag,
1114 showDragHandle: showDragHandle ?? (enableDrag && (sheetTheme.showDragHandle ?? false)),
1115 );
1116 },
1117 ),
1118 );
1119
1120 final Widget bottomSheet =
1121 useSafeArea
1122 ? SafeArea(bottom: false, child: content)
1123 : MediaQuery.removePadding(context: context, removeTop: true, child: content);
1124
1125 return capturedThemes?.wrap(bottomSheet) ?? bottomSheet;
1126 }
1127
1128 @override
1129 Widget buildModalBarrier() {
1130 if (barrierColor.a != 0 && !offstage) {
1131 // changedInternalState is called if barrierColor or offstage updates
1132 assert(barrierColor != barrierColor.withValues(alpha: 0.0));
1133 final Animation<Color?> color = animation!.drive(
1134 ColorTween(
1135 begin: barrierColor.withValues(alpha: 0.0),
1136 end: barrierColor, // changedInternalState is called if barrierColor updates
1137 ).chain(
1138 CurveTween(curve: barrierCurve),
1139 ), // changedInternalState is called if barrierCurve updates
1140 );
1141 return AnimatedModalBarrier(
1142 color: color,
1143 dismissible:
1144 barrierDismissible, // changedInternalState is called if barrierDismissible updates
1145 semanticsLabel: barrierLabel, // changedInternalState is called if barrierLabel updates
1146 barrierSemanticsDismissible: semanticsDismissible,
1147 clipDetailsNotifier: _clipDetailsNotifier,
1148 semanticsOnTapHint: barrierOnTapHint,
1149 );
1150 } else {
1151 return ModalBarrier(
1152 dismissible:
1153 barrierDismissible, // changedInternalState is called if barrierDismissible updates
1154 semanticsLabel: barrierLabel, // changedInternalState is called if barrierLabel updates
1155 barrierSemanticsDismissible: semanticsDismissible,
1156 clipDetailsNotifier: _clipDetailsNotifier,
1157 semanticsOnTapHint: barrierOnTapHint,
1158 );
1159 }
1160 }
1161}
1162
1163/// Shows a modal Material Design bottom sheet.
1164///
1165/// {@macro flutter.material.ModalBottomSheetRoute}
1166///
1167/// {@macro flutter.widgets.RawDialogRoute}
1168///
1169/// The `context` argument is used to look up the [Navigator] and [Theme] for
1170/// the bottom sheet. It is only used when the method is called. Its
1171/// corresponding widget can be safely removed from the tree before the bottom
1172/// sheet is closed.
1173///
1174/// The `useRootNavigator` parameter ensures that the root navigator is used to
1175/// display the [BottomSheet] when set to `true`. This is useful in the case
1176/// that a modal [BottomSheet] needs to be displayed above all other content
1177/// but the caller is inside another [Navigator].
1178///
1179/// Returns a `Future` that resolves to the value (if any) that was passed to
1180/// [Navigator.pop] when the modal bottom sheet was closed.
1181///
1182/// The 'barrierLabel' parameter can be used to set a custom barrier label.
1183/// Will default to [MaterialLocalizations.modalBarrierDismissLabel] of context
1184/// if not set.
1185///
1186/// {@tool dartpad}
1187/// This example demonstrates how to use [showModalBottomSheet] to display a
1188/// bottom sheet that obscures the content behind it when a user taps a button.
1189/// It also demonstrates how to close the bottom sheet using the [Navigator]
1190/// when a user taps on a button inside the bottom sheet.
1191///
1192/// ** See code in examples/api/lib/material/bottom_sheet/show_modal_bottom_sheet.0.dart **
1193/// {@end-tool}
1194///
1195/// {@tool dartpad}
1196/// This sample shows the creation of [showModalBottomSheet], as described in:
1197/// https://m3.material.io/components/bottom-sheets/overview
1198///
1199/// ** See code in examples/api/lib/material/bottom_sheet/show_modal_bottom_sheet.1.dart **
1200/// {@end-tool}
1201///
1202/// The [sheetAnimationStyle] parameter is used to override the modal bottom sheet
1203/// animation duration and reverse animation duration.
1204///
1205/// The [requestFocus] parameter is used to specify whether the bottom sheet should
1206/// request focus when shown.
1207/// {@macro flutter.widgets.navigator.Route.requestFocus}
1208///
1209/// If [AnimationStyle.duration] is provided, it will be used to override
1210/// the modal bottom sheet animation duration in the underlying
1211/// [BottomSheet.createAnimationController].
1212///
1213/// If [AnimationStyle.reverseDuration] is provided, it will be used to
1214/// override the modal bottom sheet reverse animation duration in the
1215/// underlying [BottomSheet.createAnimationController].
1216///
1217/// To disable the bottom sheet animation, use [AnimationStyle.noAnimation].
1218///
1219/// {@tool dartpad}
1220/// This sample showcases how to override the [showModalBottomSheet] animation
1221/// duration and reverse animation duration using [AnimationStyle].
1222///
1223/// ** See code in examples/api/lib/material/bottom_sheet/show_modal_bottom_sheet.2.dart **
1224/// {@end-tool}
1225///
1226/// See also:
1227///
1228/// * [BottomSheet], which becomes the parent of the widget returned by the
1229/// function passed as the `builder` argument to [showModalBottomSheet].
1230/// * [showBottomSheet] and [ScaffoldState.showBottomSheet], for showing
1231/// non-modal bottom sheets.
1232/// * [DraggableScrollableSheet], creates a bottom sheet that grows
1233/// and then becomes scrollable once it reaches its maximum size.
1234/// * [DisplayFeatureSubScreen], which documents the specifics of how
1235/// [DisplayFeature]s can split the screen into sub-screens.
1236/// * The Material 2 spec at <https://m2.material.io/components/sheets-bottom>.
1237/// * The Material 3 spec at <https://m3.material.io/components/bottom-sheets/overview>.
1238/// * [AnimationStyle], which is used to override the modal bottom sheet
1239/// animation duration and reverse animation duration.
1240Future<T?> showModalBottomSheet<T>({
1241 required BuildContext context,
1242 required WidgetBuilder builder,
1243 Color? backgroundColor,
1244 String? barrierLabel,
1245 double? elevation,
1246 ShapeBorder? shape,
1247 Clip? clipBehavior,
1248 BoxConstraints? constraints,
1249 Color? barrierColor,
1250 bool isScrollControlled = false,
1251 double scrollControlDisabledMaxHeightRatio = _defaultScrollControlDisabledMaxHeightRatio,
1252 bool useRootNavigator = false,
1253 bool isDismissible = true,
1254 bool enableDrag = true,
1255 bool? showDragHandle,
1256 bool useSafeArea = false,
1257 RouteSettings? routeSettings,
1258 AnimationController? transitionAnimationController,
1259 Offset? anchorPoint,
1260 AnimationStyle? sheetAnimationStyle,
1261 bool? requestFocus,
1262}) {
1263 assert(debugCheckHasMediaQuery(context));
1264 assert(debugCheckHasMaterialLocalizations(context));
1265
1266 final NavigatorState navigator = Navigator.of(context, rootNavigator: useRootNavigator);
1267 final MaterialLocalizations localizations = MaterialLocalizations.of(context);
1268 return navigator.push(
1269 ModalBottomSheetRoute<T>(
1270 builder: builder,
1271 capturedThemes: InheritedTheme.capture(from: context, to: navigator.context),
1272 isScrollControlled: isScrollControlled,
1273 scrollControlDisabledMaxHeightRatio: scrollControlDisabledMaxHeightRatio,
1274 barrierLabel: barrierLabel ?? localizations.scrimLabel,
1275 barrierOnTapHint: localizations.scrimOnTapHint(localizations.bottomSheetLabel),
1276 backgroundColor: backgroundColor,
1277 elevation: elevation,
1278 shape: shape,
1279 clipBehavior: clipBehavior,
1280 constraints: constraints,
1281 isDismissible: isDismissible,
1282 modalBarrierColor: barrierColor ?? Theme.of(context).bottomSheetTheme.modalBarrierColor,
1283 enableDrag: enableDrag,
1284 showDragHandle: showDragHandle,
1285 settings: routeSettings,
1286 transitionAnimationController: transitionAnimationController,
1287 anchorPoint: anchorPoint,
1288 useSafeArea: useSafeArea,
1289 sheetAnimationStyle: sheetAnimationStyle,
1290 requestFocus: requestFocus,
1291 ),
1292 );
1293}
1294
1295/// Shows a Material Design bottom sheet in the nearest [Scaffold] ancestor. To
1296/// show a persistent bottom sheet, use the [Scaffold.bottomSheet].
1297///
1298/// Returns a controller that can be used to close and otherwise manipulate the
1299/// bottom sheet.
1300///
1301/// The optional [backgroundColor], [elevation], [shape], [clipBehavior],
1302/// [constraints] and [transitionAnimationController]
1303/// parameters can be passed in to customize the appearance and behavior of
1304/// persistent bottom sheets (see the documentation for these on [BottomSheet]
1305/// for more details).
1306///
1307/// The [enableDrag] parameter specifies whether the bottom sheet can be
1308/// dragged up and down and dismissed by swiping downwards.
1309///
1310/// The [sheetAnimationStyle] parameter is used to override the bottom sheet
1311/// animation duration and reverse animation duration.
1312///
1313/// If [AnimationStyle.duration] is provided, it will be used to override
1314/// the bottom sheet animation duration in the underlying
1315/// [BottomSheet.createAnimationController].
1316///
1317/// If [AnimationStyle.reverseDuration] is provided, it will be used to
1318/// override the bottom sheet reverse animation duration in the underlying
1319/// [BottomSheet.createAnimationController].
1320///
1321/// To disable the bottom sheet animation, use [AnimationStyle.noAnimation].
1322///
1323/// {@tool dartpad}
1324/// This sample showcases how to override the [showBottomSheet] animation
1325/// duration and reverse animation duration using [AnimationStyle].
1326///
1327/// ** See code in examples/api/lib/material/bottom_sheet/show_bottom_sheet.0.dart **
1328/// {@end-tool}
1329///
1330/// To rebuild the bottom sheet (e.g. if it is stateful), call
1331/// [PersistentBottomSheetController.setState] on the controller returned by
1332/// this method.
1333///
1334/// The new bottom sheet becomes a [LocalHistoryEntry] for the enclosing
1335/// [ModalRoute] and a back button is added to the app bar of the [Scaffold]
1336/// that closes the bottom sheet.
1337///
1338/// To create a persistent bottom sheet that is not a [LocalHistoryEntry] and
1339/// does not add a back button to the enclosing Scaffold's app bar, use the
1340/// [Scaffold.bottomSheet] constructor parameter.
1341///
1342/// A closely related widget is a modal bottom sheet, which is an alternative
1343/// to a menu or a dialog and prevents the user from interacting with the rest
1344/// of the app. Modal bottom sheets can be created and displayed with the
1345/// [showModalBottomSheet] function.
1346///
1347/// The `context` argument is used to look up the [Scaffold] for the bottom
1348/// sheet. It is only used when the method is called. Its corresponding widget
1349/// can be safely removed from the tree before the bottom sheet is closed.
1350///
1351/// See also:
1352///
1353/// * [BottomSheet], which becomes the parent of the widget returned by the
1354/// `builder`.
1355/// * [showModalBottomSheet], which can be used to display a modal bottom
1356/// sheet.
1357/// * [Scaffold.of], for information about how to obtain the [BuildContext].
1358/// * The Material 2 spec at <https://m2.material.io/components/sheets-bottom>.
1359/// * The Material 3 spec at <https://m3.material.io/components/bottom-sheets/overview>.
1360/// * [AnimationStyle], which is used to override the bottom sheet animation
1361/// duration and reverse animation duration.
1362PersistentBottomSheetController showBottomSheet({
1363 required BuildContext context,
1364 required WidgetBuilder builder,
1365 Color? backgroundColor,
1366 double? elevation,
1367 ShapeBorder? shape,
1368 Clip? clipBehavior,
1369 BoxConstraints? constraints,
1370 bool? enableDrag,
1371 bool? showDragHandle,
1372 AnimationController? transitionAnimationController,
1373 AnimationStyle? sheetAnimationStyle,
1374}) {
1375 assert(debugCheckHasScaffold(context));
1376
1377 return Scaffold.of(context).showBottomSheet(
1378 builder,
1379 backgroundColor: backgroundColor,
1380 elevation: elevation,
1381 shape: shape,
1382 clipBehavior: clipBehavior,
1383 constraints: constraints,
1384 enableDrag: enableDrag,
1385 showDragHandle: showDragHandle,
1386 transitionAnimationController: transitionAnimationController,
1387 sheetAnimationStyle: sheetAnimationStyle,
1388 );
1389}
1390
1391class _BottomSheetGestureDetector extends StatelessWidget {
1392 const _BottomSheetGestureDetector({
1393 required this.child,
1394 required this.onVerticalDragStart,
1395 required this.onVerticalDragUpdate,
1396 required this.onVerticalDragEnd,
1397 });
1398
1399 final Widget child;
1400 final GestureDragStartCallback onVerticalDragStart;
1401 final GestureDragUpdateCallback onVerticalDragUpdate;
1402 final GestureDragEndCallback onVerticalDragEnd;
1403
1404 @override
1405 Widget build(BuildContext context) {
1406 return RawGestureDetector(
1407 excludeFromSemantics: true,
1408 gestures: <Type, GestureRecognizerFactory<GestureRecognizer>>{
1409 VerticalDragGestureRecognizer:
1410 GestureRecognizerFactoryWithHandlers<VerticalDragGestureRecognizer>(
1411 () => VerticalDragGestureRecognizer(debugOwner: this),
1412 (VerticalDragGestureRecognizer instance) {
1413 instance
1414 ..onStart = onVerticalDragStart
1415 ..onUpdate = onVerticalDragUpdate
1416 ..onEnd = onVerticalDragEnd
1417 ..onlyAcceptDragOnThreshold = true;
1418 },
1419 ),
1420 },
1421 child: child,
1422 );
1423 }
1424}
1425
1426// BEGIN GENERATED TOKEN PROPERTIES - BottomSheet
1427
1428// Do not edit by hand. The code between the "BEGIN GENERATED" and
1429// "END GENERATED" comments are generated from data in the Material
1430// Design token database by the script:
1431// dev/tools/gen_defaults/bin/gen_defaults.dart.
1432
1433// dart format off
1434class _BottomSheetDefaultsM3 extends BottomSheetThemeData {
1435 _BottomSheetDefaultsM3(this.context)
1436 : super(
1437 elevation: 1.0,
1438 modalElevation: 1.0,
1439 shape: const RoundedRectangleBorder(borderRadius: BorderRadius.vertical(top: Radius.circular(28.0))),
1440 constraints: const BoxConstraints(maxWidth: 640),
1441 );
1442
1443 final BuildContext context;
1444 late final ColorScheme _colors = Theme.of(context).colorScheme;
1445
1446 @override
1447 Color? get backgroundColor => _colors.surfaceContainerLow;
1448
1449 @override
1450 Color? get surfaceTintColor => Colors.transparent;
1451
1452 @override
1453 Color? get shadowColor => Colors.transparent;
1454
1455 @override
1456 Color? get dragHandleColor => _colors.onSurfaceVariant;
1457
1458 @override
1459 Size? get dragHandleSize => const Size(32, 4);
1460
1461 @override
1462 BoxConstraints? get constraints => const BoxConstraints(maxWidth: 640.0);
1463}
1464// dart format on
1465
1466// END GENERATED TOKEN PROPERTIES - BottomSheet
1467

Provided by KDAB

Privacy Policy
Learn more about Flutter for embedded and desktop on industrialflutter.com