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

Flutter应用Android返回按钮/手势触发直接退出应用问题求助

Flutter Android返回按钮/手势异常:跳转后返回直接退出应用

在Flutter应用的Android平台上,从Welcome Screen通过Navigator.pushNamed跳转到Login Screen后,点击返回按钮或触发返回手势,本该回到Welcome Screen,但应用直接退出。以下是相关代码及修复方案:


相关代码

路由类(AppRouter)

class AppRouter {
  Route<dynamic>? onGenerateRoute(RouteSettings routeSettings) {
    switch (routeSettings.name) {
      case '/login':
        return PageRouteBuilder(
            settings: routeSettings,
            pageBuilder: (_, __, ___) {
              return const LoginScreen();
            },
            transitionDuration: const Duration(milliseconds: 600),
            reverseTransitionDuration: const Duration(milliseconds: 450),
            transitionsBuilder: (_, a, __, c) => SlideTransition(
                position: Tween<Offset>(
                  begin: const Offset(1.0, 0.0),
                  end: Offset.zero,
                ).animate(
                  CurvedAnimation(
                    parent: a,
                    curve: Curves.easeInOutCubicEmphasized,
                  ),
                ),
                child: c));
      default:
        return _materialRoute(const WelcomeScreen());
    }
  }

  static Route<dynamic> _materialRoute(Widget view) {
    return MaterialPageRoute(builder: (_) => view);
  }
}

main.dart

class MyApp extends StatefulWidget {
  const MyApp({super.key});

  @override
  State<MyApp> createState() => _MyAppState();
}

class _MyAppState extends State<MyApp> {
  @override
  void initState() {
    super.initState();
  }

  @override
  Widget build(BuildContext context) {
    SystemChrome.setEnabledSystemUIMode(
      SystemUiMode.edgeToEdge,
    );

    return ScreenUtilInit(
        designSize: const Size(430, 932),
        minTextAdapt: true,
        splitScreenMode: true,
        builder: (_, child) {
          return MaterialApp(
            home: Navigator(
              key: navigatorKey,
              onGenerateRoute: AppRouter().onGenerateRoute,
            ),
          );
        });
  }
}

Welcome Screen

class WelcomeScreen extends StatefulWidget {
  const WelcomeScreen({super.key});

  @override
  State<StatefulWidget> createState() {
    return _WelcomeScreenState();
  }
}

class _WelcomeScreenState extends State<WelcomeScreen> {
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      body: SafeArea(
        child: Padding(
          padding: const EdgeInsets.all(20.0),
          child: Column(
            mainAxisAlignment: MainAxisAlignment.center,
            crossAxisAlignment: CrossAxisAlignment.center,
            mainAxisSize: MainAxisSize.max,
            children: [
              SizedBox(
                width: double.infinity,
                child: ElevatedButton(
                  onPressed: () {
                    Navigator.of(context).pushNamed('/login');
                  },
                  child: const Text('Continue with E-Mail'),
                ),
              ),
              const SizedBox(
                height: 12,
              ),
            ],
          ),
        ),
      ),
    );
  }
}

Login Screen

class LoginScreen extends StatefulWidget {
  const LoginScreen({super.key});

  @override
  State<StatefulWidget> createState() {
    return _LoginScreenState();
  }
}

class _LoginScreenState extends State<LoginScreen> {
  @override
  void initState() {
    super.initState();
  }

  @override
  Widget build(BuildContext context) {
    return PopScope(
      canPop: true,
      onPopInvoked: (didPop) => print('onPopInvoked didPop? $didPop'),
      child: Scaffold(
        appBar: AppBar(
            toolbarHeight: 0.09.sh,
            leadingWidth: 70,
            leading: Padding(
              padding: const EdgeInsets.only(left: 20),
              child: GestureDetector(
                onTap: () {
                  Navigator.of(context).pushReplacementNamed('/welcome');
                },
                child: SvgPicture.asset(
                  'assets/icons/back-in-circle.svg',
                  fit: BoxFit.contain,
                  color: Theme.of(context).brightness == Brightness.light
                      ? colorBlack
                      : null,
                ),
              ),
            )),
        body: SafeArea(
          child: Padding(
            padding: const EdgeInsets.all(20.0),
            child: Column(
              mainAxisAlignment: MainAxisAlignment.spaceBetween,
              mainAxisSize: MainAxisSize.max,
              children: [
                Text("Login screen")
              ],
            ),
          ),
        ),
      ),
    );
  }
}

AndroidManifest.xml配置

<manifest xmlns:android="http://schemas.android.com/apk/res/android">
    ...

    <application
        ...
        <activity
            ...
            android:enableOnBackInvokedCallback="true">

效果说明

  • 当前效果:从Login Screen返回时直接退出应用
  • 预期效果:从Login Screen返回时回到Welcome Screen

问题根源

MaterialApp直接将Navigator作为home属性,相当于在MaterialApp自带的导航栈之外嵌套了一层独立导航栈。Android系统的返回手势/按钮只会识别外层导航栈的状态,当Login Screen弹出后,外层栈仅存一个页面,因此触发返回时直接退出应用。


修复方案

1. 调整main.dart的导航配置

移除嵌套的Navigator,改用MaterialApp自带的导航管理,指定初始路由:

class _MyAppState extends State<MyApp> {
  @override
  Widget build(BuildContext context) {
    SystemChrome.setEnabledSystemUIMode(
      SystemUiMode.edgeToEdge,
    );

    return ScreenUtilInit(
        designSize: const Size(430, 932),
        minTextAdapt: true,
        splitScreenMode: true,
        builder: (_, child) {
          return MaterialApp(
            navigatorKey: navigatorKey,
            initialRoute: '/welcome', // 指定初始路由
            onGenerateRoute: AppRouter().onGenerateRoute,
          );
        });
  }
}

2. 完善路由规则

在AppRouter中添加/welcome的明确路由分支,避免default分支的歧义:

class AppRouter {
  Route<dynamic>? onGenerateRoute(RouteSettings routeSettings) {
    switch (routeSettings.name) {
      case '/welcome':
        return _materialRoute(const WelcomeScreen());
      case '/login':
        return PageRouteBuilder(
            settings: routeSettings,
            pageBuilder: (_, __, ___) {
              return const LoginScreen();
            },
            transitionDuration: const Duration(milliseconds: 600),
            reverseTransitionDuration: const Duration(milliseconds: 450),
            transitionsBuilder: (_, a, __, c) => SlideTransition(
                position: Tween<Offset>(
                  begin: const Offset(1.0, 0.0),
                  end: Offset.zero,
                ).animate(
                  CurvedAnimation(
                    parent: a,
                    curve: Curves.easeInOutCubicEmphasized,
                  ),
                ),
                child: c));
      default:
        return _materialRoute(const WelcomeScreen());
    }
  }

  static Route<dynamic> _materialRoute(Widget view) {
    return MaterialPageRoute(builder: (_) => view);
  }
}

3. 修正Login Screen的返回逻辑

将pushReplacementNamed('/welcome')替换为Navigator.pop(context),直接弹出当前页面回到上一页:

// LoginScreen的AppBar leading点击事件
GestureDetector(
  onTap: () {
    Navigator.pop(context); // 替换原pushReplacementNamed逻辑
  },
  child: SvgPicture.asset(
    'assets/icons/back-in-circle.svg',
    fit: BoxFit.contain,
    color: Theme.of(context).brightness == Brightness.light
        ? colorBlack
        : null,
  ),
)

4. 可选:简化PopScope配置

如果不需要自定义返回拦截逻辑,可直接移除Login Screen中的PopScope,让系统默认处理返回行为:

@override
Widget build(BuildContext context) {
  return Scaffold(
    appBar: AppBar(
        toolbarHeight: 0.09.sh,
        leadingWidth: 70,
        leading: Padding(
          padding: const EdgeInsets.only(left: 20),
          child: GestureDetector(
            onTap: () {
              Navigator.pop(context);
            },
            child: SvgPicture.asset(
              'assets/icons/back-in-circle.svg',
              fit: BoxFit.contain,
              color: Theme.of(context).brightness == Brightness.light
                  ? colorBlack
                  : null,
            ),
          ),
        )),
    body: SafeArea(
      child: Padding(
        padding: const EdgeInsets.all(20.0),
        child: Column(
          mainAxisAlignment: MainAxisAlignment.spaceBetween,
          mainAxisSize: MainAxisSize.max,
          children: [
            Text("Login screen")
          ],
        ),
      ),
    ),
  );
}

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.24 07:13:11