Not a member of Pastebin yet?
Sign Up,
it unlocks many cool features!
- import 'dart:async';
- import 'package:flutter/foundation.dart';
- import 'package:flutter/material.dart';
- import 'package:flutter/scheduler.dart';
- import 'package:flutter/widgets.dart';
- const Duration _bottomSheetDuration = Duration(milliseconds: 200);
- const double _minFlingVelocity = 700.0;
- const double _closeProgressThreshold = 0.5;
- /// A material design bottom sheet.
- ///
- /// There are two kinds of bottom sheets in material design:
- ///
- /// * _Persistent_. A persistent bottom sheet shows information that
- /// supplements the primary content of the app. A persistent bottom sheet
- /// remains visible even when the user interacts with other parts of the app.
- /// Persistent bottom sheets can be created and displayed with the
- /// [ScaffoldState.showBottomSheet] function or by specifying the
- /// [Scaffold.bottomSheet] constructor parameter.
- ///
- /// * _Modal_. A modal bottom sheet is an alternative to a menu or a dialog and
- /// prevents the user from interacting with the rest of the app. Modal bottom
- /// sheets can be created and displayed with the [showModalBottomSheet]
- /// function.
- ///
- /// The [BottomSheet] widget itself is rarely used directly. Instead, prefer to
- /// create a persistent bottom sheet with [ScaffoldState.showBottomSheet] or
- /// [Scaffold.bottomSheet], and a modal bottom sheet with [showModalBottomSheet].
- ///
- /// See also:
- ///
- /// * [showBottomSheet] and [ScaffoldState.showBottomSheet], for showing
- /// non-modal "persistent" bottom sheets.
- /// * [showModalBottomSheet], which can be used to display a modal bottom
- /// sheet.
- /// * <https://material.io/design/components/sheets-bottom.html>
- class BottomSheet extends StatefulWidget {
- /// Creates a bottom sheet.
- ///
- /// Typically, bottom sheets are created implicitly by
- /// [ScaffoldState.showBottomSheet], for persistent bottom sheets, or by
- /// [showModalBottomSheet], for modal bottom sheets.
- const BottomSheet({
- Key key,
- this.animationController,
- this.enableDrag = true,
- this.backgroundColor,
- this.elevation,
- this.shape,
- this.clipBehavior,
- @required this.onClosing,
- @required this.builder,
- }) : assert(enableDrag != null),
- assert(onClosing != null),
- assert(builder != null),
- assert(elevation == null || elevation >= 0.0),
- super(key: key);
- /// The animation controller that controls the bottom sheet's entrance and
- /// exit animations.
- ///
- /// The BottomSheet widget will manipulate the position of this animation, it
- /// is not just a passive observer.
- final AnimationController animationController;
- /// Called when the bottom sheet begins to close.
- ///
- /// A bottom sheet might be prevented from closing (e.g., by user
- /// interaction) even after this callback is called. For this reason, this
- /// callback might be call multiple times for a given bottom sheet.
- final VoidCallback onClosing;
- /// A builder for the contents of the sheet.
- ///
- /// The bottom sheet will wrap the widget produced by this builder in a
- /// [Material] widget.
- final WidgetBuilder builder;
- /// If true, the bottom sheet can be dragged up and down and dismissed by
- /// swiping downwards.
- ///
- /// Default is true.
- final bool enableDrag;
- /// The bottom sheet's background color.
- ///
- /// Defines the bottom sheet's [Material.color].
- ///
- /// Defaults to null and falls back to [Material]'s default.
- final Color backgroundColor;
- /// The z-coordinate at which to place this material relative to its parent.
- ///
- /// This controls the size of the shadow below the material.
- ///
- /// Defaults to 0. The value is non-negative.
- final double elevation;
- /// The shape of the bottom sheet.
- ///
- /// Defines the bottom sheet's [Material.shape].
- ///
- /// Defaults to null and falls back to [Material]'s default.
- final ShapeBorder shape;
- /// {@macro flutter.widgets.Clip}
- ///
- /// Defines the bottom sheet's [Material.clipBehavior].
- ///
- /// Use this property to enable clipping of content when the bottom sheet has
- /// a custom [shape] and the content can extend past this shape. For example,
- /// a bottom sheet with rounded corners and an edge-to-edge [Image] at the
- /// top.
- ///
- /// If this property is null then [ThemeData.bottomSheetTheme.clipBehavior] is
- /// used. If that's null then the behavior will be [Clip.none].
- final Clip clipBehavior;
- @override
- _BottomSheetState createState() => _BottomSheetState();
- /// Creates an [AnimationController] suitable for a
- /// [BottomSheet.animationController].
- ///
- /// This API available as a convenience for a Material compliant bottom sheet
- /// animation. If alternative animation durations are required, a different
- /// animation controller could be provided.
- static AnimationController createAnimationController(TickerProvider vsync) {
- return AnimationController(
- duration: _bottomSheetDuration,
- debugLabel: 'BottomSheet',
- vsync: vsync,
- );
- }
- }
- class _BottomSheetState extends State<BottomSheet> {
- final GlobalKey _childKey = GlobalKey(debugLabel: 'BottomSheet child');
- double get _childHeight {
- final RenderBox renderBox = _childKey.currentContext.findRenderObject() as RenderBox;
- return renderBox.size.height;
- }
- bool get _dismissUnderway => widget.animationController.status == AnimationStatus.reverse;
- void _handleDragUpdate(DragUpdateDetails details) {
- assert(widget.enableDrag);
- if (_dismissUnderway) return;
- widget.animationController.value -= details.primaryDelta / (_childHeight ?? details.primaryDelta);
- }
- void _handleDragEnd(DragEndDetails details) {
- assert(widget.enableDrag);
- if (_dismissUnderway) return;
- if (details.velocity.pixelsPerSecond.dy > _minFlingVelocity) {
- final double flingVelocity = -details.velocity.pixelsPerSecond.dy / _childHeight;
- if (widget.animationController.value > 0.0) {
- widget.animationController.fling(velocity: flingVelocity);
- }
- if (flingVelocity < 0.0) {
- widget.onClosing();
- }
- } else if (widget.animationController.value < _closeProgressThreshold) {
- if (widget.animationController.value > 0.0) widget.animationController.fling(velocity: -1.0);
- widget.onClosing();
- } else {
- widget.animationController.forward();
- }
- }
- bool extentChanged(DraggableScrollableNotification notification) {
- if (notification.extent == notification.minExtent) {
- widget.onClosing();
- }
- return false;
- }
- @override
- Widget build(BuildContext context) {
- final BottomSheetThemeData bottomSheetTheme = Theme.of(context).bottomSheetTheme;
- final Color color = widget.backgroundColor ?? bottomSheetTheme.backgroundColor;
- final double elevation = widget.elevation ?? bottomSheetTheme.elevation ?? 0;
- final ShapeBorder shape = widget.shape ?? bottomSheetTheme.shape;
- final Clip clipBehavior = widget.clipBehavior ?? bottomSheetTheme.clipBehavior ?? Clip.none;
- final Widget bottomSheet = Material(
- key: _childKey,
- color: color,
- elevation: elevation,
- shape: shape,
- clipBehavior: clipBehavior,
- child: NotificationListener<DraggableScrollableNotification>(
- onNotification: extentChanged,
- child: widget.builder(context),
- ),
- );
- return !widget.enableDrag
- ? bottomSheet
- : GestureDetector(
- onVerticalDragUpdate: _handleDragUpdate,
- onVerticalDragEnd: _handleDragEnd,
- child: bottomSheet,
- excludeFromSemantics: true,
- );
- }
- }
- // PERSISTENT BOTTOM SHEETS
- // See scaffold.dart
- // MODAL BOTTOM SHEETS
- class _ModalBottomSheetLayout extends SingleChildLayoutDelegate {
- _ModalBottomSheetLayout(this.progress, this.isScrollControlled);
- final double progress;
- final bool isScrollControlled;
- @override
- BoxConstraints getConstraintsForChild(BoxConstraints constraints) {
- return BoxConstraints(
- minWidth: constraints.maxWidth,
- maxWidth: constraints.maxWidth,
- minHeight: 0.0,
- maxHeight: isScrollControlled ? constraints.maxHeight : constraints.maxHeight * 9.0 / 16.0,
- );
- }
- @override
- Offset getPositionForChild(Size size, Size childSize) {
- return Offset(0.0, size.height - childSize.height * progress);
- }
- @override
- bool shouldRelayout(_ModalBottomSheetLayout oldDelegate) {
- return progress != oldDelegate.progress;
- }
- }
- class _ModalBottomSheet<T> extends StatefulWidget {
- const _ModalBottomSheet({
- Key key,
- this.route,
- this.backgroundColor,
- this.elevation,
- this.shape,
- this.clipBehavior,
- this.isScrollControlled = false,
- this.enableDrag = true,
- }) : assert(isScrollControlled != null),
- assert(enableDrag != null),
- super(key: key);
- final _ModalBottomSheetRoute<T> route;
- final bool isScrollControlled;
- final Color backgroundColor;
- final double elevation;
- final ShapeBorder shape;
- final Clip clipBehavior;
- final bool enableDrag;
- @override
- _ModalBottomSheetState<T> createState() => _ModalBottomSheetState<T>();
- }
- class _ModalBottomSheetState<T> extends State<_ModalBottomSheet<T>> {
- String _getRouteLabel(MaterialLocalizations localizations) {
- switch (Theme.of(context).platform) {
- case TargetPlatform.iOS:
- case TargetPlatform.macOS:
- return '';
- case TargetPlatform.android:
- case TargetPlatform.fuchsia:
- return localizations.dialogLabel;
- }
- return null;
- }
- @override
- Widget build(BuildContext context) {
- assert(debugCheckHasMediaQuery(context));
- assert(debugCheckHasMaterialLocalizations(context));
- final MediaQueryData mediaQuery = MediaQuery.of(context);
- final MaterialLocalizations localizations = MaterialLocalizations.of(context);
- final String routeLabel = _getRouteLabel(localizations);
- return AnimatedBuilder(
- animation: widget.route.animation,
- builder: (BuildContext context, Widget child) {
- // Disable the initial animation when accessible navigation is on so
- // that the semantics are added to the tree at the correct time.
- final double animationValue = mediaQuery.accessibleNavigation ? 1.0 : widget.route.animation.value;
- return Semantics(
- scopesRoute: true,
- namesRoute: true,
- label: routeLabel,
- explicitChildNodes: true,
- child: ClipRect(
- child: CustomSingleChildLayout(
- delegate: _ModalBottomSheetLayout(animationValue, widget.isScrollControlled),
- child: BottomSheet(
- animationController: widget.route._animationController,
- onClosing: () {
- if (widget.route.isCurrent) {
- Navigator.pop(context);
- }
- },
- builder: widget.route.builder,
- backgroundColor: widget.backgroundColor,
- elevation: widget.elevation,
- shape: widget.shape,
- clipBehavior: widget.clipBehavior,
- enableDrag: widget.enableDrag,
- ),
- ),
- ),
- );
- },
- );
- }
- }
- class _ModalBottomSheetRoute<T> extends PopupRoute<T> {
- _ModalBottomSheetRoute({
- this.builder,
- this.theme,
- this.barrierLabel,
- this.backgroundColor,
- this.elevation,
- this.shape,
- this.clipBehavior,
- this.modalBarrierColor,
- this.isDismissible = true,
- this.enableDrag = true,
- @required this.isScrollControlled,
- RouteSettings settings,
- }) : assert(isScrollControlled != null),
- assert(isDismissible != null),
- assert(enableDrag != null),
- super(settings: settings);
- final WidgetBuilder builder;
- final ThemeData theme;
- final bool isScrollControlled;
- final Color backgroundColor;
- final double elevation;
- final ShapeBorder shape;
- final Clip clipBehavior;
- final Color modalBarrierColor;
- final bool isDismissible;
- final bool enableDrag;
- @override
- Duration get transitionDuration => _bottomSheetDuration;
- @override
- bool get barrierDismissible => isDismissible;
- @override
- final String barrierLabel;
- @override
- Color get barrierColor => modalBarrierColor ?? Colors.black54;
- AnimationController _animationController;
- @override
- AnimationController createAnimationController() {
- assert(_animationController == null);
- _animationController = BottomSheet.createAnimationController(navigator.overlay);
- return _animationController;
- }
- @override
- Widget buildPage(BuildContext context, Animation<double> animation, Animation<double> secondaryAnimation) {
- final BottomSheetThemeData sheetTheme = theme?.bottomSheetTheme ?? Theme.of(context).bottomSheetTheme;
- // By definition, the bottom sheet is aligned to the bottom of the page
- // and isn't exposed to the top padding of the MediaQuery.
- Widget bottomSheet = SafeArea(
- child: _ModalBottomSheet<T>(
- route: this,
- backgroundColor: backgroundColor ?? sheetTheme?.modalBackgroundColor ?? sheetTheme?.backgroundColor,
- elevation: elevation ?? sheetTheme?.modalElevation ?? sheetTheme?.elevation,
- shape: shape,
- clipBehavior: clipBehavior,
- isScrollControlled: isScrollControlled,
- enableDrag: enableDrag,
- ),
- );
- if (theme != null) bottomSheet = Theme(data: theme, child: bottomSheet);
- return bottomSheet;
- }
- }
- /// Shows a modal material design bottom sheet.
- ///
- /// A modal bottom sheet is an alternative to a menu or a dialog and prevents
- /// the user from interacting with the rest of the app.
- ///
- /// A closely related widget is a persistent bottom sheet, which shows
- /// information that supplements the primary content of the app without
- /// preventing the use from interacting with the app. Persistent bottom sheets
- /// can be created and displayed with the [showBottomSheet] function or the
- /// [ScaffoldState.showBottomSheet] method.
- ///
- /// The `context` argument is used to look up the [Navigator] and [Theme] for
- /// the bottom sheet. It is only used when the method is called. Its
- /// corresponding widget can be safely removed from the tree before the bottom
- /// sheet is closed.
- ///
- /// The `isScrollControlled` parameter specifies whether this is a route for
- /// a bottom sheet that will utilize [DraggableScrollableSheet]. If you wish
- /// to have a bottom sheet that has a scrollable child such as a [ListView] or
- /// a [GridView] and have the bottom sheet be draggable, you should set this
- /// parameter to true.
- ///
- /// The `useRootNavigator` parameter ensures that the root navigator is used to
- /// display the [BottomSheet] when set to `true`. This is useful in the case
- /// that a modal [BottomSheet] needs to be displayed above all other content
- /// but the caller is inside another [Navigator].
- ///
- /// The [isDismissible] parameter specifies whether the bottom sheet will be
- /// dismissed when user taps on the scrim.
- ///
- /// The [enableDrag] parameter specifies whether the bottom sheet can be
- /// dragged up and down and dismissed by swiping downards.
- ///
- /// The optional [backgroundColor], [elevation], [shape], and [clipBehavior]
- /// parameters can be passed in to customize the appearance and behavior of
- /// modal bottom sheets.
- ///
- /// Returns a `Future` that resolves to the value (if any) that was passed to
- /// [Navigator.pop] when the modal bottom sheet was closed.
- ///
- /// {@animation 350 622 https://flutter.github.io/assets-for-api-docs/assets/material/show_modal_bottom_sheet.mp4}
- ///
- /// {@tool sample --template=stateless_widget_scaffold}
- ///
- /// This example demonstrates how to use `showModalBottomSheet` to display a
- /// bottom sheet that obscures the content behind it when a user taps a button.
- /// It also demonstrates how to close the bottom sheet using the [Navigator]
- /// when a user taps on a button inside the bottom sheet.
- ///
- /// ```dart
- /// Widget build(BuildContext context) {
- /// return Center(
- /// child: RaisedButton(
- /// child: const Text('showModalBottomSheet'),
- /// onPressed: () {
- /// showModalBottomSheet<void>(
- /// context: context,
- /// builder: (BuildContext context) {
- /// return Container(
- /// height: 200,
- /// color: Colors.amber,
- /// child: Center(
- /// child: Column(
- /// mainAxisAlignment: MainAxisAlignment.center,
- /// mainAxisSize: MainAxisSize.min,
- /// children: <Widget>[
- /// const Text('Modal BottomSheet'),
- /// RaisedButton(
- /// child: const Text('Close BottomSheet'),
- /// onPressed: () => Navigator.pop(context),
- /// )
- /// ],
- /// ),
- /// ),
- /// );
- /// },
- /// );
- /// },
- /// ),
- /// );
- /// }
- /// ```
- /// {@end-tool}
- /// See also:
- ///
- /// * [BottomSheet], which becomes the parent of the widget returned by the
- /// function passed as the `builder` argument to [showModalBottomSheet].
- /// * [showBottomSheet] and [ScaffoldState.showBottomSheet], for showing
- /// non-modal bottom sheets.
- /// * [DraggableScrollableSheet], which allows you to create a bottom sheet
- /// that grows and then becomes scrollable once it reaches its maximum size.
- /// * <https://material.io/design/components/sheets-bottom.html#modal-bottom-sheet>
- Future<T> showModalBottomSheet<T>({
- @required BuildContext context,
- @required WidgetBuilder builder,
- Color backgroundColor,
- double elevation,
- ShapeBorder shape,
- Clip clipBehavior,
- Color barrierColor,
- bool isScrollControlled = false,
- bool useRootNavigator = false,
- bool isDismissible = true,
- bool enableDrag = true,
- }) {
- assert(context != null);
- assert(builder != null);
- assert(isScrollControlled != null);
- assert(useRootNavigator != null);
- assert(isDismissible != null);
- assert(enableDrag != null);
- assert(debugCheckHasMediaQuery(context));
- assert(debugCheckHasMaterialLocalizations(context));
- return Navigator.of(context, rootNavigator: useRootNavigator).push(_ModalBottomSheetRoute<T>(
- builder: builder,
- theme: Theme.of(context, shadowThemeOnly: true),
- isScrollControlled: isScrollControlled,
- barrierLabel: MaterialLocalizations.of(context).modalBarrierDismissLabel,
- backgroundColor: backgroundColor,
- elevation: elevation,
- shape: shape,
- clipBehavior: clipBehavior,
- isDismissible: isDismissible,
- modalBarrierColor: barrierColor,
- enableDrag: enableDrag,
- ));
- }
- /// Shows a material design bottom sheet in the nearest [Scaffold] ancestor. If
- /// you wish to show a persistent bottom sheet, use [Scaffold.bottomSheet].
- ///
- /// Returns a controller that can be used to close and otherwise manipulate the
- /// bottom sheet.
- ///
- /// The optional [backgroundColor], [elevation], [shape], and [clipBehavior]
- /// parameters can be passed in to customize the appearance and behavior of
- /// persistent bottom sheets.
- ///
- /// To rebuild the bottom sheet (e.g. if it is stateful), call
- /// [PersistentBottomSheetController.setState] on the controller returned by
- /// this method.
- ///
- /// The new bottom sheet becomes a [LocalHistoryEntry] for the enclosing
- /// [ModalRoute] and a back button is added to the app bar of the [Scaffold]
- /// that closes the bottom sheet.
- ///
- /// To create a persistent bottom sheet that is not a [LocalHistoryEntry] and
- /// does not add a back button to the enclosing Scaffold's app bar, use the
- /// [Scaffold.bottomSheet] constructor parameter.
- ///
- /// A closely related widget is a modal bottom sheet, which is an alternative
- /// to a menu or a dialog and prevents the user from interacting with the rest
- /// of the app. Modal bottom sheets can be created and displayed with the
- /// [showModalBottomSheet] function.
- ///
- /// The `context` argument is used to look up the [Scaffold] for the bottom
- /// sheet. It is only used when the method is called. Its corresponding widget
- /// can be safely removed from the tree before the bottom sheet is closed.
- ///
- /// See also:
- ///
- /// * [BottomSheet], which becomes the parent of the widget returned by the
- /// `builder`.
- /// * [showModalBottomSheet], which can be used to display a modal bottom
- /// sheet.
- /// * [Scaffold.of], for information about how to obtain the [BuildContext].
- /// * <https://material.io/design/components/sheets-bottom.html#standard-bottom-sheet>
- PersistentBottomSheetController<T> showBottomSheet<T>({
- @required BuildContext context,
- @required WidgetBuilder builder,
- Color backgroundColor,
- double elevation,
- ShapeBorder shape,
- Clip clipBehavior,
- }) {
- assert(context != null);
- assert(builder != null);
- assert(debugCheckHasScaffold(context));
- return Scaffold.of(context).showBottomSheet<T>(
- builder,
- backgroundColor: backgroundColor,
- elevation: elevation,
- shape: shape,
- clipBehavior: clipBehavior,
- );
- }
Advertisement
Add Comment
Please, Sign In to add comment