如何在Flutter Navigator 2.0中实现带透明背景的页面?
问题
我正尝试通过Navigator 2.0 API完全控制路由,希望部分页面(如对话框、底部弹窗)以页面形式存在于Navigator中且背景透明。直接添加带有半透明组件的MaterialPage这种简单方式无法生效,转场动画结束后下层页面会变成黑色。
我期望看到绿色背景的RootPage上显示红色方块(UpperPage),但实际背景变为黑色。Navigator 1.0的API(如showGeneralDialog)可以正常实现该效果,但我不想混合声明式与命令式方式,因为这很难通过bloc或Provider等单一数据源进行控制。请问仅通过Pages API能否实现该效果?
复现代码:
class RootPage extends StatelessWidget { const RootPage({Key? key}) : super(key: key); @override Widget build(BuildContext context) { return SizedBox.expand( child: Container(color: Colors.green), ); } } class UpperPage extends StatelessWidget { const UpperPage({Key? key}) : super(key: key); @override Widget build(BuildContext context) { return Center( child: Container( width: 100, height: 100, color: Colors.red, ), ); } } class AppRouterDelegate extends RouterDelegate<String> with ChangeNotifier { @override Widget build(BuildContext context) { return Navigator( pages: const [ MaterialPage(child: RootPage()), MaterialPage(child: UpperPage()), ], onPopPage: (route, result) { return route.didPop(result); }, ); } ... }
解决方案
仅通过Pages API完全可以实现这个效果,问题出在MaterialPage的默认行为上——它会自动添加不透明背景和默认页面过渡,导致下层页面被遮挡。你可以通过以下两种方式解决:
方式一:自定义透明Page类(推荐)
自定义一个继承自Page的类,主动关闭不透明背景,同时可按需自定义过渡动画:
class TransparentPage extends Page { final Widget child; const TransparentPage({required this.child, LocalKey? key, String? name}) : super(key: key, name: name); @override Route createRoute(BuildContext context) { return PageRouteBuilder( settings: this, pageBuilder: (context, animation, secondaryAnimation) => child, // 自定义过渡动画(示例用淡入,可替换为其他动画) transitionsBuilder: (context, animation, secondaryAnimation, child) { return FadeTransition(opacity: animation, child: child); }, // 核心设置:关闭路由不透明属性 opaque: false, // 移除默认遮罩 barrierColor: null, ); } }
然后在RouterDelegate中替换MaterialPage为自定义的TransparentPage:
class AppRouterDelegate extends RouterDelegate<String> with ChangeNotifier { @override Widget build(BuildContext context) { return Navigator( pages: const [ MaterialPage(child: RootPage()), TransparentPage(child: UpperPage()), ], onPopPage: (route, result) { if (!route.didPop(result)) return false; // 此处需根据你的路由逻辑更新页面列表,并通知状态变化 notifyListeners(); return true; }, ); } ... }
方式二:修改MaterialPageRoute属性(便捷但灵活性低)
如果坚持使用MaterialPage,可以通过重写createRoute方法修改路由的opaque属性:
class AppRouterDelegate extends RouterDelegate<String> with ChangeNotifier { @override Widget build(BuildContext context) { return Navigator( pages: [ MaterialPage(child: RootPage()), _TransparentMaterialPage(child: UpperPage()), ], onPopPage: (route, result) { if (!route.didPop(result)) return false; notifyListeners(); return true; }, ); } ... } class _TransparentMaterialPage extends MaterialPage { const _TransparentMaterialPage({required super.child}) : super(); @override Route createRoute(BuildContext context) { return super.createRoute(context).copyWith( opaque: false, barrierColor: null, ); } }
关键注意事项
- 必须将路由的
opaque设为false,才能让下层页面显示 barrierColor设为null,避免默认的半透明遮罩遮挡下层内容- 自定义过渡动画时,要确保动画组件不会引入额外的不透明背景层
修改后即可在绿色的RootPage上显示红色方块,同时全程使用声明式路由API,无需混合Navigator 1.0的命令式方法,完美适配bloc或Provider的单一数据源控制逻辑。
内容的提问来源于stack exchange,提问作者Georgiy Khloptov
相关产品推荐
相关产品推荐

