Flutter实现连续点击返回键退出app

android app 如果调用栈处于最顶层,点击返回会退出app,Flutter如何实现点击一次不退出,提示用户再点一次返回键退出应用呢,先上代码

   bool nextKickBackExitApp = false;
   
  @override
  Widget build(BuildContext context) {
    // WillPopScope 用于android点击back按键退出app,目前判断逻辑就是2秒内点击两次就退出app
    return Scaffold(
      body: WillPopScope(
        onWillPop: () {
          if (nextKickBackExitApp) {
            return Future<bool>.value(true);
          } else {
            //todo 显示toast消息: 再按一次退出app
            nextKickBackExitApp = true;
            Future.delayed(
              const Duration(seconds: 2),
              () => nextKickBackExitApp = false,
            );
            return Future<bool>.value(false);
          }
        },
        child: Column(
          children: <Widget>[
       		//.....
          ],
        ),
      ),
    );
  }

Navigator执行pop该route时先调用WillPopScope 这个widget的onWillPop回掉,如果确实需要pop,就在onWillPop里返回Future.value(true),否则就是阻止pop

代码实现逻辑就是第一次点击时先阻止pop,记录下次点击需要退出的标志nextKickBackExitApp,延时2秒后重置该变量,如果在2秒内又点击了返回键就不阻止pop了,默认调用SystemNavigator的退出应用操作了

我们来研究下WillPopScope是怎么实现的

class WillPopScope extends StatefulWidget {
  /// Creates a widget that registers a callback to veto attempts by the user to
  /// dismiss the enclosing [ModalRoute].
  ///
  /// The [child] argument must not be null.
  const WillPopScope({
    Key key,
    @required this.child,
    @required this.onWillPop,
  }) : assert(child != null),
       super(key: key);

  /// The widget below this widget in the tree.
  ///
  /// {@macro flutter.widgets.child}
  final Widget child;

  /// Called to veto attempts by the user to dismiss the enclosing [ModalRoute].
  ///
  /// If the callback returns a Future that resolves to false, the enclosing
  /// route will not be popped.
  final WillPopCallback onWillPop;

  @override
  _WillPopScopeState createState() => _WillPopScopeState();
}

class _WillPopScopeState extends State<WillPopScope> {
  ModalRoute<dynamic> _route;

  @override
  void didChangeDependencies() {
    super.didChangeDependencies();
    if (widget.onWillPop != null)
      _route?.removeScopedWillPopCallback(widget.onWillPop);
    _route = ModalRoute.of(context);
    if (widget.onWillPop != null)
      _route?.addScopedWillPopCallback(widget.onWillPop);
  }

  @override
  void didUpdateWidget(WillPopScope oldWidget) {
    super.didUpdateWidget(oldWidget);
    assert(_route == ModalRoute.of(context));
    if (widget.onWillPop != oldWidget.onWillPop && _route != null) {
      if (oldWidget.onWillPop != null)
        _route.removeScopedWillPopCallback(oldWidget.onWillPop);
      if (widget.onWillPop != null)
        _route.addScopedWillPopCallback(widget.onWillPop);
    }
  }

  @override
  void dispose() {
    if (widget.onWillPop != null)
      _route?.removeScopedWillPopCallback(widget.onWillPop);
    super.dispose();
  }

  @override
  Widget build(BuildContext context) => widget.child;
}

我们发现didChangeDependencies中调用了
if (widget.onWillPop != null)
_route.addScopedWillPopCallback(widget.onWillPop);

大概意思就是在route中把WillPopScope.onWillPop加入willPopCall中

接下来看看addScopedWillPopCallback的实现

  final List<WillPopCallback> _willPopCallbacks = <WillPopCallback>[];
  void addScopedWillPopCallback(WillPopCallback callback) {
    assert(_scopeKey.currentState != null, 'Tried to add a willPop callback to a route that is not currently in the tree.');
    _willPopCallbacks.add(callback);
  }

把onWillPop加入_willPopCallbacks中,_willPopCallbacks在什么地方使用到了呢

 /// Returns the value of the first callback added with
  /// [addScopedWillPopCallback] that returns false. If they all return true,
  /// returns the inherited method's result (see [Route.willPop]).
  ///
  /// Typically this method is not overridden because applications usually
  /// don't create modal routes directly, they use higher level primitives
  /// like [showDialog]. The scoped [WillPopCallback] list makes it possible
  /// for ModalRoute descendants to collectively define the value of `willPop`.
  ///
  /// See also:
  ///
  ///  * [Form], which provides an `onWillPop` callback that uses this mechanism.
  ///  * [addScopedWillPopCallback], which adds a callback to the list this
  ///    method checks.
  ///  * [removeScopedWillPopCallback], which removes a callback from the list
  ///    this method checks.
  @override
  Future<RoutePopDisposition> willPop() async {
    final _ModalScopeState<T> scope = _scopeKey.currentState;
    assert(scope != null);
    for (WillPopCallback callback in List<WillPopCallback>.from(_willPopCallbacks)) {
      if (!await callback())
        return RoutePopDisposition.doNotPop;
    }
    return await super.willPop();
  }

源码很简单,callback是返回值是Future, 如果_willPopCallbacks中某个callback返回了false,就返回RoutePopDisposition.doNotPop,表示阻止route的pop,否则就返回Route.willPop,那willPop在哪调用的呢,最后定位到Navigator中了

 /// Returns the value of the current route's [Route.willPop] method for the
  /// navigator that most tightly encloses the given context.
  ///
  /// {@template flutter.widgets.navigator.maybePop}
  /// This method is typically called before a user-initiated [pop]. For example
  /// on Android it's called by the binding for the system's back button.
  ///
  /// The `T` type argument is the type of the return value of the current
  /// route.
  /// {@endtemplate}
  ///
  /// See also:
  ///
  ///  * [Form], which provides an `onWillPop` callback that enables the form
  ///    to veto a [pop] initiated by the app's back button.
  ///  * [ModalRoute], which provides a `scopedWillPopCallback` that can be used
  ///    to define the route's `willPop` method.
  @optionalTypeArgs
  static Future<bool> maybePop<T extends Object>(BuildContext context, [ T result ]) {
    return Navigator.of(context).maybePop<T>(result);
  }

先看注释
/// This method is typically called before a user-initiated [pop]. For example
/// on Android it’s called by the binding for the system’s back button.
意思是在用户发起pop时,什么时候能发起pop呢,比如android中点击返回键时
找到这里离目标应该不远了,该方法只调用了Navigator 的maybePop 方法,那好我们去看看该方法


  /// Returns the value of the current route's [Route.willPop] method for the
  /// navigator.
  ///
  /// {@macro flutter.widgets.navigator.maybePop}
  ///
  /// See also:
  ///
  ///  * [Form], which provides an `onWillPop` callback that enables the form
  ///    to veto a [pop] initiated by the app's back button.
  ///  * [ModalRoute], which provides a `scopedWillPopCallback` that can be used
  ///    to define the route's `willPop` method.
  @optionalTypeArgs
  Future<bool> maybePop<T extends Object>([ T result ]) async {
    final Route<T> route = _history.last;
    assert(route._navigator == this);
    final RoutePopDisposition disposition = await route.willPop();
    if (disposition != RoutePopDisposition.bubble && mounted) {
      if (disposition == RoutePopDisposition.pop)
        pop(result);
      return true;
    }
    return false;
  }

到这里总算找到调用的地方了,到此它的庐山真面目总算浮现了,大致的流程我们再理一下,如果route的willPop返回的是RoutePopDisposition.pop route就会pop, 而route的willPop又会先调用注册的_willPopCallbacks的来判断是否pop,WillPopScope作用就是在route中添加一个onWillPop callback,等Navigator调用route pop事件时,来参与是否pop的决策。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值