Flutter中如何检测并阻止用户离开当前页面?
Flutter 页面离开行为检测与拦截实现
Flutter中页面离开的触发场景分为两类:一类是物理返回键、AppBar返回按钮、iOS侧滑返回触发的路由弹出(pop)操作;另一类是代码主动调用路由跳转方法触发的离开,两类场景需要分别适配。
1. 拦截路由弹出类离开(官方推荐方案,适用于Flutter 3.12+)
使用官方提供的PopScope组件实现,这是替代旧版WillPopScope的标准方案,自动兼容iOS侧滑返回手势,无需额外适配。
核心参数说明:
canPop:布尔值,设置为false时会拦截所有系统触发的pop操作onPopInvoked:pop操作触发时的回调,参数didPop标识系统是否已经完成页面弹出,值为true时无需额外处理,值为false代表拦截成功,可在回调中执行业务判断逻辑
完整代码示例:
class EditPage extends StatefulWidget { const EditPage({super.key}); @override State<EditPage> createState() => _EditPageState(); } class _EditPageState extends State<EditPage> { // 业务状态标识:例如当前存在未保存的编辑内容 bool _hasUnSavedChange = true; @override Widget build(BuildContext context) { return PopScope( canPop: !_hasUnSavedChange, onPopInvoked: (bool didPop) async { if (didPop) return; // 拦截后执行业务判断,例如弹出离开确认弹窗 final bool isConfirmLeave = await showDialog<bool>( context: context, builder: (dialogContext) => AlertDialog( title: const Text('离开提示'), content: const Text('当前有未保存的修改,确定要离开当前页面吗?'), actions: [ TextButton( onPressed: () => Navigator.pop(dialogContext, false), child: const Text('继续编辑'), ), TextButton( onPressed: () => Navigator.pop(dialogContext, true), child: const Text('确认离开'), ), ], ), ) ?? false; // 用户确认离开时,手动执行pop操作 if (isConfirmLeave && mounted) { Navigator.pop(context); } }, child: Scaffold( appBar: AppBar(title: const Text('内容编辑页')), body: const Center(child: Text('编辑区域')), ), ); } }
使用注意:
- 业务状态变更时(比如用户点击保存按钮),调用
setState更新_hasUnSavedChange为false,canPop会自动变为true,后续pop操作将直接放行 - 不要在
onPopInvoked中未判断didPop就直接执行pop,避免重复弹出路由报错
2. 低版本Flutter兼容方案(3.12版本以下)
Flutter 3.12以下版本可以使用WillPopScope组件实现拦截,核心是通过onWillPop回调的返回值控制是否放行,返回true允许离开,返回false拦截操作。
代码示例:
@override Widget build(BuildContext context) { return WillPopScope( onWillPop: () async { // 执行业务判断,例如弹出确认框 final bool isConfirmLeave = await showDialog<bool>( context: context, builder: (context) => /* 同上的确认弹窗逻辑 */, ) ?? false; return isConfirmLeave; }, child: Scaffold(/* 页面内容 */), ); }
注意:该方案默认会禁用iOS的侧滑返回手势,如需保留需要额外做手势适配,优先建议升级Flutter版本使用PopScope。
3. 拦截主动代码触发的路由跳转
上述两种方案仅能拦截pop类的离开行为,对于代码主动调用Navigator.push、pushReplacement、pushNamed等方法触发的跳转,需要通过统一路由入口拦截:
- 封装项目全局的路由跳转方法,所有页面跳转必须走封装方法,在方法内提前判断当前页面的拦截状态,不符合放行条件时终止跳转
- 如果使用
go_router、auto_route等三方路由框架,可直接使用框架提供的路由拦截回调,在跳转前执行校验逻辑
简单的封装示例:
/// 统一路由跳转封装 Future<T?>? appPush<T>(BuildContext context, Route<T> targetRoute) async { // 读取当前页面的拦截状态,例如获取编辑页的未保存标识 final editPageState = context.findAncestorStateOfType<_EditPageState>(); if (editPageState?._hasUnSavedChange == true) { final confirm = await showDialog<bool>( context: context, builder: (context) => /* 离开确认弹窗 */, ) ?? false; if (!confirm) return null; } // 校验通过后执行跳转 return Navigator.push(context, targetRoute); }
内容的提问来源于stack exchange,提问作者Malik Muhangi
相关产品推荐
相关产品推荐

