Flutter中实现双击返回键退出APP

先来认识几个小知识点

WillPopScope

flutter官方提供的widget,注册一个异步函数,用于捕获并处理用户的返回键操作(系统提供的返回操作和Navigator.of(context).maybePop()),可以允许或禁止本次操作。我一般是包在Scaffold外面

WillPopScope(
	onWillPop : () async =>{
		// 一些其他操作
		return true;
	}
	child : Scaffold(
		body : Container()
	);
)

Navigator

这个不用过多赘述了,大家用到路由应该都用过,这次我们要用到其中一个静态方法:canPop,这个方法判断路由是否还能返回。

看到这,相信你已经有头绪了,尝试自己试试先敲一下?

流程分析

  1. WillPopScope捕获用户操作
  2. Navigator.of(context).canPop()判断是否可以返回,如果可以,返回true,正常执行,如果不能,返回false
  3. 阻止返回后,记录本次返回,根据与下次返回的时间关系来判断是否要退出APP

代码

// 退出APP标记
bool isQuit = false;
// 清除标记定时器
Timer quitTimer;

// 其他代码...

Widget build(BuildContext context){
	return  WillPopScope(
		// 捕获用户操作,判断能否返回
		onWillPop: () async {
		// 可以返回
          if(!Navigator.canPop(context)){
          // 检查退出标记
            if(isQuit){
           	  // 退出APP
              await SystemChannels.platform.invokeMethod('SystemNavigator.pop');
            }else{
              // 我包装的提示工具类,提示用户再次点击退出,使用的fluttertoast插件
              ToastUtils.showToast(TipsTextConfig.quitApp);
              // 记录退出标记
              isQuit = true;
              // 开启定时器,两秒后清除标记
              quitTimer = Timer(Duration(seconds: 2) , (){
                isQuit = false;
                quitTimer.cancel();
              });
            }
            // 阻止返回
            return false;
          }
          // 默认正常运行
          return true;
        },
        child : Scaffold(
        	body : Container(),
        )
	)
}

没什么用的小技巧

  • WillPopScope不止可以用在Scaffold上,比如在showDialog中使用,可以阻止用户通过返回键关闭dialog窗口
showDialog(
	context : context , 
	child : WillPopScope(
		onWillPop : () async => true , 
		child : SimpleDialog(),
	)
)
  • 可以自己包装一个CustomScaffold,这样可以做一些自己的设置,而且可以公用双击退出APP的代码
/// 包装Scaffold
/// 添加了安全区
/// 添加了防止元素大小随手机字体大小设置改变
class CustomScaffold extends StatelessWidget {
  final bool extendBody;
  final bool extendBodyBehindAppBar;
  final PreferredSizeWidget appBar;
  final Widget body;
  final Widget floatingActionButton;
  final FloatingActionButtonLocation floatingActionButtonLocation;
  final FloatingActionButtonAnimator floatingActionButtonAnimator;
  final List<Widget> persistentFooterButtons;
  final Widget drawer;
  final Widget endDrawer;
  final Color drawerScrimColor;
  final Color backgroundColor;
  final Widget bottomNavigationBar;
  final Widget bottomSheet;
  final bool resizeToAvoidBottomPadding;
  final bool resizeToAvoidBottomInset;
  final bool primary;
  final DragStartBehavior drawerDragStartBehavior;
  final double drawerEdgeDragWidth;
  final bool drawerEnableOpenDragGesture;
  final bool endDrawerEnableOpenDragGesture;
  final bool safeArea;

  CustomScaffold({
    Key key,
    this.appBar,
    this.body,
    this.floatingActionButton,
    this.floatingActionButtonLocation,
    this.floatingActionButtonAnimator,
    this.persistentFooterButtons,
    this.drawer,
    this.endDrawer,
    this.bottomNavigationBar,
    this.bottomSheet,
    this.backgroundColor,
    this.resizeToAvoidBottomPadding,
    this.resizeToAvoidBottomInset,
    this.primary = true,
    this.drawerDragStartBehavior = DragStartBehavior.start,
    this.extendBody = false,
    this.extendBodyBehindAppBar = false,
    this.drawerScrimColor,
    this.drawerEdgeDragWidth,
    this.drawerEnableOpenDragGesture = true,
    this.endDrawerEnableOpenDragGesture = true,
    this.safeArea = true
  }) : super(key: key);

  @override
  Widget build(BuildContext context) {
    // 退出APP标记
    bool isQuit = false;
    Timer quitTimer;

    return MediaQuery(
      data: MediaQuery.of(context).copyWith(textScaleFactor: 1.0),
      child: WillPopScope(
        onWillPop: () async {
          if(!Navigator.canPop(context)){
            if(isQuit){
              await SystemChannels.platform.invokeMethod('SystemNavigator.pop');
            }else{
              ToastUtils.showToast(TipsTextConfig.quitApp);
              isQuit = true;
              quitTimer = Timer(Duration(seconds: 2) , (){
                isQuit = false;
                quitTimer.cancel();
              });
            }
            return false;
          }
          return true;
        },
        child: Scaffold(
          appBar: this.appBar,
          body: SafeArea(
            top: safeArea,
            bottom: safeArea,
            left: safeArea,
            right: safeArea,
            child: this.body,
          ),
          floatingActionButton: this.floatingActionButton,
          floatingActionButtonLocation: this.floatingActionButtonLocation,
          floatingActionButtonAnimator: this.floatingActionButtonAnimator,
          persistentFooterButtons: this.persistentFooterButtons,
          drawer: this.drawer,
          endDrawer: this.endDrawer,
          bottomNavigationBar: this.bottomNavigationBar,
          bottomSheet: this.bottomSheet,
          backgroundColor: this.backgroundColor,
          resizeToAvoidBottomPadding: this.resizeToAvoidBottomPadding,
          resizeToAvoidBottomInset: this.resizeToAvoidBottomInset,
          primary: this.primary,
          drawerDragStartBehavior: this.drawerDragStartBehavior,
          extendBody: this.extendBody,
          extendBodyBehindAppBar: this.extendBodyBehindAppBar,
          drawerScrimColor: this.drawerScrimColor,
          drawerEdgeDragWidth: this.drawerEdgeDragWidth,
          drawerEnableOpenDragGesture: this.drawerEnableOpenDragGesture,
          endDrawerEnableOpenDragGesture: this.endDrawerEnableOpenDragGesture,
        ),
      ),
    );
  }
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值