如何在Flutter中禁用或覆盖Android的“返回”按钮?

244

在特定的页面上是否有一种方法可以停用 Android 返回按钮?

class WakeUpApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return new MaterialApp(
      title: "Time To Wake Up ?",
      home: new WakeUpHome(),
      routes: <String, WidgetBuilder>{
        '/pageOne': (BuildContext context) => new pageOne(),
        '/pageTwo': (BuildContext context) => new pageTwo(),
      },
    );
  }
}

在pageOne页面上,我有一个按钮可以跳转到pageTwo页面:

new FloatingActionButton(
  onPressed: () {    
    Navigator.of(context).pushNamed('/pageTwo');
  },
)

我的问题是,如果我按安卓屏幕底部的“返回”箭头,我会回到第一页。我希望这个按钮根本不显示。 理想情况下,在没有用户按住屏幕5秒钟的情况下,我希望没有任何可能离开此屏幕的方式。(我正在尝试编写一个儿童应用程序,并且只希望父母能够浏览特定的屏幕)。

15个回答

439

答案是WillPopScope。这将防止系统弹出页面。您仍然可以使用Navigator.of(context).pop()

@override
Widget build(BuildContext context) {
  return new WillPopScope(
    onWillPop: () async => false,
    child: new Scaffold(
      appBar: new AppBar(
        title: new Text("data"),
        leading: new IconButton(
          icon: new Icon(Icons.ac_unit),
          onPressed: () => Navigator.of(context).pop(),
        ),
      ),
    ),
  );
}

6
对于希望拦截表单弹出的用户,更方便的方法是使用表单的onWillPop属性。它可以访问表单状态,并且可以首先检查是否存在任何用户可能不想丢失的状态。 - Joe Lapp
嗨@RémiRousselet,你能帮我处理后台堆栈管理吗?比如我有一个包含A、B、C、D屏幕的堆栈,我想要从D->B或者D->A进行导航,那我该如何处理呢?你能给我指点一下吗? - Ravindra Kushwaha
1
我知道这个答案可能有点老了,但是它真的很棒!考虑扩展一下解释吧。它的效果非常好。谢谢。 - Val
7
对于想在调用pop()之前执行某些操作的人,可以使用以下代码:onWillPop: () async { onBack(); // "在返回时执行你的函数" return false; }该代码段会在用户尝试从当前页面返回时执行onBack()函数,并阻止默认的返回行为。 - Huy Tower

56
如Rémi Rousselet所指出的,使用WillPopScope通常是可行的方法。但是,如果您正在开发一个应该直接响应返回按钮的有状态小部件,则可以使用以下方法:

https://pub.dartlang.org/packages/back_button_interceptor

注意:我是该软件包的作者。


49
不行,因为这不是对另一个答案的评论,而是解决同一问题的完全不同的方式。 - MarcG
8
这是一个很好的替代方案,如果它只是评论的一部分可能就被忽略了。 - abhijat_saxena
4
这是一个很好的替代方案,效果非常棒,感谢你将其在另一个答案中突出展示。 - vipes

48

虽然Remi的回答是正确的,通常你不想简单地阻止返回按钮,而是希望用户确认退出。

您可以通过从确认对话框获取答案来以类似的方式完成此操作,因为 onWillPop 是一个future。

@override
Widget build(BuildContext context) {
  return WillPopScope(
    child: Scaffold(...),
    onWillPop: () => showDialog<bool>(
      context: context,
      builder: (c) => AlertDialog(
        title: Text('Warning'),
        content: Text('Do you really want to exit'),
        actions: [
          FlatButton(
            child: Text('Yes'),
            onPressed: () => Navigator.pop(c, true),
          ),
          FlatButton(
            child: Text('No'),
            onPressed: () => Navigator.pop(c, false),
          ),
        ],
      ),
    ),
  );
}

为什么这个答案会被踩?有人实现过并且遇到了不好的经历吗? - SamiAzar
1
我想在按下设备的返回按钮后回到上一个屏幕,而不是回到第一个屏幕。我不需要这样的对话框。我该怎么办? - Lakshmi

19

您可以使用 Future.value(bool) 来处理返回按钮。

bool _allow = true;

@override
Widget build(BuildContext context) {
  return WillPopScope(
    child: Scaffold(appBar: AppBar(title: Text("Back"))),
    onWillPop: () {
      return Future.value(_allow); // if true allow back else block it
    },
  );
}

2
FYI,这等同于 onWillPop:() async => _allow - Lynn
@Lynn 是的,我知道。我只是想分享 Future.value() 的调用。 - CopsOnRoad
@CopsOnRoad 如果 SafeArea() 已经被定义了,那么如何在 Flutter 中处理返回按钮的操作? - s.j
@CopsOnRoad 你好,在我的课程代码中Widget build(BuildContext context) { return SafeArea( child: Stack( children: <Widget>[ ..... 所以在这里返回WillPopScope(); 不能定义两个返回语句,那么如何在flutter中初始化backpressed - s.j
1
@s.j 将你的 SafeArea 组件包装在 WillPopScope 中。 - CopsOnRoad

10

也许你知道可以使用WillPopScope来解决这个问题,但很遗憾,在iOS上你不能通过滑动返回到上一页,所以让我们自定义你的MaterialPageRoute:

class CustomMaterialPageRoute<T> extends MaterialPageRoute<T> {
  @protected
  bool get hasScopedWillPopCallback {
    return false;
  }
  CustomMaterialPageRoute({
    @required WidgetBuilder builder,
    RouteSettings settings,
    bool maintainState = true,
    bool fullscreenDialog = false,
  }) : super( 
          builder: builder,
          settings: settings,
          maintainState: maintainState,
          fullscreenDialog: fullscreenDialog,
        );
}

现在你可以使用WillPopScope,向右滑动返回功能在IOS上也可以使用。详细信息请参见:https://github.com/flutter/flutter/issues/14203#issuecomment-540663717


9

一个简单的方法。使用WillPopScope小部件将Scaffold包装起来。

  WillPopScope(
    onWillPop: () async => false,
    child: Scaffold();

7
我在这里发布这篇文章,以防有人发现并希望找到一个简单的例子。以下是相关内容:https://gist.github.com/b-cancel/0ca372017a25f0c120b14dfca3591aa5
import 'package:flutter/material.dart';

import 'dart:async';

void main() => runApp(new BackButtonOverrideDemoWidget());

class BackButtonOverrideDemoWidget extends StatefulWidget{
  @override
  _BackButtonOverrideDemoWidgetState createState() => new _BackButtonOverrideDemoWidgetState();
}

class _BackButtonOverrideDemoWidgetState extends State<BackButtonOverrideDemoWidget> with WidgetsBindingObserver{

  //-------------------------Test Variable

  bool isBackButtonActivated = false;

  //-------------------------Required For WidgetsBindingObserver

  @override
  void initState() {
    super.initState();
    WidgetsBinding.instance.addObserver(this);
  }

  @override
  void dispose() {
    WidgetsBinding.instance.removeObserver(this);
    super.dispose();
  }

  //-------------------------Function That Triggers when you hit the back key

  @override
  didPopRoute(){
    bool override;
    if(isBackButtonActivated)
      override = false;
    else
      override = true;
    return new Future<bool>.value(override);
  }

  //-------------------------Build Method

  @override
  Widget build(BuildContext context) {
    return new Directionality(
      textDirection: TextDirection.ltr,
      child: new Container(
          color: (isBackButtonActivated) ? Colors.green : Colors.red,
          child: new Center(
              child: new FlatButton(
                color: Colors.white,
                onPressed: () {
                  isBackButtonActivated = !isBackButtonActivated;
                  setState(() {});
                },
                child: (isBackButtonActivated) ?
                new Text("DeActive the Back Button") : new Text("Activate the Back Button"),
              )
          )
      ),
    );
  }
}

我昨天自己亲测过。请记住,它仅适用于Android,苹果没有返回按钮。虽然你能告诉我出了什么问题,这样我就可以修复它,或者它只在我的特定模拟器上运行。我已经几周没有更新我的Flutter了,所以也许这就是问题所在。让我知道吧。 - Bryan Cancel
1
@BryanCancel 您的答案仅在您尚未有任何推送路由时有效。请参阅方法WidgetsBinding.handlePopRoute()。它按注册顺序通知观察者,并在收到true后立即停止。当存在推送路由时,导航器首先返回true,然后您的观察者实际上永远不会被调用。换句话说,您的代码仅在没有剩余路由时才能防止用户单击返回按钮关闭应用程序。 - MarcG

4

试图这样做会破坏你的应用状态

@override
  Widget build(BuildContext context) {
    return WillPopScope(
      ////////////////
      onWillPop: () => showDialog<bool>(
        context: context,
        builder: (c) => AlertDialog(
          title: Text(
            'Warning',
            textAlign: TextAlign.center,
          ),
          content: Text('Are you sure to exit?'),
          actions: [
            TextButton(
              style: TextButton.styleFrom(
                primary: Colors.green,
              ),
              onPressed: () async {
                exit(0);// kill app
              },
              child: Text('Yes'),
            ),
            TextButton(
              style: TextButton.styleFrom(
                primary: Colors.red,
              ),
              onPressed: () => Navigator.pop(c, false),
              child: Text('No'),
            )
          ],
        ),
      ),
      /////////////////////
      child: Scaffold(),
    );
  }

4

如果您使用了空安全规则进行编码,则可以尝试另一种解决方案。您需要禁用默认返回按钮,并将其替换为 IconButton。在此示例中,当用户点击返回按钮以确认退出时,我会推送一个 AlertDialog。您可以替换此功能并将用户发送到任何其他页面。

return WillPopScope(
  onWillPop: () async => false,
  child: Scaffold(
    appBar: AppBar(
      automaticallyImplyLeading: true,
      title: Text(),
      leading: IconButton(
        icon: Icon(Icons.arrow_back),
        onPressed: () => showDialog<bool>(
          context: context,
          builder: (c) => AlertDialog(
            title: Text('Warning'),
            content: Text('Are you sure you want to exit?'),
            ),
            actions: [
              TextButton(
                  child: Text('Yes'),
                  onPressed: () {
                    Navigator.pop(c, true);
                    Navigator.pop(context);
                  }),
              TextButton(
                child: Text('No'),
                onPressed: () => Navigator.pop(c, false),
              ),
            ],
          ),
        ),
      ),
    ),

使用BackButtonIcon()函数来获取适合平台的图标。 - Suragch

3

我使用mixin和WillPopScope小部件,但无法完成我的工作。

在我看来,这是我找到的最佳方法,比WillPopScope好得多。

final bool canPop = ModalRoute.of(context)?.canPop ?? false;

在appbar中像这样使用:

leading: ModalRoute.of(context)?.canPop ?? false
    ? IconButton(
        onPressed: () {
          Navigator.pop(context);
        },
        icon: (Platform.isAndroid)
            ? const Icon(Icons.arrow_back)
            : const Icon(Icons.arrow_back_ios),
      )
    : Container(),

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接