You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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),
    );
  }
}

关键修改点说明

  1. 添加GlobalKey:给每个嵌套Navigator分配GlobalKey<NavigatorState>,用于获取当前导航栈的状态,判断是否可以pop。
  2. 包裹PopScope:每个Navigator外层用PopScope包裹,设置canPop: false阻止系统默认返回行为,在onPopInvoked回调中自定义返回逻辑。
  3. 修复路由生成bug:原代码中/job_details和/my_job_details的case缺少break,会导致后续路由被错误匹配,已修复。
  4. 自定义返回逻辑:在onPopInvoked中先检查当前嵌套Navigator是否有页面可返回(navState.canPop()),如果有则执行pop;如果嵌套栈为空,再执行退出应用或其他逻辑。

内容的提问来源于stack exchange,提问作者yusuf çelebi

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.17 06:57:33