
上代码(MyModalBottomSheetRoute<T>
):
点击查看代码
class MyModalBottomSheetRoute extends PopupRoute {
MyModalBottomSheetRoute({
this.builder,
this.theme,
this.barrierLabel,
this.backgroundColor,
this.elevation,
this.shape,
this.clipBehavior,
this.isDismissible = true,
this.isBlurred = false,
@required this.isScrollControlled,
RouteSettings settings,
}) : assert(isScrollControlled != null),
assert(isDismissible != null),
super(
settings: settings,
filter: isBlurred ? ImageFilter.blur(sigmaX: 20, sigmaY: 20) : null);
final WidgetBuilder builder;
final ThemeData theme;
final bool isScrollControlled;
final Color backgroundColor;
final double elevation;
final ShapeBorder shape;
final Clip clipBehavior;
final bool isDismissible;
final bool isBlurred;
@override
Duration get transitionDuration => Duration(milliseconds: 200);
@override
bool get barrierDismissible => isDismissible;
@override
final String barrierLabel;
@override
Color get barrierColor => Colors.white.withOpacity(0.7);
AnimationController _animationController;
@override
AnimationController createAnimationController() {
assert(_animationController == null);
_animationController =
BottomSheet.createAnimationController(navigator.overlay);
return _animationController;
}
@override
Widget buildPage(BuildContext context, Animation animation,
Animation 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 = MediaQuery.removePadding(
context: context,
removeTop: true,
child: _ModalBottomSheet(
route: this,
backgroundColor: backgroundColor ??
sheetTheme?.modalBackgroundColor ??
sheetTheme?.backgroundColor,
elevation:
elevation ?? sheetTheme?.modalElevation ?? sheetTheme?.elevation,
shape: shape,
clipBehavior: clipBehavior,
isScrollControlled: isScrollControlled,
),
);
if (theme != null) bottomSheet = Theme(data: theme, child: bottomSheet);
return bottomSheet;
}
}
class _ModalBottomSheet extends StatefulWidget {
const _ModalBottomSheet({
Key key,
this.route,
this.backgroundColor,
this.elevation,
this.shape,
this.clipBehavior,
this.isScrollControlled = false,
}) : assert(isScrollControlled != null),
super(key: key);
final MyModalBottomSheetRoute route;
final bool isScrollControlled;
final Color backgroundColor;
final double elevation;
final ShapeBorder shape;
final Clip clipBehavior;
@override
_ModalBottomSheetState createState() => _ModalBottomSheetState();
}
class _ModalBottomSheetState extends State<_ModalBottomSheet> {
String _getRouteLabel(MaterialLocalizations localizations) {
switch (Theme.of(context).platform) {
case TargetPlatform.iOS:
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,
),
),
),
);
},
);
}
}
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 MyModalBottomSheetRoute extends PopupRoute {
MyModalBottomSheetRoute({
this.builder,
this.theme,
this.barrierLabel,
this.backgroundColor,
this.elevation,
this.shape,
this.clipBehavior,
this.isDismissible = true,
this.isBlurred = false,
@required this.isScrollControlled,
RouteSettings settings,
}) : assert(isScrollControlled != null),
assert(isDismissible != null),
super(
settings: settings,
filter: isBlurred ? ImageFilter.blur(sigmaX: 20, sigmaY: 20) : null);
final WidgetBuilder builder;
final ThemeData theme;
final bool isScrollControlled;
final Color backgroundColor;
final double elevation;
final ShapeBorder shape;
final Clip clipBehavior;
final bool isDismissible;
final bool isBlurred;
@override
Duration get transitionDuration => Duration(milliseconds: 200);
@override
bool get barrierDismissible => isDismissible;
@override
final String barrierLabel;
@override
Color get barrierColor => Colors.white.withOpacity(0.7);
AnimationController _animationController;
@override
AnimationController createAnimationController() {
assert(_animationController == null);
_animationController =
BottomSheet.createAnimationController(navigator.overlay);
return _animationController;
}
@override
Widget buildPage(BuildContext context, Animation animation,
Animation 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 = MediaQuery.removePadding(
context: context,
removeTop: true,
child: _ModalBottomSheet(
route: this,
backgroundColor: backgroundColor ??
sheetTheme?.modalBackgroundColor ??
sheetTheme?.backgroundColor,
elevation:
elevation ?? sheetTheme?.modalElevation ?? sheetTheme?.elevation,
shape: shape,
clipBehavior: clipBehavior,
isScrollControlled: isScrollControlled,
),
);
if (theme != null) bottomSheet = Theme(data: theme, child: bottomSheet);
return bottomSheet;
}
}
class _ModalBottomSheet extends StatefulWidget {
const _ModalBottomSheet({
Key key,
this.route,
this.backgroundColor,
this.elevation,
this.shape,
this.clipBehavior,
this.isScrollControlled = false,
}) : assert(isScrollControlled != null),
super(key: key);
final MyModalBottomSheetRoute route;
final bool isScrollControlled;
final Color backgroundColor;
final double elevation;
final ShapeBorder shape;
final Clip clipBehavior;
@override
_ModalBottomSheetState createState() => _ModalBottomSheetState();
}
class _ModalBottomSheetState extends State<_ModalBottomSheet> {
String _getRouteLabel(MaterialLocalizations localizations) {
switch (Theme.of(context).platform) {
case TargetPlatform.iOS:
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,
),
),
),
);
},
);
}
}
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;
}
}
MyBlurDialogRoute<T>
:
点击查看代码
import 'dart:ui';
import 'package:flutter/material.dart';
/// 模糊背景 Dialog
class MyBlurDialogRoute extends PopupRoute {
MyBlurDialogRoute({
@required RoutePageBuilder pageBuilder,
bool barrierDismissible = true,
String barrierLabel,
Duration transitionDuration = const Duration(milliseconds: 200),
RouteTransitionsBuilder transitionBuilder,
RouteSettings settings,
}) : assert(barrierDismissible != null),
_pageBuilder = pageBuilder,
_barrierDismissible = barrierDismissible,
_barrierLabel = barrierLabel,
_transitionDuration = transitionDuration,
_transitionBuilder = transitionBuilder,
// super(settings: settings, filter: BlurSettings.blurFilter);
super(
settings: settings,
filter: ImageFilter.blur(sigmaX: 60, sigmaY: 60));
final RoutePageBuilder _pageBuilder;
@override
bool get barrierDismissible => _barrierDismissible;
final bool _barrierDismissible;
@override
String get barrierLabel => _barrierLabel;
final String _barrierLabel;
@override
Color get barrierColor => _barrierColor;
final Color _barrierColor = Colors.white.withOpacity(0.05);
@override
Duration get transitionDuration => _transitionDuration;
final Duration _transitionDuration;
final RouteTransitionsBuilder _transitionBuilder;
@override
Widget buildPage(BuildContext context, Animation animation,
Animation secondaryAnimation) {
return Semantics(
child: _pageBuilder(context, animation, secondaryAnimation),
scopesRoute: true,
explicitChildNodes: true,
);
}
@override
Widget buildTransitions(BuildContext context, Animation animation,
Animation secondaryAnimation, Widget child) {
if (_transitionBuilder == null) {
return FadeTransition(
opacity: CurvedAnimation(
parent: animation,
curve: Curves.linear,
),
child: child);
} // Some default transition
return _transitionBuilder(context, animation, secondaryAnimation, child);
}
}
调用:
点击查看代码
static Future showBlurDialog(
final BuildContext context, final WidgetBuilder builder,
{final bool barrierDismissible = true}) {
final ThemeData theme = Theme.of(context, shadowThemeOnly: true);
return Navigator.of(context, rootNavigator: true)
.push(MyBlurDialogRoute(
pageBuilder: (BuildContext buildContext, Animation animation,
Animation secondaryAnimation) {
final Widget pageChild = Builder(builder: builder);
return SafeArea(
child: Builder(builder: (BuildContext context) {
return theme != null
? Theme(data: theme, child: pageChild)
: pageChild;
}),
);
},
barrierDismissible: barrierDismissible,
barrierLabel: MaterialLocalizations.of(context).modalBarrierDismissLabel,
transitionDuration: const Duration(milliseconds: 150),
));
}
原理:
在 PopupRoute<T>
的构造中, ui.ImageFilter filter
参数用于控制 Popup 背景。
我们传入 ImageFilter.blur(sigmaX: 60, sigmaY: 60)
,便会导致 dialog、bottomsheet背景变模糊。
除此之外,可以通过:
@override
Color get barrierColor => _barrierColor;
控制 Popup 背景色,在本案例中:
final Color _barrierColor = Colors.white.withOpacity(0.05);
我们设置白色加0.05 不透明度,以便显示白色带透明的模糊效果。
Comments | NOTHING