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

Provided by KDAB

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