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

Flutter安卓应用原生返回键失效问题求助

问题解决:Flutter安卓原生返回键直接退出应用

问题根源

你的代码没有使用Flutter原生的Navigator导航栈管理页面,而是通过setState直接替换_currentPage变量切换页面。这种方式下,Flutter的导航栈始终只有MainScreen这一个页面,所以Navigator.of(context).canPop()永远返回false,导致按下返回键直接退出应用。你自己维护的_pageStack只是普通列表,和Flutter的导航栈完全无关,因此无法实现返回逻辑。

修复方案

改用Flutter原生Navigator管理页面跳转,同时使用最新的PopScope替代已废弃的WillPopScope,确保返回键能正确识别导航栈中的页面。

修改后的完整代码

import 'package:flutter/material.dart';
import 'package:flutter_secure_storage/flutter_secure_storage.dart';
import 'login_screen.dart';
import 'menu_page.dart';
import 'user_page.dart';
import 'statistici_page.dart';

class MainScreen extends StatefulWidget {
  final String phoneId;
  final String idHash;

  const MainScreen({super.key, required this.phoneId, required this.idHash});

  @override
  MainScreenState createState() => MainScreenState();
}

class MainScreenState extends State<MainScreen> {
  int _selectedIndex = 2;
  // 为每个底部导航项维护独立导航键,切换底部栏时保留各自页面栈
  final List<GlobalKey<NavigatorState>> _navigatorKeys = [
    GlobalKey<NavigatorState>(),
    GlobalKey<NavigatorState>(),
    GlobalKey<NavigatorState>(),
  ];

  @override
  void initState() {
    super.initState();
  }

  void _onBottomNavTap(int index) {
    if (index == 2) {
      _logout();
      return;
    }

    setState(() {
      _selectedIndex = index;
    });
  }

  // 统一导航方法,使用对应底部项的Navigator入栈
  void _onNavigate(Widget page, String title) {
    _navigatorKeys[_selectedIndex].currentState?.push(
      MaterialPageRoute(builder: (context) => page),
    );
  }

  Future<void> _logout() async {
    bool confirmLogout = await showDialog(
      context: context,
      builder: (BuildContext context) {
        return AlertDialog(
          title: const Text('确认退出'),
          content: const Text('确定要注销吗?'),
          actions: <Widget>[
            TextButton(
              onPressed: () => Navigator.of(context).pop(false),
              child: const Text('取消'),
            ),
            ElevatedButton(
              onPressed: () => Navigator.of(context).pop(true),
              child: const Text('注销'),
            ),
          ],
        );
      },
    );

    if (confirmLogout == true) {
      const FlutterSecureStorage storage = FlutterSecureStorage();
      final allKeys = await storage.readAll();
      final preserveSet = {'key1', 'key2'};
      final keysToDelete = allKeys.keys.where((key) => !preserveSet.contains(key)).toList();

      for (var key in keysToDelete) {
        await storage.delete(key: key);
      }

      if (!mounted) return;

      Navigator.of(context).pushReplacement(
        MaterialPageRoute(
          builder: (context) => LoginScreen(phoneId: widget.phoneId),
        ),
      );
    }
  }

  // 处理返回键逻辑:先尝试当前底部项导航栈出栈,再处理整体退出
  Future<bool> _handleBackPress() async {
    final currentNavigator = _navigatorKeys[_selectedIndex].currentState;
    if (currentNavigator != null && currentNavigator.canPop()) {
      currentNavigator.pop();
      return false;
    }
    // 初始页面询问是否退出应用
    if (_selectedIndex == 2) {
      bool confirmExit = await showDialog(
        context: context,
        builder: (context) => AlertDialog(
          title: const Text('确认退出'),
          content: const Text('确定要退出应用吗?'),
          actions: [
            TextButton(onPressed: () => Navigator.pop(context, false), child: const Text('取消')),
            TextButton(onPressed: () => Navigator.pop(context, true), child: const Text('退出')),
          ],
        ),
      );
      return confirmExit;
    }
    // 切换到默认统计页而非退出
    setState(() => _selectedIndex = 2);
    return false;
  }

  @override
  Widget build(BuildContext context) {
    return PopScope(
      canPop: false,
      onPopInvoked: (didPop) async {
        if (didPop) return;
        bool shouldExit = await _handleBackPress();
        if (shouldExit) {
          Navigator.of(context).pop();
        }
      },
      child: Scaffold(
        appBar: AppBar(
          title: const Text('应用标题'),
          // 根据当前导航栈状态显示返回按钮
          leading: Builder(
            builder: (context) {
              final canPop = _navigatorKeys[_selectedIndex].currentState?.canPop() ?? false;
              return canPop
                  ? IconButton(
                      icon: const Icon(Icons.arrow_back),
                      color: Colors.black,
                      onPressed: () => _navigatorKeys[_selectedIndex].currentState?.pop(),
                    )
                  : const SizedBox.shrink();
            },
          ),
        ),
        body: IndexedStack(
          index: _selectedIndex,
          children: [
            // 每个底部项对应独立Navigator,保留各自页面栈
            Navigator(
              key: _navigatorKeys[0],
              onGenerateRoute: (settings) => MaterialPageRoute(
                builder: (context) => MenuPage(onNavigate: _onNavigate),
              ),
            ),
            Navigator(
              key: _navigatorKeys[1],
              onGenerateRoute: (settings) => MaterialPageRoute(
                builder: (context) => UserPage(onNavigate: _onNavigate),
              ),
            ),
            Navigator(
              key: _navigatorKeys[2],
              onGenerateRoute: (settings) => MaterialPageRoute(
                builder: (context) => const StatisticiPage(),
              ),
            ),
          ],
        ),
        bottomNavigationBar: BottomNavigationBar(
          currentIndex: _selectedIndex,
          onTap: _onBottomNavTap,
          items: const <BottomNavigationBarItem>[
            BottomNavigationBarItem(
              icon: Icon(Icons.menu),
              label: '菜单',
            ),
            BottomNavigationBarItem(
              icon: Icon(Icons.person),
              label: '用户',
            ),
            BottomNavigationBarItem(
              icon: Icon(Icons.logout),
              label: '注销',
            ),
          ],
        ),
      ),
    );
  }
}

关键修改点

  • 独立Navigator管理底部栏页面栈:每个底部导航项对应GlobalKey<NavigatorState>,切换底部栏时保留各自页面历史,避免互相干扰。
  • 替换WillPopScope为PopScope:适配Flutter 3.12+新API,正确拦截返回键事件。
  • IndexedStack+Navigator替代setState切换页面:既保留底部栏页面状态,又通过原生导航栈实现返回逻辑。
  • 统一导航入栈:所有页面跳转通过对应底部项的Navigator入栈,确保页面进入导航栈中。

内容的提问来源于stack exchange,提问作者gelu76

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.18 02:50:58