深入进阶-Flutter导航Navigator操作解析

3,877 阅读5分钟

深入进阶-Flutter Navigator详解

无论是IOS还是安卓,都有对应页面堆栈的管理。而在Flutter中,使用Navigator进行页面切换管理。需要注意的是Flutter中页面的概念对应的是Route,并不是Scaffold,Scaffold只是一个Widget,帮助我们搭建页面UI而已。那Navigator有哪些操作,对应原理是什么呢?


Push相关操作

Future push(Route route)

通过Router打开一个新页面

//通过一个列表以栈的形式维护了所有的Router信息
final List<Route<dynamic>> _history = <Route<dynamic>>[];
//根据Router信息开启一个新页面
@optionalTypeArgs
Future<T> push<T extends Object>(Route<T> route) {
  //取_history.last,也就是当前的页面Router
  final Route<dynamic> oldRoute = _history.isNotEmpty ? _history.last : null;
  route._navigator = this;
  //处理页面置顶的Overlay相关内容
  route.install(_currentOverlayEntry);
  //将当前Router添加进列表中
  _history.add(route);
  route.didPush();
  route.didChangeNext(null);
  if (oldRoute != null) {
    //回调给上一个Router信息,内部处理了一些动画状态
    oldRoute.didChangeNext(route);
    //将旧的Router回调给新的Router
    route.didChangePrevious(oldRoute);
  }
  _afterNavigation(route);
  //返回一个Future对象
  return route.popped;
}

在Flutter中,我们常用Navigator.of(context).push(Router)方法开启一个页面。查看源码可知,Flutter通过一个列表List<Route<dynamic>> _history维护所有的路由信息,整个push过程主要是就是将新的路由添加至这个列表的队尾,并进行相关的回调,最后返回了一个Future对象。这个Future对象,往往用于页面关闭时候的回调。可以理解push页面是一个带返回值的耗时操作,那什么时候这个Future会有返回值呢,肯定是当启动的这个页面pop的时候。果然,pop方法会调用到bool didPop(T result)方法中,执行这个future的 complete([FutureOr<T> value])回传返回值。


static Future pushNamed(String routeName, {Object arguments,})

通过routeName打开一个新页面

1、Navigator.dart
//根据路由名称开启一个页面
@optionalTypeArgs
static Future<T> pushNamed<T extends Object>(
  String routeName, {
  Object arguments,
 }) {
  	//最终会调用到上面的push方法,所以这里的_routeNamed会根据routeName返回一个Router
    return push<T>(_routeNamed<T>(routeName, arguments: arguments));
}
2、Navigator.dart
Route<T> _routeNamed<T>(String name, { @required Object arguments, bool allowNull = false }) {
  //根据RouteName生成一个setting信息
  final RouteSettings settings = RouteSettings(
    name: name,
    isInitialRoute: _history.isEmpty,
    arguments: arguments,
  );
  //这里的widget是Navigator,应该是Navigator初始化额时候外部传入的
  Route<T> route = widget.onGenerateRoute(settings);
  if (route == null && !allowNull) {
    route = widget.onUnknownRoute(settings);
  }
  return route;
}

pushNamed<T extends Object>(String routeName, {Object arguments,})通过routeName打开一个新页面,其实最终都是调用上面的push方法,由(2)可知这里的Router是由widget.onGenerateRoute(settings)生成的。这里的onGener肯定是在Navigator初始化的时候传入,那Navigator什么时候会初始化或者说Navigator是如何嵌套入app中的?

反向查看调用信息,发现Navigator被嵌套在WidgetsApp中,而WigetsApp在初始化的时候将下面的方法传入了Navigator。

//存储App中路由信息的Map,一般是在runApp()的时候自定义
final Map<String, WidgetBuilder> routes;
Route<dynamic> _onGenerateRoute(RouteSettings settings) {
  final String name = settings.name;
  final WidgetBuilder pageContentBuilder = name == Navigator.defaultRouteName && widget.home != null
      ? (BuildContext context) => widget.home
    	//通过路由表查找对应的Router
      : widget.routes[name];
  if (widget.onGenerateRoute != null)
    return widget.onGenerateRoute(settings);
  return null;
}

所以pushNamed<T extends Object>(String routeName, {Object arguments,})方法就是利用我们在MaterialApp中定义好的路由信息生成Router对象,最终调用push方法。后面与Name相关的都类似,就不做额外分析了。


Future pushReplacement<T extends Object, TO extends Object>(Route newRoute, { TO result })

关闭当前页面并且打开一个新的页面

Future<T> pushReplacement<T extends Object, TO extends Object>(Route<T> newRoute, { TO result }) {
  final Route<dynamic> oldRoute = _history.last;
  //这里的index就是移除的routerIndex
  final int index = _history.length - 1;
  newRoute._navigator = this;
  newRoute.install(_currentOverlayEntry);
  _history[index] = newRoute;
  newRoute.didPush().whenCompleteOrCancel(() {
    // The old route's exit is not animated. We're assuming that the
    // new route completely obscures the old one.
    if (mounted) {
      //调用上一个页面的关闭与资源释放
      oldRoute
        ..didComplete(result ?? oldRoute.currentResult)
        ..dispose();
    }
  });
  newRoute.didChangeNext(null);
  //与push保持一致的状态回调
  if (index > 0) {
    _history[index - 1].didChangeNext(newRoute);
    newRoute.didChangePrevious(_history[index - 1]);
  }
  _afterNavigation(newRoute);
  return newRoute.popped;
}

通过对比push过程发现,pushReplacement<T extends Object, TO extends Object>(Route<T> newRoute, { TO result })先将新的Router添加到集合中。在页面Push完成后,push之前的页面进行清理,这里如果方法参数中传入的result不为空,可以通知到打开上一个页面的页面,之后和push一样,进行相关的状态回调。


Future pushAndRemoveUntil(Route newRoute, RoutePredicate predicate)

通过predicate将之前所有的Router回传给调用方,从当前页面开始,关闭满足predicate的一系列页面,之后打开一个新页面。

@optionalTypeArgs
Future<T> pushAndRemoveUntil<T extends Object>(Route<T> newRoute, RoutePredicate predicate) {
  // The route that is being pushed on top of
  final Route<dynamic> precedingRoute = _history.isNotEmpty ? _history.last : null;
  // Routes to remove
  final List<Route<dynamic>> removedRoutes = <Route<dynamic>>[];
  //每次都把栈顶的Router信息回传给外界,如果predicate返回false则将这个Router加入待关闭列表中
  while (_history.isNotEmpty && !predicate(_history.last)) {
    final Route<dynamic> removedRoute = _history.removeLast();
    removedRoutes.add(removedRoute);
  }

  final Route<dynamic> newPrecedingRoute = _history.isNotEmpty ? _history.last : null;
  newRoute._navigator = this;
  newRoute.install(precedingRouteOverlay);
  _history.add(newRoute);

  newRoute.didPush().whenCompleteOrCancel(() {
    if (mounted) {
     	//当新页面启动成功后,关闭上面收集的所有页面
      for (Route<dynamic> removedRoute in removedRoutes) {
        for (NavigatorObserver observer in widget.observers)
          observer.didRemove(removedRoute, newPrecedingRoute);
        removedRoute.dispose();
      }

      if (newPrecedingRoute != null)
        newPrecedingRoute.didChangeNext(newRoute);
    }
  });
  newRoute.didChangeNext(null);
  for (NavigatorObserver observer in widget.observers)
    observer.didPush(newRoute, precedingRoute);
  _afterNavigation(newRoute);
  return newRoute.popped;
}

这个方法会循环的把栈顶的页面取出,根据predicate判断是否要销毁这个页面,直到当predicate返回ture停止,关闭了从当前页面开始满足条件的多个页面。这个操作类似于安卓设置某个页面为SingleTask的形式


Pop操作

bool pop([ T result ])


@optionalTypeArgs
bool pop<T extends Object>([ T result ]) {
  final Route<dynamic> route = _history.last;
  bool debugPredictedWouldPop;
  //didPop中,将result传给了打开这个页面的地方
  if (route.didPop(result ?? route.currentResult)) {
    if (_history.length > 1) {
      _history.removeLast();
      // If route._navigator is null, the route called finalizeRoute from
      // didPop, which means the route has already been disposed and doesn't
      // need to be added to _poppedRoutes for later disposal.
      if (route._navigator != null)
        //这里有_poppedRoutes的set集合收集退出的页面
        _poppedRoutes.add(route);
      	//回调通知当前的页面
      _history.last.didPopNext(route);
      for (NavigatorObserver observer in widget.observers)
        observer.didPop(route, _history.last);
    } else {
      return false;
    }
  }
  _afterNavigation<dynamic>(route);
  return true;
}

pop的操作非常直观,将当前栈顶的页面关闭,并且将result作为返回结果回传给启动这个页面的地方。


Remove操作

void removeRoute(Route route)

将指定的Route移除

void removeRoute(Route<dynamic> route) {
  final int index = _history.indexOf(route);
  final Route<dynamic> previousRoute = index > 0 ? _history[index - 1] : null;
  final Route<dynamic> nextRoute = (index + 1 < _history.length) ? _history[index + 1] : null;
  _history.removeAt(index);
  previousRoute?.didChangeNext(nextRoute);
  nextRoute?.didChangePrevious(previousRoute);
  for (NavigatorObserver observer in widget.observers)
    observer.didRemove(route, previousRoute);
  route.dispose();
  _afterNavigation<dynamic>(nextRoute);
}

将制定的route从栈中移除,并且通知这个rout前后的页面。