【问题标题】:How to write a double back button pressed to exit app using flutter如何编写按下双后退按钮以使用颤振退出应用程序
【发布时间】:2019-04-28 23:40:29
【问题描述】:

我是 Flutter 的新手,我看到很多 Android 应用在双击返回按钮时会退出。

第一次按下后退按钮,应用程序显示吐司“再次按下退出应用程序”。 第二次按下,应用程序退出。 当然,两次按下之间的时间一定不长。

flutter 中怎么做?

【问题讨论】:

    标签: dart flutter


    【解决方案1】:

    这是我的代码示例(我使用“fluttertoast”来显示 toast 消息,您可以使用 snapbar 或 alert 或其他任何东西)

    DateTime currentBackPressTime;
    
    @override
    Widget build(BuildContext context) {
      return Scaffold(
        ...
        body: WillPopScope(child: getBody(), onWillPop: onWillPop),
      );
    }
    
    Future<bool> onWillPop() {
        DateTime now = DateTime.now();
        if (currentBackPressTime == null || 
            now.difference(currentBackPressTime) > Duration(seconds: 2)) {
          currentBackPressTime = now;
          Fluttertoast.showToast(msg: exit_warning);
          return Future.value(false);
        }
        return Future.value(true);
      }
    

    【讨论】:

    • 你能解释一下什么是get body吗?
    • 构建我的内容的单独方法
    • 当然可以。 WillPopScope 只管理背压。因此,可以在第一页上实现此代码,以防止意外关闭应用程序。如果要关闭应用,可以拨打SystemChannels.platform.invokeMethod('SystemNavigator.pop');SystemNavigator.pop()
    • 感谢您提供小巧优雅的解决方案!但我对这段代码有疑问:我的主要Scaffold 包含一个Drawer。抽屉打开时,通常可以用返回按钮将其关闭。但现在抽屉在第一次点击时打开,我有一个带有 exit_warning 的吐司,第二次点击时抽屉关闭。我的意思是,“再次按下退出”不应该影响抽屉。但我无法弄清楚我该如何解决它。我使用WillPopScope 作为Scaffold 的主体,与答案完全相同。谢谢。
    • 回答我自己的问题:ScaffoldStateisDrawerOpen 属性,所以我应该使用它。
    【解决方案2】:

    你可以试试this包。

    在包装所有小部件的Scaffold 中,将DoubleBackToCloseApp 放置在传递SnackBar 的位置:

    class MyApp extends StatelessWidget {
      @override
      Widget build(BuildContext context) {
        return MaterialApp(
          home: Scaffold(
            body: DoubleBackToCloseApp(
              child: Home(),
              snackBar: const SnackBar(
                content: Text('Tap back again to leave'),
              ),
            ),
          ),
        );
      }
    }
    

    下面的解决方案必须被视为已弃用,因为它会导致提到的包中已解决的一些问题。例如,如果快餐店被用户关闭,应用程序将关闭(请参阅hcbpassos/double_back_to_close_app#2)。

    旧答案

    您还可以选择涉及SnackBar 的解决方案。它不像Andrey Turkovsky 的回答那么简单,但它更加优雅,您不会依赖库。

    class _FooState extends State<Foo> {
      static const snackBarDuration = Duration(seconds: 3);
    
      final snackBar = SnackBar(
        content: Text('Press back again to leave'),
        duration: snackBarDuration,
      );
    
      DateTime backButtonPressTime;
    
      @override
      Widget build(BuildContext context) {
        return Scaffold(
          // The BuildContext must be from one of the Scaffold's children.
          body: Builder(
            builder: (context) {
              return WillPopScope(
                onWillPop: () => handleWillPop(context),
                child: Text('Place your child here'),
              );
            },
          ),
        );
      }
    
      Future<bool> handleWillPop(BuildContext context) async {
        final now = DateTime.now();
        final backButtonHasNotBeenPressedOrSnackBarHasBeenClosed =
            backButtonPressTime == null ||
                now.difference(backButtonPressTime) > snackBarDuration;
    
        if (backButtonHasNotBeenPressedOrSnackBarHasBeenClosed) {
          backButtonPressTime = now;
          Scaffold.of(context).showSnackBar(snackBar);
          return false;
        }
    
        return true;
      }
    }
    

    【讨论】:

      【解决方案3】:

      第一次按下返回按钮,应用程序显示一个AlertDialog“按是退出应用程序,按否无法退出应用程序”。 这是我的代码示例(我使用了“AlertDialog”)

         @override
            Widget build(BuildContext context) {
      
              return new WillPopScope(
                onWillPop: _onBackPressed,
                child: DefaultTabController(
                  initialIndex: _selectedIndex,
                  length: choices.length,
                  child: Scaffold(
                    appBar: AppBar(
      
                      ),
                    ),
      
                ),
              );
            }
               Future<bool> _onBackPressed() {
              return showDialog(
                context: context,
                builder: (context) {
                  return AlertDialog(
                    title: Text('Are you sure?'),
                    content: Text('Do you want to exit an App'),
                    actions: <Widget>[
                      FlatButton(
                        child: Text('No'),
                        onPressed: () {
                          Navigator.of(context).pop(false);
                        },
                      ),
                      FlatButton(
                        child: Text('Yes'),
                        onPressed: () {
                          Navigator.of(context).pop(true);
                        },
                      )
                    ],
                  );
                },
              ) ?? false;
            }
      

      【讨论】:

        【解决方案4】:

        这是我的回答。我使用 AlertDialog() 来实现这一点

         @override
          Widget build(BuildContext context) {
            return new WillPopScope(
              onWillPop: _onBackPressed,
              child: Scaffold(
                appBar: AppBar(),
                body: Container(),
              ),
            );
          }
          Future<bool> _onBackPressed() {
            return showDialog(
              context: context,
              builder: (context) {
                return AlertDialog(
                  title: Text('Confirm'),
                  content: Text('Do you want to exit the App'),
                  actions: <Widget>[
                    FlatButton(
                      child: Text('No'),
                      onPressed: () {
                        Navigator.of(context).pop(false); //Will not exit the App
                      },
                    ),
                    FlatButton(
                      child: Text('Yes'),
                      onPressed: () {
                        Navigator.of(context).pop(true); //Will exit the App
                      },
                    )
                  ],
                );
              },
            ) ?? false;
          }
        

        【讨论】:

          【解决方案5】:

          只需使用 double_back_to_close_app 库

          https://pub.dev/packages/double_back_to_close_app

          在pubspec.yaml文件的依赖项下添加double_back_to_close_app

          dependencies:
            double_back_to_close_app: ^1.2.0
          

          这里是示例代码

          import 'package:double_back_to_close_app/double_back_to_close_app.dart';
          import 'package:flutter/material.dart';
          
          void main() => runApp(Example());
          
          class Example extends StatelessWidget {
            @override
            Widget build(BuildContext context) {
              return MaterialApp(
                home: Scaffold(
                  body: DoubleBackToCloseApp(
                    snackBar: const SnackBar(
                      content: Text('Tap back again to leave'),
                    ),
                    child: Center(
                      child: OutlineButton(
                        child: const Text('Tap to simulate back'),
                        // ignore: invalid_use_of_protected_member
                        onPressed: WidgetsBinding.instance.handlePopRoute,
                      ),
                    ),
                  ),
                ),
              );
            }
          }
          

          只需将你的正文内容移动到“DoubleBackToCloseApp's”的孩子

          【讨论】:

            【解决方案6】:

            如果你想要一个snackbar,你应该提供一个scaffold key,因为它与scaffold相关,所以这个key应该可以在它的scaffold parent之外调用snackbar。

            这是一个解决方案:

            class Home extends StatelessWidget {
            
              final GlobalKey<ScaffoldState> _scaffoldKey = new GlobalKey<ScaffoldState>();
            
              @override
              Widget build(BuildContext context) {
              return WillPopScope(
                onWillPop: () async{
                    DateTime initTime = DateTime.now();
                    popped +=1;
                    if(popped>=2) return true;
                    await _scaffoldKey.currentState.showSnackBar(
                    SnackBar(
                        behavior: SnackBarBehavior.floating,
                        content: Text('Tap one more time to exit.',textAlign: TextAlign.center,),
                        duration: Duration(seconds: 2),
                    )).closed;
            
                    // if timer is > 2 seconds reset popped counter
                    if(DateTime.now().difference(initTime)>=Duration(seconds: 2)) {
                    popped = 0;
                    }
                    return false;
                    },
                child: Scaffold(
                    key: _scaffoldKey,
                    appBar: AppBar(title : Text("Demo")),
                    body: Text("body")
                );
              )
            }
            
            

            【讨论】:

              【解决方案7】:

              这是我的解决方案,你可以将 backPressTotal 值更改为你想要的按下次数!

              int backPressCounter = 0;
              int backPressTotal = 2;
              
              @override
              Widget build(BuildContext context) {
                return Scaffold(
                  ...
                  body: WillPopScope(child: getBody(), onWillPop: onWillPop),
                );
              }
              
              Future<bool> onWillPop() {
                  if (backPressCounter < 2) {
                    Fluttertoast.showToast(msg: "Press ${backPressTotal - backPressCounter} time to exit app");
                    backPressCounter++;
                    Future.delayed(Duration(seconds: 1, milliseconds: 500), () {
                      backPressCounter--;
                    });
                    return Future.value(false);
                  } else {
                    return Future.value(true);
                  }
              }
              

              【讨论】:

                【解决方案8】:

                如果条件是用户只按了两次,当然可以使用第一种方案。 如果你想增加点击次数,可以使用这个方案。用户必须在两秒内按 3 次才能退出

                  DateTime currentBackPressTime;
                  /// init counter of clicks
                  int pressCount=1;
                

                然后:

                Future<bool> onWillPop() async {
                
                  DateTime now = DateTime.now();
                
                
                
                /// here I check if number of clicks equal 3
                if(pressCount!=3){
                
                  ///should be assigned at the first click.
                  if(pressCount ==1 )
                    currentBackPressTime = now;
                  pressCount+=1;
                
                
                  return Future.value(false);
                  }else{
                  if (currentBackPressTime == null ||
                      now.difference(currentBackPressTime) > Duration(seconds: 2)) {
                
                
                    currentBackPressTime = now;
                    pressCount=0;
                
                  
                    return Future.value(false);
                  }
                 }
                
                
                 return Future.value(true);
                }
                

                【讨论】:

                  【解决方案9】:

                  不幸的是,它们都不适合我,我编写了一个通用类(小部件)来处理双击退出。如果有人感兴趣

                  class DoubleBackToCloseWidget extends StatefulWidget {
                    final Widget child; // Make Sure this child has a Scaffold widget as parent.
                  
                    const DoubleBackToCloseWidget({
                      @required this.child,
                    });
                  
                    @override
                    _DoubleBackToCloseWidgetState createState() =>
                        _DoubleBackToCloseWidgetState();
                  }
                  
                  class _DoubleBackToCloseWidgetState extends State<DoubleBackToCloseWidget> {
                    int _lastTimeBackButtonWasTapped;
                    static const exitTimeInMillis = 2000;
                  
                    bool get _isAndroid => Theme.of(context).platform == TargetPlatform.android;
                  
                    @override
                    Widget build(BuildContext context) {
                      if (_isAndroid) {
                        return WillPopScope(
                          onWillPop: _handleWillPop,
                          child: widget.child,
                        );
                      } else {
                        return widget.child;
                      }
                    }
                  
                    Future<bool> _handleWillPop() async {
                      final _currentTime = DateTime.now().millisecondsSinceEpoch;
                  
                      if (_lastTimeBackButtonWasTapped != null &&
                          (_currentTime - _lastTimeBackButtonWasTapped) < exitTimeInMillis) {
                        Scaffold.of(context).removeCurrentSnackBar();
                        return true;
                      } else {
                        _lastTimeBackButtonWasTapped = DateTime.now().millisecondsSinceEpoch;
                        Scaffold.of(context).removeCurrentSnackBar();
                        Scaffold.of(context).showSnackBar(
                          _getExitSnackBar(context),
                        );
                        return false;
                      }
                    }
                  
                    SnackBar _getExitSnackBar(
                      BuildContext context,
                    ) {
                      return SnackBar(
                        content: Text(
                          'Press BACK again to exit!',
                          color: Colors.white,
                        ),
                        backgroundColor: Colors.red,
                        duration: const Duration(
                          seconds: 2,
                        ),
                        behavior: SnackBarBehavior.floating,
                      );
                    }
                  }
                  
                  

                  使用这个类的方式如下:

                  class Dashboard extends StatelessWidget {
                    @override
                    Widget build(BuildContext context) {
                      return SafeArea(
                        child: Scaffold(
                          body: DoubleBackToCloseWidget(
                            child: Container(
                              child: Column(
                                children: [
                                  const Text('Hello there'),
                                  const Text('Hello there again'),
                                ],
                              ),
                            ),
                          ),
                        ),
                      );
                    }
                  }
                  
                  
                  

                  【讨论】:

                    【解决方案10】:

                    不使用包使用系统的最佳解决方案

                    SystemChannels.platform.invokeMethod<void>('SystemNavigator.pop');
                    

                    SystemNavigator.pop();
                    

                    完整代码

                    import 'package:flutter/material.dart';
                    import 'package:flutter/services.dart';
                    import 'package:fluttertoast/fluttertoast.dart';
                    
                    class ExitApp extends StatefulWidget {
                      final Widget child;
                      const ExitApp({
                        Key? key,
                        required this.child,
                      }) : super(key: key);
                    
                      @override
                      _ExitAppState createState() => _ExitAppState();
                    }
                    
                    class _ExitAppState extends State<ExitApp> {
                      @override
                      build(BuildContext context) {
                        DateTime timeBackPressed = DateTime.now();
                        return WillPopScope(
                          child: widget.child,
                          onWillPop: () async {
                            final differeance = DateTime.now().difference(timeBackPressed);
                            timeBackPressed = DateTime.now();
                            if (differeance >= Duration(seconds: 2)) {
                              final String msg = 'Press the back button to exit';
                              Fluttertoast.showToast(
                                msg: msg,
                              );
                              return false;
                            } else {
                              Fluttertoast.cancel();
                              SystemNavigator.pop();
                              return true;
                            }
                          },
                        );
                      }
                    }
                    
                    

                    【讨论】:

                      猜你喜欢
                      • 2019-10-29
                      • 2020-08-31
                      • 1970-01-01
                      • 2014-01-02
                      • 1970-01-01
                      • 2020-05-11
                      • 2019-06-17
                      • 2020-07-26
                      • 1970-01-01
                      相关资源
                      最近更新 更多