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的决策。