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

Flutter Navigator 2.0中Android返回键直接退出APP而非回退路由问题

问题:Flutter Navigator 2.0 物理返回键直接关闭应用,而非回退路由

问题现象

  • 点击AppBar返回按钮时,页面可正常按层级回退:
    Second Page ---> pop ---
        |                  │
        |                  ↓
    First Page        First Page ---> pop ---
        |                  |                │
        |                  |                ↓
    Main Page          Main Page        Main Page
    
  • 按下/滑动Android物理返回键时,应用直接关闭,不执行路由回退:
    Second Page ---> Android back btn pop ---
        |                                   │
        |                                   |
    First Page                              |
        |                                   |
        |                                   ↓
    Main Page                         App is closed
    

现象演示:
Android物理返回键直接关闭应用

简化版复现代码

import 'package:flutter/material.dart';

void main() {
  runApp(const MainApp());
}

class MainApp extends StatelessWidget {
  const MainApp({super.key});

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      home: Router(
        routerDelegate: AppRouterDelegate(),
        backButtonDispatcher: RootBackButtonDispatcher(),
      ),
    );
  }
}

class MainPage extends StatelessWidget {
  const MainPage({super.key, required this.openFirstPage});

  final VoidCallback openFirstPage;

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: const Text('Main page'),
        backgroundColor: Colors.lightGreen,
      ),
      body: Center(
        child: Column(
          mainAxisAlignment: MainAxisAlignment.center,
          children: [
            const Text('Welcome to main page!'),
            ElevatedButton(
              onPressed: openFirstPage,
              child: const Text('Go to first page'),
            ),
          ],
        ),
      ),
    );
  }
}

class FirstPage extends StatelessWidget {
  const FirstPage({super.key, required this.openSecondPage});

  final VoidCallback openSecondPage;

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: const Text('First page'),
        backgroundColor: Colors.amber,
      ),
      body: Center(
        child: Column(
          mainAxisAlignment: MainAxisAlignment.center,
          children: [
            const Text('Welcome to first page!'),
            ElevatedButton(
              onPressed: openSecondPage,
              child: const Text('Go to second page'),
            ),
          ],
        ),
      ),
    );
  }
}

class SecondPage extends StatelessWidget {
  const SecondPage({super.key});

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: const Text('Second page'),
        backgroundColor: Colors.lightBlueAccent,
      ),
      body: const Center(
        child: Text('Welcome to second page!'),
      ),
    );
  }
}

class AppRouterDelegate extends RouterDelegate
    with ChangeNotifier, PopNavigatorRouterDelegateMixin {
  bool _showFirstPage = false;
  void showFirstPage() {
    _showFirstPage = true;
    notifyListeners();
  }

  bool _showSecondPage = false;
  void showSecondPage() {
    _showSecondPage = true;
    notifyListeners();
  }

  @override
  Widget build(BuildContext context) {
    return Navigator(
      key: navigatorKey,
      pages: [
        MaterialPage(
          key: const ValueKey('MainPage'),
          child: MainPage(openFirstPage: showFirstPage),
        ),
        if (_showFirstPage)
          MaterialPage(
            key: const ValueKey('FirstPage'),
            child: FirstPage(openSecondPage: showSecondPage),
          ),
        if (_showSecondPage)
          const MaterialPage(
            key: ValueKey('SecondPage'),
            child: SecondPage(),
          ),
      ],
      onDidRemovePage: (page) {
        if (_showSecondPage) {
          _showSecondPage = false;
          return;
        }
        if (_showFirstPage) _showFirstPage = false;
      },
    );
  }

  @override
  GlobalKey<NavigatorState>? get navigatorKey => GlobalKey();

  @override
  Future<void> setNewRoutePath(configuration) async {
    // Do nothing
  }
}

问题原因

使用RootBackButtonDispatcher会直接将物理返回键事件传递给系统,跳过RouterDelegate的路由处理逻辑,触发系统默认的“关闭应用”行为,而非执行页面回退。

解决方案

步骤1:替换返回键调度器

将RootBackButtonDispatcher替换为ChildBackButtonDispatcher,并让其优先处理返回事件,确保路由代理能捕获到物理返回键事件:
修改MainApp的build方法:

@override
Widget build(BuildContext context) {
  final routerDelegate = AppRouterDelegate();
  return MaterialApp(
    home: Router(
      routerDelegate: routerDelegate,
      backButtonDispatcher: ChildBackButtonDispatcher(Router.of(context).backButtonDispatcher!)
        ..takePriority(),
    ),
  );
}

步骤2:实现路由回退逻辑

在AppRouterDelegate中重写popRoute方法,按路由层级处理回退:

@override
Future<bool> popRoute() {
  // 先回退第二页
  if (_showSecondPage) {
    _showSecondPage = false;
    notifyListeners();
    return Future.value(true);
  }
  // 再回退第一页
  if (_showFirstPage) {
    _showFirstPage = false;
    notifyListeners();
    return Future.value(true);
  }
  // 无路由可回退时,返回false让系统处理(关闭应用)
  return Future.value(false);
}

修改后,物理返回键会按照路由层级依次回退页面,只有当没有可回退路由时才会关闭应用。


内容的提问来源于stack exchange,提问作者sm-sayedi

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.18 08:20:57