Flutter嵌套导航中使用PopScope处理返回按钮问题
解决Flutter嵌套导航中返回按钮直接退出应用的问题
我尝试用PopScope处理Flutter嵌套导航逻辑,但在嵌套路由里点击设备返回按钮时,应用直接退出而不是返回上一页。而且onInvoked方法已经废弃,不知道具体该怎么实现。以下是main_screen.dart的代码:
import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:calistimapp/widgetlar/bottom_navigation.dart'; import 'package:calistimapp/providers/main_screen_provider.dart'; import 'package:calistimapp/screens/job_search.dart'; import 'package:calistimapp/screens/cuzdan.dart'; import 'package:calistimapp/screens/islerim.dart'; import 'package:calistimapp/screens/profil.dart'; import 'package:calistimapp/screens/kategori_sayfalari/komi_garson_screen.dart'; import 'package:calistimapp/screens/kategori_sayfalari/etkinlik_gorevli_screen.dart'; import 'package:calistimapp/screens/kategori_sayfalari/otel_gorevlisi_screen.dart'; import 'package:calistimapp/screens/kategori_sayfalari/diger_isler_screen.dart'; import 'package:calistimapp/screens/job_details_screen.dart'; import 'package:calistimapp/screens/my_job_details_screen.dart'; import 'package:calistimapp/screens/pending_applications_screen.dart'; import 'package:calistimapp/screens/other_accepted_jobs_screen.dart'; import 'package:calistimapp/screens/update_worker_screen.dart'; class MainScreen extends ConsumerStatefulWidget { final String initialRoute; const MainScreen({Key? key, required this.initialRoute}) : super(key: key); @override ConsumerState<MainScreen> createState() => _MainScreenState(); } class _MainScreenState extends ConsumerState<MainScreen> { late final PageController _pageController; @override void initState() { super.initState(); _pageController = PageController(); WidgetsBinding.instance.addPostFrameCallback((_) { final initialPage = _getInitialPage(widget.initialRoute); ref.read(mainScreenProvider.notifier).setPage(initialPage); _pageController.jumpToPage(initialPage); }); } @override void dispose() { _pageController.dispose(); super.dispose(); } @override Widget build(BuildContext context) { final currentIndex = ref.watch(mainScreenProvider); return Scaffold( body: PageView( controller: _pageController, physics: const NeverScrollableScrollPhysics(), onPageChanged: (index) { ref.read(mainScreenProvider.notifier).setPage(index); }, children: [ Navigator( onGenerateRoute: (settings) => _generateRoute(settings, const JobSearchScreen()), ), Navigator( onGenerateRoute: (settings) => _generateRoute(settings, const CuzdanScreen()), ), Navigator( onGenerateRoute: (settings) => _generateRoute(settings, const IslerimScreen()), ), Navigator( onGenerateRoute: (settings) => _generateRoute(settings, const ProfilScreen()), ), ], ), bottomNavigationBar: CustomBottomNavigationBar( currentIndex: currentIndex, onTap: (index) { _pageController.jumpToPage(index); }, ), ); } int _getInitialPage(String route) { switch (route) { case '/job_search': return 0; case '/cuzdan': return 1; case '/islerim': return 2; case '/profil': return 3; default: return 0; } } PageRouteBuilder _generateRoute(RouteSettings settings, Widget defaultPage) { Widget page; switch (settings.name) { case '/komi_garson': page = const KomiGarsonScreen(); break; case '/etkinlik_gorevli': page = const EtkinlikGorevliScreen(); break; case '/otel_gorevlisi': page = const OtelGorevlisiScreen(); break; case '/diger_isler': page = const DigerIslerScreen(); break; case '/job_details': final jobId = settings.arguments as String; page = JobDetailsScreen(jobId: jobId); case '/my_job_details': final jobId = settings.arguments as String; page = MyJobDetailsScreen(jobId: jobId); case '/pending_applications': page = const PendingApplicationsScreen(); break; case '/other_accepted_jobs': page = const OtherAcceptedJobsScreen(); break; case '/update-worker': page = const UpdateWorkerScreen(); break; default: page = defaultPage; } return PageRouteBuilder( pageBuilder: (context, animation, secondaryAnimation) => page, transitionsBuilder: (context, animation, secondaryAnimation, child) { const begin = Offset(1.0, 0.0); const end = Offset.zero; const curve = Curves.easeInOut; var tween = Tween(begin: begin, end: end).chain(CurveTween(curve: curve)); var offsetAnimation = animation.drive(tween); return SlideTransition( position: offsetAnimation, child: child, ); }, transitionDuration: const Duration(milliseconds: 300), ); } }
解决方案
问题核心是每个嵌套Navigator没有拦截返回事件,导致系统直接触发应用退出。需要给每个嵌套Navigator添加PopScope,并通过GlobalKey跟踪导航栈状态,实现正确的返回逻辑。
修改后的完整代码:
import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:calistimapp/widgetlar/bottom_navigation.dart'; import 'package:calistimapp/providers/main_screen_provider.dart'; import 'package:calistimapp/screens/job_search.dart'; import 'package:calistimapp/screens/cuzdan.dart'; import 'package:calistimapp/screens/islerim.dart'; import 'package:calistimapp/screens/profil.dart'; import 'package:calistimapp/screens/kategori_sayfalari/komi_garson_screen.dart'; import 'package:calistimapp/screens/kategori_sayfalari/etkinlik_gorevli_screen.dart'; import 'package:calistimapp/screens/kategori_sayfalari/otel_gorevlisi_screen.dart'; import 'package:calistimapp/screens/kategori_sayfalari/diger_isler_screen.dart'; import 'package:calistimapp/screens/job_details_screen.dart'; import 'package:calistimapp/screens/my_job_details_screen.dart'; import 'package:calistimapp/screens/pending_applications_screen.dart'; import 'package:calistimapp/screens/other_accepted_jobs_screen.dart'; import 'package:calistimapp/screens/update_worker_screen.dart'; class MainScreen extends ConsumerStatefulWidget { final String initialRoute; const MainScreen({Key? key, required this.initialRoute}) : super(key: key); @override ConsumerState<MainScreen> createState() => _MainScreenState(); } class _MainScreenState extends ConsumerState<MainScreen> { late final PageController _pageController; // 给每个嵌套Navigator创建GlobalKey,用于跟踪导航栈状态 final _navKeys = [ GlobalKey<NavigatorState>(), GlobalKey<NavigatorState>(), GlobalKey<NavigatorState>(), GlobalKey<NavigatorState>(), ]; @override void initState() { super.initState(); _pageController = PageController(); WidgetsBinding.instance.addPostFrameCallback((_) { final initialPage = _getInitialPage(widget.initialRoute); ref.read(mainScreenProvider.notifier).setPage(initialPage); _pageController.jumpToPage(initialPage); }); } @override void dispose() { _pageController.dispose(); super.dispose(); } @override Widget build(BuildContext context) { final currentIndex = ref.watch(mainScreenProvider); return Scaffold( body: PageView( controller: _pageController, physics: const NeverScrollableScrollPhysics(), onPageChanged: (index) { ref.read(mainScreenProvider.notifier).setPage(index); }, children: [ // 用PopScope包裹每个Navigator,处理返回逻辑 PopScope( canPop: false, // 阻止默认返回行为,由我们自定义处理 onPopInvoked: (bool didPop) async { final navState = _navKeys[0].currentState; if (navState != null && navState.canPop()) { navState.pop(); } else { // 如果嵌套栈为空,可选择退出应用或者其他逻辑 Navigator.of(context).pop(); } }, child: Navigator( key: _navKeys[0], onGenerateRoute: (settings) => _generateRoute(settings, const JobSearchScreen()), ), ), PopScope( canPop: false, onPopInvoked: (bool didPop) async { final navState = _navKeys[1].currentState; if (navState != null && navState.canPop()) { navState.pop(); } else { Navigator.of(context).pop(); } }, child: Navigator( key: _navKeys[1], onGenerateRoute: (settings) => _generateRoute(settings, const CuzdanScreen()), ), ), PopScope( canPop: false, onPopInvoked: (bool didPop) async { final navState = _navKeys[2].currentState; if (navState != null && navState.canPop()) { navState.pop(); } else { Navigator.of(context).pop(); } }, child: Navigator( key: _navKeys[2], onGenerateRoute: (settings) => _generateRoute(settings, const IslerimScreen()), ), ), PopScope( canPop: false, onPopInvoked: (bool didPop) async { final navState = _navKeys[3].currentState; if (navState != null && navState.canPop()) { navState.pop(); } else { Navigator.of(context).pop(); } }, child: Navigator( key: _navKeys[3], onGenerateRoute: (settings) => _generateRoute(settings, const ProfilScreen()), ), ), ], ), bottomNavigationBar: CustomBottomNavigationBar( currentIndex: currentIndex, onTap: (index) { _pageController.jumpToPage(index); }, ), ); } int _getInitialPage(String route) { switch (route) { case '/job_search': return 0; case '/cuzdan': return 1; case '/islerim': return 2; case '/profil': return 3; default: return 0; } } PageRouteBuilder _generateRoute(RouteSettings settings, Widget defaultPage) { Widget page; switch (settings.name) { case '/komi_garson': page = const KomiGarsonScreen(); break; case '/etkinlik_gorevli': page = const EtkinlikGorevliScreen(); break; case '/otel_gorevlisi': page = const OtelGorevlisiScreen(); break; case '/diger_isler': page = const DigerIslerScreen(); break; case '/job_details': final jobId = settings.arguments as String; page = JobDetailsScreen(jobId: jobId); break; // 原代码这里缺少break,会导致后续case被执行,需要修复 case '/my_job_details': final jobId = settings.arguments as String; page = MyJobDetailsScreen(jobId: jobId); break; // 同样修复缺少break的问题 case '/pending_applications': page = const PendingApplicationsScreen(); break; case '/other_accepted_jobs': page = const OtherAcceptedJobsScreen(); break; case '/update-worker': page = const UpdateWorkerScreen(); break; default: page = defaultPage; } return PageRouteBuilder( pageBuilder: (context, animation, secondaryAnimation) => page, transitionsBuilder: (context, animation, secondaryAnimation, child) { const begin = Offset(1.0, 0.0); const end = Offset.zero; const curve = Curves.easeInOut; var tween = Tween(begin: begin, end: end).chain(CurveTween(curve: curve)); var offsetAnimation = animation.drive(tween); return SlideTransition( position: offsetAnimation, child: child, ); }, transitionDuration: const Duration(milliseconds: 300), ); } }
关键修改点说明
- 添加GlobalKey:给每个嵌套Navigator分配
GlobalKey<NavigatorState>,用于获取当前导航栈的状态,判断是否可以pop。 - 包裹PopScope:每个Navigator外层用
PopScope包裹,设置canPop: false阻止系统默认返回行为,在onPopInvoked回调中自定义返回逻辑。 - 修复路由生成bug:原代码中
/job_details和/my_job_details的case缺少break,会导致后续路由被错误匹配,已修复。 - 自定义返回逻辑:在
onPopInvoked中先检查当前嵌套Navigator是否有页面可返回(navState.canPop()),如果有则执行pop;如果嵌套栈为空,再执行退出应用或其他逻辑。
内容的提问来源于stack exchange,提问作者yusuf çelebi
相关产品推荐
相关产品推荐

