import 'dart:async'; import 'package:flutter/material.dart'; /// Below is the usage for this function, you'll only have to import this file /// [radius] takes a double and will be the radius to the rounded corners of this modal /// [color] will color the modal itself, the default being `Colors.white` /// [builder] takes the content of the modal, if you're using [Column] /// or a similar widget, remember to set `mainAxisSize: MainAxisSize.min` /// so it will only take the needed space. /// /// ```dart /// showRoundedModalBottomSheet( /// context: context, /// radius: 10.0, // This is the default /// color: Colors.white, // Also default /// builder: (context) => ???, /// ); /// ``` Future<T> showRoundedModalBottomSheet<T>({ @required BuildContext context, @required WidgetBuilder builder, Color color = Colors.white, double radius = 10.0, }) { assert(context != null); assert(builder != null); assert(radius != null && radius > 0.0); assert(color != null && color != Colors.transparent); return Navigator.push<T>( context, _RoundedCornerModalRoute<T>( builder: builder, color: color, radius: radius, barrierLabel: MaterialLocalizations.of(context).modalBarrierDismissLabel, )); } class _RoundedModalBottomSheetLayout extends SingleChildLayoutDelegate { _RoundedModalBottomSheetLayout(this.progress); final double progress; @override BoxConstraints getConstraintsForChild(BoxConstraints constraints) { return new BoxConstraints( minWidth: constraints.maxWidth, maxWidth: constraints.maxWidth, minHeight: 0.0, maxHeight: constraints.maxHeight * 9.0 / 16.0); } @override Offset getPositionForChild(Size size, Size childSize) { return new Offset(0.0, size.height - childSize.height * progress); } @override bool shouldRelayout(_RoundedModalBottomSheetLayout oldDelegate) { return progress != oldDelegate.progress; } } class _RoundedCornerModalRoute<T> extends PopupRoute<T> { _RoundedCornerModalRoute({ this.builder, this.barrierLabel, this.color, this.radius, RouteSettings settings, }) : super(settings: settings); final WidgetBuilder builder; final double radius; final Color color; @override Color get barrierColor => Colors.black54; @override bool get barrierDismissible => true; @override String barrierLabel; 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) { return MediaQuery.removePadding( context: context, removeTop: true, child: Theme( data: Theme.of(context).copyWith(canvasColor: Colors.transparent), child: AnimatedBuilder( animation: animation, builder: (context, child) => CustomSingleChildLayout( delegate: _RoundedModalBottomSheetLayout(animation.value), child: BottomSheet( animationController: _animationController, onClosing: () => Navigator.pop(context), builder: (context) => Container( decoration: BoxDecoration( color: this.color, borderRadius: BorderRadius.only( topLeft: Radius.circular(this.radius), topRight: Radius.circular(this.radius), ), ), child: SafeArea(child: Builder(builder: this.builder)), ), ), ), ), ), ); } @override bool get maintainState => false; @override bool get opaque => false; @override Duration get transitionDuration => Duration(milliseconds: 200); }