在flutter中,如果当前页面已经是路由栈上的最后一个页面时,当我们点击返回键,会导致app退出,这通常是不能被接受的,下面我们来谈谈如何用更合适的方法解决这个问题。

# 第一种实现

通过WillPopScope来实现返回事件的拦截,防止app直接退出,通常情况下这种办法没有任何问题,但是在写法上就比较繁琐了。

假如,有APage这个样一个一面,有时我们希望它成为第一个页面,及点击返回时直接退出app,有时又希望它作为其它页面的子页面存在,不想在返回按键的退出,那么我们就需要再WillPopScope中做判断当前是应该返回还退出。

# 第二种实现

如果有这样一个地方可以监听所有的返回事件我们就可以做统一的处理,不过不幸的是并没有这个地方。

那么换种思路,通常根页面是不会出现返回按钮的,我们只能通过返回按键来触发返回,此时我们可以通过WidgetsBindingObserver来实现。

虽然WidgetsBindingObserver可以做全局拦截,但是我们没有办法判断当前激活的页面是不是根页面(第一个页面),按照前面我们对WidgetsBindingObserver的讲解,因为我们自定义的WidgetsBindingObserver是作为MaterialApp的父存在的,而路由的生成是在WidgetsApp中才生出的,所以我们没有办法拿到子的context,因此也无法判断当前活动的页面是谁,现在我们的问题转变成了如何获取context。

# 第三种实现

基于第二种实现,如果我们首次进来时将首屏设置为loading,在loading页时记录下context,并缓存到全局变量,看者可行,因为此时的context是可以获取路由信息的。

但是我们要跳转到真正的首页,所以不得不使用由于pushNamedAndRemoveUntil将路由栈中的loading页移除了,并将真正的首页推入栈中,这一切看时没有问题,但当我们使用Navigator.of(globalContext).canPop()判断是否可以pop时会触发异常提示,具体原因是:因为我们从栈上移除了loading,那么loading中缓存context已经是不安全的了,所以会触发异常提示,看来这种方式形同。

# 第四种实现

基于第二种,当我们push页面的时候就记录下当前页面是谁,并记录真正的首页是谁,当点击返回按钮时判断当前页面是不是首页,从而做出要不要退出app的绝对,不过这种方式当遇见页面循环时需要额外添加更多的状态才行,而且我们还要拦截push事件,以便记录下当前页面是谁,工作量大。

# 第五种实现

基于第二种,通过在MaterialApp.navigatorObservers种添加一个自定义的观察者,当页面发生变动时,这个观察者的navigator值会始终指向最新的页面,从而我们就可以获取正确的navigator对象.

import 'package:flutter/material.dart';
import 'package:overlooking/home.dart';
import 'package:overlooking/home2.dart';
import 'package:overlooking/welcome.dart';
class BackToDesktopController extends NavigatorObserver{
}
class NavigatorObserversDemo extends StatefulWidget {
  
  State<StatefulWidget> createState() {
    return _NavigatorObserversDemo();
  }
}
class _NavigatorObserversDemo extends State<NavigatorObserversDemo> with WidgetsBindingObserver {
  BackToDesktopController _desktopController;
  
  void initState() {
    super.initState();
    _desktopController = new BackToDesktopController();
    WidgetsBinding.instance.addObserver(this);
  }
  
  void dispose() {
    WidgetsBinding.instance.removeObserver(this);
    super.dispose();
  }
  
  Future<bool> didPopRoute() async {
    if (_desktopController.navigator != null) {
      if (!_desktopController.navigator.canPop()) {
        print("回到桌面");
        return true;
      }
    }
    return super.didPopRoute();
  }
  
  Widget build(BuildContext context) {
    return MaterialApp(
        title: 'Flutter Demo',
        theme: ThemeData(
          primarySwatch: Colors.blue,
        ),
        //注册我们的自定义导航观察者
        navigatorObservers: [_desktopController],
        routes: {
          "/": (context) => HomePage(),
          "/home2": (context) => Home2Page(),
          "/welcome": (context) => WelcomePage(),
        },
        initialRoute: "/welcome");
  }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60

# 第六种实现

基于第二种实现,与第五中类似只不过采用另外的属性。

import 'package:flutter/material.dart';
import 'package:overlooking/home.dart';
import 'package:overlooking/home2.dart';
import 'package:overlooking/welcome.dart';
class BackToDesktopController extends NavigatorObserver{
}
class NavigatorObserversDemo extends StatefulWidget {
  
  State<StatefulWidget> createState() {
    return _NavigatorObserversDemo();
  }
}
class _NavigatorObserversDemo extends State<NavigatorObserversDemo> with WidgetsBindingObserver {
  GlobalKey<NavigatorState> _globalKey = GlobalKey();
  
  void initState() {
    super.initState();
    _desktopController = new BackToDesktopController();
    WidgetsBinding.instance.addObserver(this);
  }
  
  void dispose() {
    WidgetsBinding.instance.removeObserver(this);
    super.dispose();
  }
  
  Future<bool> didPopRoute() async {
    if(_globalKey.currentState!=null){
      if(!_globalKey.currentState.canPop()){
        print("回到桌面");
        return true;
      }
    }
    return super.didPopRoute();
  }
  
  Widget build(BuildContext context) {
    return MaterialApp(
        title: 'Flutter Demo',
        theme: ThemeData(
          primarySwatch: Colors.blue,
        ),
        navigatorKey: _globalKey,
        routes: {
          "/": (context) => HomePage(),
          "/home2": (context) => Home2Page(),
          "/welcome": (context) => WelcomePage(),
        },
        initialRoute: "/welcome");
  }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59